diff --git a/.github/buildomat/jobs/build-and-test.sh b/.github/buildomat/jobs/build-and-test.sh index d0331ea4..e17398f0 100755 --- a/.github/buildomat/jobs/build-and-test.sh +++ b/.github/buildomat/jobs/build-and-test.sh @@ -2,7 +2,7 @@ #: #: name = "build-and-test" #: variety = "basic" -#: target = "helios-2.0" +#: target = "helios-3.0" #: rust_toolchain = "stable" #: output_rules = [ #: "/work/debug/*", diff --git a/codegen/rust/src/pipeline.rs b/codegen/rust/src/pipeline.rs index e05cb3d6..4bca914e 100644 --- a/codegen/rust/src/pipeline.rs +++ b/codegen/rust/src/pipeline.rs @@ -119,6 +119,8 @@ impl<'a> PipelineGenerator<'a> { self.add_table_entry_method(ingress, egress); let remove_table_entry_method = self.remove_table_entry_method(ingress, egress); + let clear_table_entries_method = + self.clear_table_entries_method(ingress, egress); let get_table_entries_method = self.get_table_entries_method(ingress, egress); let get_table_ids_method = self.get_table_ids_method(ingress, egress); @@ -156,6 +158,7 @@ impl<'a> PipelineGenerator<'a> { #pipeline_impl_process_packet #add_table_entry_method #remove_table_entry_method + #clear_table_entries_method #get_table_entries_method #get_table_ids_method } @@ -650,6 +653,45 @@ impl<'a> PipelineGenerator<'a> { } } + fn clear_table_entries_method( + &mut self, + ingress: &Control, + egress: &Control, + ) -> TokenStream { + let mut body = TokenStream::new(); + + for control in &[ingress, egress] { + let tables = control.tables(self.ast); + for (cs, table) in tables.iter() { + let qtn = qualified_table_name(Some(control), cs, table); + let qtfn = + qualified_table_function_name(Some(control), cs, table); + let call = format_ident!("clear_{qtfn}_entries"); + body.extend(quote! { + #qtn => { + self.#call(); + true + } + }); + } + } + + body.extend(quote! { + x => { + println!("clear table entries: unknown table id {x}, ignoring"); + false + } + }); + + quote! { + fn clear_table_entries(&mut self, table_id: &str) -> bool { + match table_id { + #body + } + } + } + } + fn get_table_ids_method( &mut self, ingress: &Control, @@ -736,6 +778,7 @@ impl<'a> PipelineGenerator<'a> { table_control, &qtfn, )); + tokens.extend(self.clear_table_entries_function(&qtfn)); tokens.extend(self.get_table_entries_function( table, table_control, @@ -1086,6 +1129,17 @@ impl<'a> PipelineGenerator<'a> { } } + fn clear_table_entries_function(&mut self, qtfn: &str) -> TokenStream { + let name = format_ident!("clear_{qtfn}_entries"); + let tname = format_ident!("{qtfn}"); + + quote! { + pub fn #name(&mut self) { + self.#tname.entries.clear(); + } + } + } + fn get_table_entries_function( &mut self, _table: &Table, diff --git a/lang/p4rs/src/lib.rs b/lang/p4rs/src/lib.rs index a0387314..05ff9c94 100644 --- a/lang/p4rs/src/lib.rs +++ b/lang/p4rs/src/lib.rs @@ -177,6 +177,16 @@ pub trait Pipeline: Send { /// Remove an entry from a table identified by table_id. fn remove_table_entry(&mut self, table_id: &str, keyset_data: &[u8]); + /// Remove all entries from a table identified by table_id. + /// + /// Returns `true` if the table was cleared, or `false` if `table_id` maps + /// to no table in this pipeline. This return type distinguishes an unknown + /// table from an already-empty one, like [`Pipeline::get_table_entries`] + /// does with `None`. + // NOTE: This maps to dendrite's SoftNPU `TableOps::clear` implementation, + // which is the analogue of `bf_rt_table_clear` on Tofino, for example. + fn clear_table_entries(&mut self, table_id: &str) -> bool; + /// Get all the entries in a table. fn get_table_entries(&self, table_id: &str) -> Option>; diff --git a/test/src/lib.rs b/test/src/lib.rs index 3e8a9996..febf8169 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -45,6 +45,8 @@ mod slice_assign_empty; #[cfg(test)] mod slice_read; #[cfg(test)] +mod table_clear; +#[cfg(test)] mod table_in_egress_and_ingress; #[cfg(test)] mod vlan; diff --git a/test/src/p4/table_clear.p4 b/test/src/p4/table_clear.p4 new file mode 100644 index 00000000..5bc38b76 --- /dev/null +++ b/test/src/p4/table_clear.p4 @@ -0,0 +1,61 @@ +#include +#include +#include + +SoftNPU( + parse(), + ingress(), + egress() +) main; + +struct headers_t { + ethernet_h eth; +} + +parser parse( + packet_in pkt, + out headers_t headers, + inout ingress_metadata_t ingress, +){ + state start { + transition accept; + } +} + +control bakery( + inout headers_t hdr, + inout ingress_metadata_t ingress, + inout egress_metadata_t egress, +) { + + action drop() { } + action forward(bit<16> port) { egress.port = port; } + table muffins { + key = { ingress.port: exact; } + actions = { drop; forward; } + default_action = drop; + } + +} + +control ingress( + inout headers_t hdr, + inout ingress_metadata_t ingress, + inout egress_metadata_t egress, +) { + bakery() bakery; + apply { + bakery.apply(hdr, ingress, egress); + } +} + +control egress( + inout headers_t hdr, + inout ingress_metadata_t ingress, + inout egress_metadata_t egress, +) { + bakery() bakery; + apply { + bakery.apply(hdr, ingress, egress); + } +} diff --git a/test/src/table_clear.rs b/test/src/table_clear.rs new file mode 100644 index 00000000..ad5d27c2 --- /dev/null +++ b/test/src/table_clear.rs @@ -0,0 +1,54 @@ +p4_macro::use_p4!( + p4 = "test/src/p4/table_clear.p4", + pipeline_name = "table_clear", +); + +const INGRESS_TBL: &str = "ingress.bakery.muffins"; +const EGRESS_TBL: &str = "egress.bakery.muffins"; + +fn entry_count(pipeline: &main_pipeline, table: &str) -> usize { + pipeline.get_table_entries(table).unwrap().len() +} + +fn add_forward(pipeline: &mut main_pipeline, table: &str, port: u16) { + pipeline.add_table_entry( + table, + "forward", + &port.to_le_bytes(), + &1u16.to_le_bytes(), + 0, + ); +} + +#[test] +fn clear_table_entries() { + let mut pipeline = main_pipeline::new(4); + + let ids = pipeline.get_table_ids(); + assert!(ids.contains(&INGRESS_TBL)); + assert!(ids.contains(&EGRESS_TBL)); + + for port in 0..3 { + add_forward(&mut pipeline, INGRESS_TBL, port); + add_forward(&mut pipeline, EGRESS_TBL, port); + } + assert_eq!(entry_count(&pipeline, INGRESS_TBL), 3); + assert_eq!(entry_count(&pipeline, EGRESS_TBL), 3); + + assert!(pipeline.clear_table_entries(INGRESS_TBL)); + assert_eq!(entry_count(&pipeline, INGRESS_TBL), 0); + assert_eq!(entry_count(&pipeline, EGRESS_TBL), 3); + + assert!(pipeline.clear_table_entries(INGRESS_TBL)); + assert_eq!(entry_count(&pipeline, INGRESS_TBL), 0); + + assert!(!pipeline.clear_table_entries("egress.bakery.scones")); + assert_eq!(entry_count(&pipeline, EGRESS_TBL), 3); + + add_forward(&mut pipeline, INGRESS_TBL, 7); + assert_eq!(entry_count(&pipeline, INGRESS_TBL), 1); + + assert!(pipeline.clear_table_entries(EGRESS_TBL)); + assert_eq!(entry_count(&pipeline, EGRESS_TBL), 0); + assert_eq!(entry_count(&pipeline, INGRESS_TBL), 1); +}