|
1 | | -// Copyright 2022 Oxide Computer Company |
| 1 | +// Copyright 2026 Oxide Computer Company |
2 | 2 |
|
3 | 3 | use p4::ast::{BinOp, DeclarationInfo, Expression, ExpressionKind, Lvalue}; |
4 | 4 | use p4::hlir::Hlir; |
@@ -111,22 +111,42 @@ impl<'a> ExpressionGenerator<'a> { |
111 | 111 | } |
112 | 112 | ExpressionKind::Index(lval, xpr) => { |
113 | 113 | let mut ts = self.generate_lvalue(lval); |
114 | | - ts.extend(self.generate_expression(xpr.as_ref())); |
| 114 | + // For slices, look up the parent field's bit width |
| 115 | + // so generate_slice can adjust for header.rs byte |
| 116 | + // reversal. |
| 117 | + if let ExpressionKind::Slice(begin, end) = &xpr.kind { |
| 118 | + let ni = |
| 119 | + self.hlir.lvalue_decls.get(lval).unwrap_or_else(|| { |
| 120 | + panic!("unresolved lvalue {:#?} in slice", lval) |
| 121 | + }); |
| 122 | + |
| 123 | + let field_width = match &ni.ty { |
| 124 | + p4::ast::Type::Bit(w) |
| 125 | + | p4::ast::Type::Varbit(w) |
| 126 | + | p4::ast::Type::Int(w) => *w, |
| 127 | + ty => panic!( |
| 128 | + "slice on non-bit type {:?} reached codegen", |
| 129 | + ty, |
| 130 | + ), |
| 131 | + }; |
| 132 | + let (hi, lo) = Self::slice_bounds(begin, end); |
| 133 | + if Self::slice_is_contiguous(hi, lo, field_width) { |
| 134 | + ts.extend(self.generate_slice(begin, end, field_width)); |
| 135 | + } else { |
| 136 | + // Non-contiguous after byte reversal; |
| 137 | + // replace the lvalue suffix with arithmetic. |
| 138 | + return Self::generate_slice_read_arith(&ts, hi, lo); |
| 139 | + } |
| 140 | + } else { |
| 141 | + ts.extend(self.generate_expression(xpr.as_ref())); |
| 142 | + } |
115 | 143 | ts |
116 | 144 | } |
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 | | - } |
| 145 | + ExpressionKind::Slice(_begin, _end) => { |
| 146 | + // The HLIR rejects bare slices outside an Index |
| 147 | + // expression, so this is unreachable for well-typed |
| 148 | + // programs. |
| 149 | + unreachable!("bare Slice reached codegen"); |
130 | 150 | } |
131 | 151 | ExpressionKind::Call(call) => { |
132 | 152 | let lv: Vec<TokenStream> = call |
@@ -158,6 +178,79 @@ impl<'a> ExpressionGenerator<'a> { |
158 | 178 | } |
159 | 179 | } |
160 | 180 |
|
| 181 | + /// Extract compile-time hi and lo from slice bound expressions. |
| 182 | + pub(crate) fn slice_bounds( |
| 183 | + begin: &Expression, |
| 184 | + end: &Expression, |
| 185 | + ) -> (P4Bit, P4Bit) { |
| 186 | + let hi: P4Bit = match &begin.kind { |
| 187 | + ExpressionKind::IntegerLit(v) => *v as usize, |
| 188 | + _ => panic!("slice ranges can only be integer literals"), |
| 189 | + }; |
| 190 | + let lo: P4Bit = match &end.kind { |
| 191 | + ExpressionKind::IntegerLit(v) => *v as usize, |
| 192 | + _ => panic!("slice ranges can only be integer literals"), |
| 193 | + }; |
| 194 | + (hi, lo) |
| 195 | + } |
| 196 | + |
| 197 | + /// Whether `[hi:lo]` on a field of `field_width` bits can be |
| 198 | + /// expressed as a contiguous bitvec range after byte reversal. |
| 199 | + pub(crate) fn slice_is_contiguous( |
| 200 | + hi: P4Bit, |
| 201 | + lo: P4Bit, |
| 202 | + field_width: FieldWidth, |
| 203 | + ) -> bool { |
| 204 | + if field_width <= 8 { |
| 205 | + return true; |
| 206 | + } |
| 207 | + reversed_slice_range(hi, lo, field_width).is_some() |
| 208 | + } |
| 209 | + |
| 210 | + pub(crate) fn generate_slice( |
| 211 | + &self, |
| 212 | + begin: &Expression, |
| 213 | + end: &Expression, |
| 214 | + field_width: FieldWidth, |
| 215 | + ) -> TokenStream { |
| 216 | + let (hi, lo) = Self::slice_bounds(begin, end); |
| 217 | + |
| 218 | + if field_width > 8 { |
| 219 | + let (r, l) = reversed_slice_range(hi, lo, field_width).expect( |
| 220 | + "non-contiguous slice reads must be handled \ |
| 221 | + by the caller via generate_slice_read_arith", |
| 222 | + ); |
| 223 | + quote! { [#r..#l] } |
| 224 | + } else { |
| 225 | + // Fields <= 8 bits are not byte-reversed by header.rs, |
| 226 | + // so the naive P4-to-bitvec mapping is correct. |
| 227 | + let l = hi + 1; |
| 228 | + let r = lo; |
| 229 | + quote! { [#r..#l] } |
| 230 | + } |
| 231 | + } |
| 232 | + |
| 233 | + /// Emit an arithmetic slice read for non-contiguous slices. |
| 234 | + /// Loads the field as an integer, shifts and masks to extract |
| 235 | + /// the requested bits, then packs into a new bitvec. |
| 236 | + pub(crate) fn generate_slice_read_arith( |
| 237 | + lhs: &TokenStream, |
| 238 | + hi: P4Bit, |
| 239 | + lo: P4Bit, |
| 240 | + ) -> TokenStream { |
| 241 | + let slice_width = hi - lo + 1; |
| 242 | + let mask_val = (1u128 << slice_width) - 1; |
| 243 | + quote! { |
| 244 | + { |
| 245 | + let __v: u128 = #lhs.load_le(); |
| 246 | + let __extracted = (__v >> #lo) & #mask_val; |
| 247 | + let mut __out = bitvec![u8, Msb0; 0; #slice_width]; |
| 248 | + __out.store_le(__extracted); |
| 249 | + __out |
| 250 | + } |
| 251 | + } |
| 252 | + } |
| 253 | + |
161 | 254 | pub(crate) fn generate_bit_literal( |
162 | 255 | &self, |
163 | 256 | width: u16, |
@@ -223,3 +316,160 @@ impl<'a> ExpressionGenerator<'a> { |
223 | 316 | } |
224 | 317 | } |
225 | 318 | } |
| 319 | + |
| 320 | +/// P4 bit position (MSB-first index within a field). |
| 321 | +type P4Bit = usize; |
| 322 | + |
| 323 | +/// Width of a P4 header field in bits. |
| 324 | +type FieldWidth = usize; |
| 325 | + |
| 326 | +/// Half-open bitvec range `(start, end)` into the storage representation. |
| 327 | +type BitvecRange = (usize, usize); |
| 328 | + |
| 329 | +/// Map a P4 slice `[hi:lo]` to a bitvec range in byte-reversed storage. |
| 330 | +/// |
| 331 | +/// header.rs reverses byte order for fields wider than 8 bits. Bit |
| 332 | +/// positions within each byte are preserved (Msb0). The mapping from |
| 333 | +/// P4 bit positions to storage indices: |
| 334 | +/// |
| 335 | +/// ```text |
| 336 | +/// wire_idx = W - 1 - b |
| 337 | +/// wire_byte = wire_idx / 8 |
| 338 | +/// bit_in_byte = wire_idx % 8 |
| 339 | +/// storage_byte = W/8 - 1 - wire_byte |
| 340 | +/// bitvec_idx = storage_byte * 8 + bit_in_byte |
| 341 | +/// ``` |
| 342 | +/// |
| 343 | +/// # Returns |
| 344 | +/// |
| 345 | +/// `Some(range)` when the slice maps to a contiguous bitvec range |
| 346 | +/// (single-byte slices or byte-aligned multi-byte slices), `None` |
| 347 | +/// for non-byte-aligned multi-byte slices where byte reversal makes |
| 348 | +/// the bits non-contiguous. |
| 349 | +pub(crate) fn reversed_slice_range( |
| 350 | + hi: P4Bit, |
| 351 | + lo: P4Bit, |
| 352 | + field_width: FieldWidth, |
| 353 | +) -> Option<BitvecRange> { |
| 354 | + // Wire byte indices for the slice endpoints. P4 bit W-1 is in wire |
| 355 | + // byte 0 (MSB-first), so higher bit numbers map to lower byte indices. |
| 356 | + let wire_byte_hi = (field_width - 1 - hi) / 8; |
| 357 | + let wire_byte_lo = (field_width - 1 - lo) / 8; |
| 358 | + |
| 359 | + if wire_byte_hi == wire_byte_lo { |
| 360 | + // Single-byte slice: map each endpoint individually. |
| 361 | + let map_bit = |bit_pos: usize| -> usize { |
| 362 | + let wire_idx = field_width - 1 - bit_pos; |
| 363 | + let wire_byte = wire_idx / 8; |
| 364 | + let bit_in_byte = wire_idx % 8; |
| 365 | + let storage_byte = field_width / 8 - 1 - wire_byte; |
| 366 | + storage_byte * 8 + bit_in_byte |
| 367 | + }; |
| 368 | + |
| 369 | + let mapped_hi = map_bit(hi); |
| 370 | + let mapped_lo = map_bit(lo); |
| 371 | + Some((mapped_hi.min(mapped_lo), mapped_hi.max(mapped_lo) + 1)) |
| 372 | + } else if (hi + 1).is_multiple_of(8) && lo.is_multiple_of(8) { |
| 373 | + // Multi-byte byte-aligned slice: reversed bytes form a |
| 374 | + // contiguous block. |
| 375 | + let storage_byte_start = field_width / 8 - 1 - wire_byte_lo; |
| 376 | + let storage_byte_end = field_width / 8 - 1 - wire_byte_hi; |
| 377 | + Some((storage_byte_start * 8, (storage_byte_end + 1) * 8)) |
| 378 | + } else { |
| 379 | + // Non-byte-aligned multi-byte slice: byte reversal makes the |
| 380 | + // bits non-contiguous, so there is no single bitvec range. |
| 381 | + None |
| 382 | + } |
| 383 | +} |
| 384 | + |
| 385 | +#[cfg(test)] |
| 386 | +mod test { |
| 387 | + use super::*; |
| 388 | + |
| 389 | + // Verify the reversed slice range mapping against the byte reversal |
| 390 | + // in header.rs. For each case we check that the bitvec range lands |
| 391 | + // on the correct bits in the reversed storage layout. |
| 392 | + |
| 393 | + // Sub-byte slices within a single wire byte. |
| 394 | + |
| 395 | + #[test] |
| 396 | + fn slice_32bit_top_nibble() { |
| 397 | + // P4 [31:28] on 32-bit: top nibble of wire byte 0. |
| 398 | + // Storage: wire byte 0 -> storage byte 3. |
| 399 | + // High nibble of storage byte 3 = bitvec [24..28]. |
| 400 | + assert_eq!(reversed_slice_range(31, 28, 32), Some((24, 28))); |
| 401 | + } |
| 402 | + |
| 403 | + #[test] |
| 404 | + fn slice_32bit_bottom_nibble() { |
| 405 | + // P4 [3:0] on 32-bit: bottom nibble of wire byte 3. |
| 406 | + // Storage: wire byte 3 -> storage byte 0. |
| 407 | + // Low nibble (Msb0) of storage byte 0 = bitvec [4..8]. |
| 408 | + assert_eq!(reversed_slice_range(3, 0, 32), Some((4, 8))); |
| 409 | + } |
| 410 | + |
| 411 | + #[test] |
| 412 | + fn slice_16bit_top_nibble() { |
| 413 | + // P4 [15:12] on 16-bit: top nibble of wire byte 0. |
| 414 | + // Storage: wire byte 0 -> storage byte 1. |
| 415 | + // High nibble of storage byte 1 = bitvec [8..12]. |
| 416 | + assert_eq!(reversed_slice_range(15, 12, 16), Some((8, 12))); |
| 417 | + } |
| 418 | + |
| 419 | + // Full-byte slices (single byte). |
| 420 | + |
| 421 | + #[test] |
| 422 | + fn slice_128bit_top_byte() { |
| 423 | + // P4 [127:120] on 128-bit: wire byte 0 -> storage byte 15. |
| 424 | + // bitvec [120..128]. |
| 425 | + assert_eq!(reversed_slice_range(127, 120, 128), Some((120, 128))); |
| 426 | + } |
| 427 | + |
| 428 | + #[test] |
| 429 | + fn slice_16bit_low_byte() { |
| 430 | + // P4 [7:0] on 16-bit: wire byte 1 -> storage byte 0. |
| 431 | + // bitvec [0..8]. |
| 432 | + assert_eq!(reversed_slice_range(7, 0, 16), Some((0, 8))); |
| 433 | + } |
| 434 | + |
| 435 | + #[test] |
| 436 | + fn slice_32bit_middle_byte() { |
| 437 | + // P4 [23:16] on 32-bit: wire byte 1 -> storage byte 2. |
| 438 | + // bitvec [16..24]. |
| 439 | + assert_eq!(reversed_slice_range(23, 16, 32), Some((16, 24))); |
| 440 | + } |
| 441 | + |
| 442 | + // Multi-byte byte-aligned slices. |
| 443 | + |
| 444 | + #[test] |
| 445 | + fn slice_128bit_top_two_bytes() { |
| 446 | + // P4 [127:112] on 128-bit: wire bytes 0-1 -> storage bytes 14-15. |
| 447 | + // bitvec [112..128]. |
| 448 | + assert_eq!(reversed_slice_range(127, 112, 128), Some((112, 128))); |
| 449 | + } |
| 450 | + |
| 451 | + #[test] |
| 452 | + fn slice_32bit_top_three_bytes() { |
| 453 | + // P4 [31:8] on 32-bit: wire bytes 0-2 -> storage bytes 1-3. |
| 454 | + // bitvec [8..32]. |
| 455 | + assert_eq!(reversed_slice_range(31, 8, 32), Some((8, 32))); |
| 456 | + } |
| 457 | + |
| 458 | + #[test] |
| 459 | + fn slice_32bit_bottom_two_bytes() { |
| 460 | + // P4 [15:0] on 32-bit: wire bytes 2-3 -> storage bytes 0-1. |
| 461 | + // bitvec [0..16]. |
| 462 | + assert_eq!(reversed_slice_range(15, 0, 32), Some((0, 16))); |
| 463 | + } |
| 464 | + |
| 465 | + #[test] |
| 466 | + fn slice_48bit_upper_24() { |
| 467 | + assert_eq!(reversed_slice_range(47, 24, 48), Some((24, 48))); |
| 468 | + } |
| 469 | + |
| 470 | + #[test] |
| 471 | + fn slice_non_contiguous_returns_none() { |
| 472 | + assert_eq!(reversed_slice_range(11, 4, 32), None); |
| 473 | + assert_eq!(reversed_slice_range(22, 0, 32), None); |
| 474 | + } |
| 475 | +} |
0 commit comments