1- // Copyright 2022 Oxide Computer Company
1+ // Copyright 2026 Oxide Computer Company
22
33use p4:: ast:: { BinOp , DeclarationInfo , Expression , ExpressionKind , Lvalue } ;
44use p4:: hlir:: Hlir ;
@@ -101,6 +101,25 @@ impl<'a> ExpressionGenerator<'a> {
101101 ts. extend ( op_tks) ;
102102 ts. extend ( rhs_tks_) ;
103103 }
104+ BinOp :: BitOr | BinOp :: BitAnd | BinOp :: Xor | BinOp :: Mask => {
105+ ts. extend ( quote ! {
106+ {
107+ let __lhs = #lhs_tks. clone( ) ;
108+ let __rhs = #rhs_tks. clone( ) ;
109+ __lhs #op_tks __rhs
110+ }
111+ } ) ;
112+ }
113+ BinOp :: Shl => {
114+ ts. extend ( quote ! {
115+ p4rs:: bitmath:: shl_le( #lhs_tks. clone( ) , #rhs_tks. clone( ) )
116+ } ) ;
117+ }
118+ BinOp :: Shr => {
119+ ts. extend ( quote ! {
120+ p4rs:: bitmath:: shr_le( #lhs_tks. clone( ) , #rhs_tks. clone( ) )
121+ } ) ;
122+ }
104123 _ => {
105124 ts. extend ( lhs_tks) ;
106125 ts. extend ( op_tks) ;
@@ -111,22 +130,42 @@ impl<'a> ExpressionGenerator<'a> {
111130 }
112131 ExpressionKind :: Index ( lval, xpr) => {
113132 let mut ts = self . generate_lvalue ( lval) ;
114- ts. extend ( self . generate_expression ( xpr. as_ref ( ) ) ) ;
133+ // For slices, look up the parent field's bit width
134+ // so generate_slice can adjust for header.rs byte
135+ // reversal.
136+ if let ExpressionKind :: Slice ( begin, end) = & xpr. kind {
137+ let ni =
138+ self . hlir . lvalue_decls . get ( lval) . unwrap_or_else ( || {
139+ panic ! ( "unresolved lvalue {:#?} in slice" , lval)
140+ } ) ;
141+
142+ let field_width = match & ni. ty {
143+ p4:: ast:: Type :: Bit ( w)
144+ | p4:: ast:: Type :: Varbit ( w)
145+ | p4:: ast:: Type :: Int ( w) => * w,
146+ ty => panic ! (
147+ "slice on non-bit type {:?} reached codegen" ,
148+ ty,
149+ ) ,
150+ } ;
151+ let ( hi, lo) = Self :: slice_bounds ( begin, end) ;
152+ if Self :: slice_is_contiguous ( hi, lo, field_width) {
153+ ts. extend ( self . generate_slice ( begin, end, field_width) ) ;
154+ } else {
155+ // Non-contiguous after byte reversal;
156+ // replace the lvalue suffix with arithmetic.
157+ return Self :: generate_slice_read_arith ( & ts, hi, lo) ;
158+ }
159+ } else {
160+ ts. extend ( self . generate_expression ( xpr. as_ref ( ) ) ) ;
161+ }
115162 ts
116163 }
117- ExpressionKind :: Slice ( begin, end) => {
118- let l = match & begin. kind {
119- ExpressionKind :: IntegerLit ( v) => * v as usize ,
120- _ => panic ! ( "slice ranges can only be integer literals" ) ,
121- } ;
122- let l = l + 1 ;
123- let r = match & end. kind {
124- ExpressionKind :: IntegerLit ( v) => * v as usize ,
125- _ => panic ! ( "slice ranges can only be integer literals" ) ,
126- } ;
127- quote ! {
128- [ #r..#l]
129- }
164+ ExpressionKind :: Slice ( _begin, _end) => {
165+ // The HLIR rejects bare slices outside an Index
166+ // expression, so this is unreachable for well-typed
167+ // programs.
168+ unreachable ! ( "bare Slice reached codegen" ) ;
130169 }
131170 ExpressionKind :: Call ( call) => {
132171 let lv: Vec < TokenStream > = call
@@ -158,6 +197,84 @@ impl<'a> ExpressionGenerator<'a> {
158197 }
159198 }
160199
200+ /// Extract compile-time hi and lo from slice bound expressions.
201+ pub ( crate ) fn slice_bounds (
202+ begin : & Expression ,
203+ end : & Expression ,
204+ ) -> ( P4Bit , P4Bit ) {
205+ let hi: P4Bit = match & begin. kind {
206+ ExpressionKind :: IntegerLit ( v) => * v as usize ,
207+ _ => panic ! ( "slice ranges can only be integer literals" ) ,
208+ } ;
209+ let lo: P4Bit = match & end. kind {
210+ ExpressionKind :: IntegerLit ( v) => * v as usize ,
211+ _ => panic ! ( "slice ranges can only be integer literals" ) ,
212+ } ;
213+ ( hi, lo)
214+ }
215+
216+ /// Whether `[hi:lo]` on a field of `field_width` bits can be
217+ /// expressed as a contiguous bitvec range after byte reversal.
218+ pub ( crate ) fn slice_is_contiguous (
219+ hi : P4Bit ,
220+ lo : P4Bit ,
221+ field_width : FieldWidth ,
222+ ) -> bool {
223+ if field_width <= 8 {
224+ return true ;
225+ }
226+ // Non-byte-multiple widths have an additional bit-shift in
227+ // header.rs storage that reversed_slice_range does not model.
228+ if !field_width. is_multiple_of ( 8 ) {
229+ return false ;
230+ }
231+ reversed_slice_range ( hi, lo, field_width) . is_some ( )
232+ }
233+
234+ pub ( crate ) fn generate_slice (
235+ & self ,
236+ begin : & Expression ,
237+ end : & Expression ,
238+ field_width : FieldWidth ,
239+ ) -> TokenStream {
240+ let ( hi, lo) = Self :: slice_bounds ( begin, end) ;
241+
242+ if field_width > 8 {
243+ let ( r, l) = reversed_slice_range ( hi, lo, field_width) . expect (
244+ "non-contiguous slice reads must be handled \
245+ by the caller via generate_slice_read_arith",
246+ ) ;
247+ quote ! { [ #r..#l] }
248+ } else {
249+ // Fields <= 8 bits are not byte-reversed by header.rs,
250+ // so the naive P4-to-bitvec mapping is correct.
251+ let l = hi + 1 ;
252+ let r = lo;
253+ quote ! { [ #r..#l] }
254+ }
255+ }
256+
257+ /// Emit an arithmetic slice read for non-contiguous slices.
258+ /// Loads the field as an integer, shifts and masks to extract
259+ /// the requested bits, then packs into a new bitvec.
260+ pub ( crate ) fn generate_slice_read_arith (
261+ lhs : & TokenStream ,
262+ hi : P4Bit ,
263+ lo : P4Bit ,
264+ ) -> TokenStream {
265+ let slice_width = hi - lo + 1 ;
266+ let mask_val = ( 1u128 << slice_width) - 1 ;
267+ quote ! {
268+ {
269+ let __v: u128 = #lhs. load_le( ) ;
270+ let __extracted = ( __v >> #lo) & #mask_val;
271+ let mut __out = bitvec![ u8 , Msb0 ; 0 ; #slice_width] ;
272+ __out. store_le( __extracted) ;
273+ __out
274+ }
275+ }
276+ }
277+
161278 pub ( crate ) fn generate_bit_literal (
162279 & self ,
163280 width : u16 ,
@@ -191,6 +308,8 @@ impl<'a> ExpressionGenerator<'a> {
191308 BinOp :: BitAnd => quote ! { & } ,
192309 BinOp :: BitOr => quote ! { | } ,
193310 BinOp :: Xor => quote ! { ^ } ,
311+ BinOp :: Shl => quote ! { << } ,
312+ BinOp :: Shr => quote ! { >> } ,
194313 }
195314 }
196315
@@ -223,3 +342,160 @@ impl<'a> ExpressionGenerator<'a> {
223342 }
224343 }
225344}
345+
346+ /// P4 bit position (MSB-first index within a field).
347+ type P4Bit = usize ;
348+
349+ /// Width of a P4 header field in bits.
350+ type FieldWidth = usize ;
351+
352+ /// Half-open bitvec range `(start, end)` into the storage representation.
353+ type BitvecRange = ( usize , usize ) ;
354+
355+ /// Map a P4 slice `[hi:lo]` to a bitvec range in byte-reversed storage.
356+ ///
357+ /// header.rs reverses byte order for fields wider than 8 bits. Bit
358+ /// positions within each byte are preserved (Msb0). The mapping from
359+ /// P4 bit positions to storage indices:
360+ ///
361+ /// ```text
362+ /// wire_idx = W - 1 - b
363+ /// wire_byte = wire_idx / 8
364+ /// bit_in_byte = wire_idx % 8
365+ /// storage_byte = W/8 - 1 - wire_byte
366+ /// bitvec_idx = storage_byte * 8 + bit_in_byte
367+ /// ```
368+ ///
369+ /// # Returns
370+ ///
371+ /// `Some(range)` when the slice maps to a contiguous bitvec range
372+ /// (single-byte slices or byte-aligned multi-byte slices), `None`
373+ /// for non-byte-aligned multi-byte slices where byte reversal makes
374+ /// the bits non-contiguous.
375+ pub ( crate ) fn reversed_slice_range (
376+ hi : P4Bit ,
377+ lo : P4Bit ,
378+ field_width : FieldWidth ,
379+ ) -> Option < BitvecRange > {
380+ // Wire byte indices for the slice endpoints. P4 bit W-1 is in wire
381+ // byte 0 (MSB-first), so higher bit numbers map to lower byte indices.
382+ let wire_byte_hi = ( field_width - 1 - hi) / 8 ;
383+ let wire_byte_lo = ( field_width - 1 - lo) / 8 ;
384+
385+ if wire_byte_hi == wire_byte_lo {
386+ // Single-byte slice: map each endpoint individually.
387+ let map_bit = |bit_pos : usize | -> usize {
388+ let wire_idx = field_width - 1 - bit_pos;
389+ let wire_byte = wire_idx / 8 ;
390+ let bit_in_byte = wire_idx % 8 ;
391+ let storage_byte = field_width / 8 - 1 - wire_byte;
392+ storage_byte * 8 + bit_in_byte
393+ } ;
394+
395+ let mapped_hi = map_bit ( hi) ;
396+ let mapped_lo = map_bit ( lo) ;
397+ Some ( ( mapped_hi. min ( mapped_lo) , mapped_hi. max ( mapped_lo) + 1 ) )
398+ } else if ( hi + 1 ) . is_multiple_of ( 8 ) && lo. is_multiple_of ( 8 ) {
399+ // Multi-byte byte-aligned slice: reversed bytes form a
400+ // contiguous block.
401+ let storage_byte_start = field_width / 8 - 1 - wire_byte_lo;
402+ let storage_byte_end = field_width / 8 - 1 - wire_byte_hi;
403+ Some ( ( storage_byte_start * 8 , ( storage_byte_end + 1 ) * 8 ) )
404+ } else {
405+ // Non-byte-aligned multi-byte slice: byte reversal makes the
406+ // bits non-contiguous, so there is no single bitvec range.
407+ None
408+ }
409+ }
410+
411+ #[ cfg( test) ]
412+ mod tests {
413+ use super :: * ;
414+
415+ // Verify the reversed slice range mapping against the byte reversal
416+ // in header.rs. For each case we check that the bitvec range lands
417+ // on the correct bits in the reversed storage layout.
418+
419+ // Sub-byte slices within a single wire byte.
420+
421+ #[ test]
422+ fn slice_32bit_top_nibble ( ) {
423+ // P4 [31:28] on 32-bit: top nibble of wire byte 0.
424+ // Storage: wire byte 0 -> storage byte 3.
425+ // High nibble of storage byte 3 = bitvec [24..28].
426+ assert_eq ! ( reversed_slice_range( 31 , 28 , 32 ) , Some ( ( 24 , 28 ) ) ) ;
427+ }
428+
429+ #[ test]
430+ fn slice_32bit_bottom_nibble ( ) {
431+ // P4 [3:0] on 32-bit: bottom nibble of wire byte 3.
432+ // Storage: wire byte 3 -> storage byte 0.
433+ // Low nibble (Msb0) of storage byte 0 = bitvec [4..8].
434+ assert_eq ! ( reversed_slice_range( 3 , 0 , 32 ) , Some ( ( 4 , 8 ) ) ) ;
435+ }
436+
437+ #[ test]
438+ fn slice_16bit_top_nibble ( ) {
439+ // P4 [15:12] on 16-bit: top nibble of wire byte 0.
440+ // Storage: wire byte 0 -> storage byte 1.
441+ // High nibble of storage byte 1 = bitvec [8..12].
442+ assert_eq ! ( reversed_slice_range( 15 , 12 , 16 ) , Some ( ( 8 , 12 ) ) ) ;
443+ }
444+
445+ // Full-byte slices (single byte).
446+
447+ #[ test]
448+ fn slice_128bit_top_byte ( ) {
449+ // P4 [127:120] on 128-bit: wire byte 0 -> storage byte 15.
450+ // bitvec [120..128].
451+ assert_eq ! ( reversed_slice_range( 127 , 120 , 128 ) , Some ( ( 120 , 128 ) ) ) ;
452+ }
453+
454+ #[ test]
455+ fn slice_16bit_low_byte ( ) {
456+ // P4 [7:0] on 16-bit: wire byte 1 -> storage byte 0.
457+ // bitvec [0..8].
458+ assert_eq ! ( reversed_slice_range( 7 , 0 , 16 ) , Some ( ( 0 , 8 ) ) ) ;
459+ }
460+
461+ #[ test]
462+ fn slice_32bit_middle_byte ( ) {
463+ // P4 [23:16] on 32-bit: wire byte 1 -> storage byte 2.
464+ // bitvec [16..24].
465+ assert_eq ! ( reversed_slice_range( 23 , 16 , 32 ) , Some ( ( 16 , 24 ) ) ) ;
466+ }
467+
468+ // Multi-byte byte-aligned slices.
469+
470+ #[ test]
471+ fn slice_128bit_top_two_bytes ( ) {
472+ // P4 [127:112] on 128-bit: wire bytes 0-1 -> storage bytes 14-15.
473+ // bitvec [112..128].
474+ assert_eq ! ( reversed_slice_range( 127 , 112 , 128 ) , Some ( ( 112 , 128 ) ) ) ;
475+ }
476+
477+ #[ test]
478+ fn slice_32bit_top_three_bytes ( ) {
479+ // P4 [31:8] on 32-bit: wire bytes 0-2 -> storage bytes 1-3.
480+ // bitvec [8..32].
481+ assert_eq ! ( reversed_slice_range( 31 , 8 , 32 ) , Some ( ( 8 , 32 ) ) ) ;
482+ }
483+
484+ #[ test]
485+ fn slice_32bit_bottom_two_bytes ( ) {
486+ // P4 [15:0] on 32-bit: wire bytes 2-3 -> storage bytes 0-1.
487+ // bitvec [0..16].
488+ assert_eq ! ( reversed_slice_range( 15 , 0 , 32 ) , Some ( ( 0 , 16 ) ) ) ;
489+ }
490+
491+ #[ test]
492+ fn slice_48bit_upper_24 ( ) {
493+ assert_eq ! ( reversed_slice_range( 47 , 24 , 48 ) , Some ( ( 24 , 48 ) ) ) ;
494+ }
495+
496+ #[ test]
497+ fn slice_non_contiguous_returns_none ( ) {
498+ assert_eq ! ( reversed_slice_range( 11 , 4 , 32 ) , None ) ;
499+ assert_eq ! ( reversed_slice_range( 22 , 0 , 32 ) , None ) ;
500+ }
501+ }
0 commit comments