From d300d95aa39f1d679960197ec4ea2ab931ab7767 Mon Sep 17 00:00:00 2001 From: verios Date: Mon, 10 Aug 2026 19:08:22 -0700 Subject: [PATCH] [PSYC] Integrate StatefulReferenceChecker and TranslationState PiperOrigin-RevId: 962498082 --- p4_pdpi/BUILD.bazel | 21 +++ p4_pdpi/ir_entry_builder.cc | 231 ++++++++++++++++++++++++ p4_pdpi/ir_entry_builder.h | 123 +++++++++++++ p4_pdpi/ir_entry_builder_test.cc | 290 +++++++++++++++++++++++++++++++ p4_pdpi/references.h | 3 + 5 files changed, 668 insertions(+) create mode 100644 p4_pdpi/ir_entry_builder.cc create mode 100644 p4_pdpi/ir_entry_builder.h create mode 100644 p4_pdpi/ir_entry_builder_test.cc diff --git a/p4_pdpi/BUILD.bazel b/p4_pdpi/BUILD.bazel index 32e8ede..a35bc92 100644 --- a/p4_pdpi/BUILD.bazel +++ b/p4_pdpi/BUILD.bazel @@ -1082,3 +1082,24 @@ cc_test( "@p4runtime//proto/p4/v1:p4runtime_cc_proto", ], ) + +cc_library( + name = "ir_entry_builder", + srcs = ["ir_entry_builder.cc"], + hdrs = ["ir_entry_builder.h"], + deps = [ + ":ir_cc_proto", + "@abseil-cpp//absl/strings", + ], +) + +cc_test( + name = "ir_entry_builder_test", + srcs = ["ir_entry_builder_test.cc"], + deps = [ + ":ir_cc_proto", + ":ir_entry_builder", + "@googletest//:gtest_main", + "@gutil//gutil:proto_matchers", + ], +) diff --git a/p4_pdpi/ir_entry_builder.cc b/p4_pdpi/ir_entry_builder.cc new file mode 100644 index 0000000..e88439e --- /dev/null +++ b/p4_pdpi/ir_entry_builder.cc @@ -0,0 +1,231 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "p4_pdpi/ir_entry_builder.h" + +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "p4_pdpi/ir.pb.h" + +namespace pdpi { + +IrTableEntryBuilder::IrTableEntryBuilder(absl::string_view table_name) { + entry_.set_table_name(std::string(table_name)); +} + +IrTableEntryBuilder& IrTableEntryBuilder::TableName( + absl::string_view table_name) { + entry_.set_table_name(std::string(table_name)); + return *this; +} + +IrTableEntryBuilder& IrTableEntryBuilder::Priority(int32_t priority) { + entry_.set_priority(priority); + return *this; +} + +IrTableEntryBuilder& IrTableEntryBuilder::Exact(absl::string_view field_name, + IrValue value) { + // Append an exact match field with the provided IrValue. + IrMatch* match = entry_.add_matches(); + match->set_name(std::string(field_name)); + *match->mutable_exact() = std::move(value); + return *this; +} + +IrTableEntryBuilder& IrTableEntryBuilder::ExactStr(absl::string_view field_name, + absl::string_view value) { + IrValue ir_value; + ir_value.set_str(std::string(value)); + return Exact(field_name, std::move(ir_value)); +} + +IrTableEntryBuilder& IrTableEntryBuilder::ExactHexStr( + absl::string_view field_name, absl::string_view value) { + IrValue ir_value; + ir_value.set_hex_str(std::string(value)); + return Exact(field_name, std::move(ir_value)); +} + +IrTableEntryBuilder& IrTableEntryBuilder::ExactIpv4( + absl::string_view field_name, absl::string_view value) { + IrValue ir_value; + ir_value.set_ipv4(std::string(value)); + return Exact(field_name, std::move(ir_value)); +} + +IrTableEntryBuilder& IrTableEntryBuilder::ExactIpv6( + absl::string_view field_name, absl::string_view value) { + IrValue ir_value; + ir_value.set_ipv6(std::string(value)); + return Exact(field_name, std::move(ir_value)); +} + +IrTableEntryBuilder& IrTableEntryBuilder::ExactMac(absl::string_view field_name, + absl::string_view value) { + IrValue ir_value; + ir_value.set_mac(std::string(value)); + return Exact(field_name, std::move(ir_value)); +} + +IrTableEntryBuilder& IrTableEntryBuilder::Lpm(absl::string_view field_name, + IrValue value, + int32_t prefix_length) { + // Append an LPM match field with value and prefix length. + IrMatch* match = entry_.add_matches(); + match->set_name(std::string(field_name)); + *match->mutable_lpm()->mutable_value() = std::move(value); + match->mutable_lpm()->set_prefix_length(prefix_length); + return *this; +} + +IrTableEntryBuilder& IrTableEntryBuilder::LpmIpv4(absl::string_view field_name, + absl::string_view value, + int32_t prefix_length) { + IrValue ir_value; + ir_value.set_ipv4(std::string(value)); + return Lpm(field_name, std::move(ir_value), prefix_length); +} + +IrTableEntryBuilder& IrTableEntryBuilder::LpmIpv6(absl::string_view field_name, + absl::string_view value, + int32_t prefix_length) { + IrValue ir_value; + ir_value.set_ipv6(std::string(value)); + return Lpm(field_name, std::move(ir_value), prefix_length); +} + +IrTableEntryBuilder& IrTableEntryBuilder::Ternary(absl::string_view field_name, + IrValue value, IrValue mask) { + // Append a ternary match field with value and bitmask. + IrMatch* match = entry_.add_matches(); + match->set_name(std::string(field_name)); + *match->mutable_ternary()->mutable_value() = std::move(value); + *match->mutable_ternary()->mutable_mask() = std::move(mask); + return *this; +} + +IrTableEntryBuilder& IrTableEntryBuilder::Optional(absl::string_view field_name, + IrValue value) { + // Append an optional match field with value. + IrMatch* match = entry_.add_matches(); + match->set_name(std::string(field_name)); + *match->mutable_optional()->mutable_value() = std::move(value); + return *this; +} + +IrTableEntryBuilder& IrTableEntryBuilder::Action( + absl::string_view action_name) { + entry_.mutable_action()->set_name(std::string(action_name)); + return *this; +} + +IrTableEntryBuilder& IrTableEntryBuilder::AddActionSetMember( + absl::string_view action_name, int32_t weight, + absl::string_view watch_port) { + // Append a new weighted member action to the WCMP action set. + IrActionSetInvocation* member = entry_.mutable_action_set()->add_actions(); + member->mutable_action()->set_name(std::string(action_name)); + member->set_weight(weight); + if (!watch_port.empty()) { + member->set_watch_port(std::string(watch_port)); + } + return *this; +} + +IrTableEntryBuilder& IrTableEntryBuilder::AddActionSetMember( + IrActionInvocation action, int32_t weight, absl::string_view watch_port) { + // Append a pre-constructed member action to the WCMP action set. + IrActionSetInvocation* member = entry_.mutable_action_set()->add_actions(); + *member->mutable_action() = std::move(action); + member->set_weight(weight); + if (!watch_port.empty()) { + member->set_watch_port(std::string(watch_port)); + } + return *this; +} + +IrActionInvocation* IrTableEntryBuilder::MutableCurrentAction() { + // Target the last action set member if present, or the single action. + if (entry_.has_action_set() && !entry_.action_set().actions().empty()) { + return entry_.mutable_action_set() + ->mutable_actions(entry_.action_set().actions_size() - 1) + ->mutable_action(); + } + return entry_.mutable_action(); +} + +IrTableEntryBuilder& IrTableEntryBuilder::Param(absl::string_view param_name, + IrValue value) { + // Append parameter name and value to the currently active action. + IrActionInvocation::IrActionParam* param = + MutableCurrentAction()->add_params(); + param->set_name(std::string(param_name)); + *param->mutable_value() = std::move(value); + return *this; +} + +IrTableEntryBuilder& IrTableEntryBuilder::ParamStr(absl::string_view param_name, + absl::string_view value) { + IrValue ir_value; + ir_value.set_str(std::string(value)); + return Param(param_name, std::move(ir_value)); +} + +IrTableEntryBuilder& IrTableEntryBuilder::ParamHexStr( + absl::string_view param_name, absl::string_view value) { + IrValue ir_value; + ir_value.set_hex_str(std::string(value)); + return Param(param_name, std::move(ir_value)); +} + +IrTableEntryBuilder& IrTableEntryBuilder::ParamIpv4( + absl::string_view param_name, absl::string_view value) { + IrValue ir_value; + ir_value.set_ipv4(std::string(value)); + return Param(param_name, std::move(ir_value)); +} + +IrTableEntryBuilder& IrTableEntryBuilder::ParamIpv6( + absl::string_view param_name, absl::string_view value) { + IrValue ir_value; + ir_value.set_ipv6(std::string(value)); + return Param(param_name, std::move(ir_value)); +} + +IrTableEntryBuilder& IrTableEntryBuilder::ParamMac(absl::string_view param_name, + absl::string_view value) { + IrValue ir_value; + ir_value.set_mac(std::string(value)); + return Param(param_name, std::move(ir_value)); +} + +IrEntity IrTableEntryBuilder::BuildEntity() const& { + // Wrap a copy of the constructed IrTableEntry into an IrEntity. + IrEntity entity; + *entity.mutable_table_entry() = entry_; + return entity; +} + +IrEntity IrTableEntryBuilder::BuildEntity() && { + // Move the constructed IrTableEntry into an IrEntity. + IrEntity entity; + *entity.mutable_table_entry() = std::move(entry_); + return entity; +} + +} // namespace pdpi diff --git a/p4_pdpi/ir_entry_builder.h b/p4_pdpi/ir_entry_builder.h new file mode 100644 index 0000000..87033d8 --- /dev/null +++ b/p4_pdpi/ir_entry_builder.h @@ -0,0 +1,123 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef P4_INFRA_P4_PDPI_IR_ENTRY_BUILDER_H_ +#define P4_INFRA_P4_PDPI_IR_ENTRY_BUILDER_H_ + +#include +#include + +#include "absl/strings/string_view.h" +#include "p4_pdpi/ir.pb.h" + +namespace pdpi { + +// Fluent builder for constructing pdpi::IrTableEntry and pdpi::IrEntity +// objects with concise match, action, and action-set specifications. +class IrTableEntryBuilder { + public: + // Constructs an empty table entry builder. + IrTableEntryBuilder() = default; + // Constructs a builder targeting the specified P4 table name. + explicit IrTableEntryBuilder(absl::string_view table_name); + + // Sets the table name. + IrTableEntryBuilder& TableName(absl::string_view table_name); + + // Sets the entry priority (required for ternary/optional tables; 0 for + // exact/LPM). + IrTableEntryBuilder& Priority(int32_t priority); + + // Appends an exact match with the specified IrValue format. + IrTableEntryBuilder& Exact(absl::string_view field_name, IrValue value); + IrTableEntryBuilder& ExactStr(absl::string_view field_name, + absl::string_view value); + IrTableEntryBuilder& ExactHexStr(absl::string_view field_name, + absl::string_view value); + IrTableEntryBuilder& ExactIpv4(absl::string_view field_name, + absl::string_view value); + IrTableEntryBuilder& ExactIpv6(absl::string_view field_name, + absl::string_view value); + IrTableEntryBuilder& ExactMac(absl::string_view field_name, + absl::string_view value); + + // Appends an LPM match with the specified IrValue format and prefix length. + IrTableEntryBuilder& Lpm(absl::string_view field_name, IrValue value, + int32_t prefix_length); + IrTableEntryBuilder& LpmIpv4(absl::string_view field_name, + absl::string_view value, int32_t prefix_length); + IrTableEntryBuilder& LpmIpv6(absl::string_view field_name, + absl::string_view value, int32_t prefix_length); + + // Appends a ternary match with value and mask. + IrTableEntryBuilder& Ternary(absl::string_view field_name, IrValue value, + IrValue mask); + + // Appends an optional match. + IrTableEntryBuilder& Optional(absl::string_view field_name, IrValue value); + + // Sets the single action name for this entry. Subsequent Param* calls will + // attach parameters to this action. + IrTableEntryBuilder& Action(absl::string_view action_name); + + // Appends an action member to the entry's WCMP action set with the given + // weight and optional watch port. Subsequent Param* calls will attach + // parameters to this newly added member action. + IrTableEntryBuilder& AddActionSetMember(absl::string_view action_name, + int32_t weight, + absl::string_view watch_port = ""); + IrTableEntryBuilder& AddActionSetMember(IrActionInvocation action, + int32_t weight, + absl::string_view watch_port = ""); + + // Appends a parameter to the currently active action (either the single + // entry action set by Action(), or the most recently added action set member + // from AddActionSetMember()). + IrTableEntryBuilder& Param(absl::string_view param_name, IrValue value); + IrTableEntryBuilder& ParamStr(absl::string_view param_name, + absl::string_view value); + IrTableEntryBuilder& ParamHexStr(absl::string_view param_name, + absl::string_view value); + IrTableEntryBuilder& ParamIpv4(absl::string_view param_name, + absl::string_view value); + IrTableEntryBuilder& ParamIpv6(absl::string_view param_name, + absl::string_view value); + IrTableEntryBuilder& ParamMac(absl::string_view param_name, + absl::string_view value); + + // Builds and returns the constructed IrTableEntry. + IrTableEntry Build() const& { return entry_; } + IrTableEntry Build() && { return std::move(entry_); } + IrTableEntry BuildTableEntry() const& { return entry_; } + IrTableEntry BuildTableEntry() && { return std::move(entry_); } + + // Builds and returns an IrEntity wrapping the constructed IrTableEntry. + IrEntity BuildEntity() const&; + IrEntity BuildEntity() &&; + + private: + // === Internal Helpers ====================================================== + + // Returns a mutable pointer to the active action invocation (either the + // entry's single action, or the last member of action_set if present). + IrActionInvocation* MutableCurrentAction(); + + // === Mutable State ========================================================= + + IrTableEntry entry_; +}; + +} // namespace pdpi + +#endif // P4_INFRA_P4_PDPI_IR_ENTRY_BUILDER_H_ diff --git a/p4_pdpi/ir_entry_builder_test.cc b/p4_pdpi/ir_entry_builder_test.cc new file mode 100644 index 0000000..3a7f6e5 --- /dev/null +++ b/p4_pdpi/ir_entry_builder_test.cc @@ -0,0 +1,290 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "p4_pdpi/ir_entry_builder.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "gutil/proto_matchers.h" +#include "p4_pdpi/ir.pb.h" + +namespace pdpi { +namespace { + +using ::gutil::EqualsProto; + +TEST(IrTableEntryBuilderTest, BuildsExactMatchAndSingleActionTableEntry) { + IrTableEntry entry = IrTableEntryBuilder("router_interface_table") + .ExactStr("router_interface_id", "rif-1") + .Action("set_port_and_src_mac") + .ParamStr("port", "Ethernet0") + .ParamMac("src_mac", "00:11:22:33:44:55") + .Build(); + + EXPECT_THAT(entry, EqualsProto(R"pb( + table_name: "router_interface_table" + matches { + name: "router_interface_id" + exact { str: "rif-1" } + } + action { + name: "set_port_and_src_mac" + params { + name: "port" + value { str: "Ethernet0" } + } + params { + name: "src_mac" + value { mac: "00:11:22:33:44:55" } + } + } + )pb")); +} + +TEST(IrTableEntryBuilderTest, BuildsLpmIpv4AndIpv6RouteEntryAsEntity) { + IrEntity ipv4_entity = IrTableEntryBuilder() + .TableName("ipv4_table") + .ExactStr("vrf_id", "vrf-default") + .LpmIpv4("ipv4_dst", "10.0.0.0", 24) + .Action("set_nexthop_id") + .ParamStr("nexthop_id", "nh-1") + .BuildEntity(); + + EXPECT_THAT(ipv4_entity, EqualsProto(R"pb( + table_entry { + table_name: "ipv4_table" + matches { + name: "vrf_id" + exact { str: "vrf-default" } + } + matches { + name: "ipv4_dst" + lpm { + value { ipv4: "10.0.0.0" } + prefix_length: 24 + } + } + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nh-1" } + } + } + } + )pb")); + + IrEntity ipv6_entity = IrTableEntryBuilder("ipv6_table") + .ExactStr("vrf_id", "vrf-blue") + .LpmIpv6("ipv6_dst", "2001:db8::", 64) + .Action("set_wcmp_group_id") + .ParamStr("wcmp_group_id", "grp-1") + .BuildEntity(); + + EXPECT_THAT(ipv6_entity, EqualsProto(R"pb( + table_entry { + table_name: "ipv6_table" + matches { + name: "vrf_id" + exact { str: "vrf-blue" } + } + matches { + name: "ipv6_dst" + lpm { + value { ipv6: "2001:db8::" } + prefix_length: 64 + } + } + action { + name: "set_wcmp_group_id" + params { + name: "wcmp_group_id" + value { str: "grp-1" } + } + } + } + )pb")); +} + +TEST(IrTableEntryBuilderTest, BuildsWcmpActionSetEntry) { + IrTableEntry wcmp_entry = + IrTableEntryBuilder("wcmp_group_table") + .ExactStr("wcmp_group_id", "grp-1") + .AddActionSetMember("set_nexthop_id", /*weight=*/2, + /*watch_port=*/"1") + .ParamStr("nexthop_id", "nh-1") + .AddActionSetMember("set_nexthop_id", /*weight=*/3) + .ParamStr("nexthop_id", "nh-2") + .BuildTableEntry(); + + EXPECT_THAT(wcmp_entry, EqualsProto(R"pb( + table_name: "wcmp_group_table" + matches { + name: "wcmp_group_id" + exact { str: "grp-1" } + } + action_set { + actions { + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nh-1" } + } + } + weight: 2 + watch_port: "1" + } + actions { + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nh-2" } + } + } + weight: 3 + } + } + )pb")); +} + +TEST(IrTableEntryBuilderTest, BuildsAllValueFormatsAndTernaryOptionalMatches) { + IrValue ternary_val; + ternary_val.set_hex_str("0x0800"); + IrValue ternary_mask; + ternary_mask.set_hex_str("0xffff"); + IrValue optional_val; + optional_val.set_str("opt-1"); + + IrTableEntry entry = IrTableEntryBuilder("acl_ingress_table") + .Priority(100) + .ExactHexStr("ether_type", "0x0800") + .ExactIpv4("src_ip", "192.168.1.1") + .ExactIpv6("dst_ip", "fe80::1") + .ExactMac("dst_mac", "aa:bb:cc:dd:ee:ff") + .Ternary("l4_port", ternary_val, ternary_mask) + .Optional("in_port", optional_val) + .Action("acl_forward") + .ParamHexStr("meter_id", "0x01") + .ParamIpv4("redirect_ipv4", "10.1.1.1") + .ParamIpv6("redirect_ipv6", "2001:db8::1") + .Build(); + + EXPECT_THAT(entry, EqualsProto(R"pb( + table_name: "acl_ingress_table" + priority: 100 + matches { + name: "ether_type" + exact { hex_str: "0x0800" } + } + matches { + name: "src_ip" + exact { ipv4: "192.168.1.1" } + } + matches { + name: "dst_ip" + exact { ipv6: "fe80::1" } + } + matches { + name: "dst_mac" + exact { mac: "aa:bb:cc:dd:ee:ff" } + } + matches { + name: "l4_port" + ternary { + value { hex_str: "0x0800" } + mask { hex_str: "0xffff" } + } + } + matches { + name: "in_port" + optional { value { str: "opt-1" } } + } + action { + name: "acl_forward" + params { + name: "meter_id" + value { hex_str: "0x01" } + } + params { + name: "redirect_ipv4" + value { ipv4: "10.1.1.1" } + } + params { + name: "redirect_ipv6" + value { ipv6: "2001:db8::1" } + } + } + )pb")); +} + +TEST(IrTableEntryBuilderTest, LvalueBuildAndBuildEntityPreserveBuilderState) { + IrTableEntryBuilder builder = IrTableEntryBuilder("vrf_table") + .ExactStr("vrf_id", "vrf-1") + .Action("no_action"); + + // Lvalue Build, BuildTableEntry, and BuildEntity preserve builder state. + IrTableEntry copied_entry = builder.Build(); + IrEntity copied_entity = builder.BuildEntity(); + EXPECT_EQ(copied_entry.table_name(), "vrf_table"); + EXPECT_EQ(copied_entity.table_entry().table_name(), "vrf_table"); + + IrTableEntry copied_table_entry = builder.BuildTableEntry(); + EXPECT_EQ(copied_table_entry.table_name(), "vrf_table"); + + // Moving rvalue extraction still succeeds. + IrTableEntry moved_entry = std::move(builder).Build(); + EXPECT_EQ(moved_entry.table_name(), "vrf_table"); +} + +TEST(IrTableEntryBuilderTest, SupportsActionSetMemberWithActionInvocation) { + IrActionInvocation action; + action.set_name("set_nexthop_id"); + IrActionInvocation::IrActionParam* param = action.add_params(); + param->set_name("nexthop_id"); + param->mutable_value()->set_str("nh-10"); + + IrTableEntry entry = + IrTableEntryBuilder("wcmp_group_table") + .ExactStr("wcmp_group_id", "grp-10") + .AddActionSetMember(action, /*weight=*/5, /*watch_port=*/"Ethernet4") + .Build(); + + EXPECT_THAT(entry, EqualsProto(R"pb( + table_name: "wcmp_group_table" + matches { + name: "wcmp_group_id" + exact { str: "grp-10" } + } + action_set { + actions { + action { + name: "set_nexthop_id" + params { + name: "nexthop_id" + value { str: "nh-10" } + } + } + weight: 5 + watch_port: "Ethernet4" + } + } + )pb")); +} + +} // namespace +} // namespace pdpi diff --git a/p4_pdpi/references.h b/p4_pdpi/references.h index e60440e..ac53166 100644 --- a/p4_pdpi/references.h +++ b/p4_pdpi/references.h @@ -216,6 +216,9 @@ class StatefulReferenceChecker { // FOR TESTING ONLY. absl::Status CheckInvariantsForTesting() const; + // Returns the IrP4Info used by this reference checker. + const IrP4Info& ir_p4_info() const { return info_; } + private: IrP4Info info_;