Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions p4_pdpi/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,48 @@ cc_library(
deps = ["@abseil-cpp//absl/strings:str_format"],
)

proto_library(
name = "action_profile_mode_proto",
srcs = ["action_profile_mode.proto"],
deps = ["@p4runtime//proto/p4/config/v1:p4info_proto"],
)

cc_proto_library(
name = "action_profile_mode_cc_proto",
deps = [":action_profile_mode_proto"],
)

cc_library(
name = "action_profile_modes",
srcs = [
"action_profile_modes.cc",
],
hdrs = [
"action_profile_modes.h",
],
deps = [
":action_profile_mode_cc_proto",
":annotation_parser",
"@abseil-cpp//absl/status:statusor",
"@abseil-cpp//absl/strings",
"@p4runtime//proto/p4/config/v1:p4info_cc_proto",
"@p4runtime//proto/p4/config/v1:p4types_cc_proto",
"@protobuf",
],
)

cc_test(
name = "action_profile_modes_test",
srcs = ["action_profile_modes_test.cc"],
deps = [
":action_profile_mode_cc_proto",
":action_profile_modes",
"@googletest//:gtest_main",
"@p4runtime//proto/p4/config/v1:p4info_cc_proto",
"@p4runtime//proto/p4/config/v1:p4types_cc_proto",
],
)

cc_library(
name = "annotation_parser",
srcs = [
Expand Down
52 changes: 52 additions & 0 deletions p4_pdpi/action_profile_mode.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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.

syntax = "proto3";

package pdpi;

import "p4/config/v1/p4info.proto";

// Supported or required ActionProfile group programming mode.
message ActionProfileMode {
enum ActionSelectionMode {
UNDEFINED_ACTION_SELECTION_MODE = 0;
HASH = 1;
RANDOM = 2;
}
ActionSelectionMode action_selection_mode = 1;

// Size semantics specified by P4Runtime ActionProfile.
oneof size_semantics {
p4.config.v1.ActionProfile.SumOfWeights sum_of_weights = 2;
p4.config.v1.ActionProfile.SumOfMembers sum_of_members = 3;
}

// Defines how groups of particular modes use resources.
message ResourceUsageMultipliers {
// Multiplies the members used by a group of this type by this number.
// Affects the guarantee given by ActionProfile `size`.
// copybara:strip_begin
// On Broadcom switches that support it, Native WCMP member resources are
// multiplied by 4, while Legacy ECMP resources are multiplied by 1.
// copybara:strip_end
// If the field is not present, it is treated as 1.
optional int32 member_usage_multiplier = 1;
}
ResourceUsageMultipliers resource_usage_multipliers = 4;
}

message ActionProfileModes {
repeated ActionProfileMode action_profile_modes = 1;
}
230 changes: 230 additions & 0 deletions p4_pdpi/action_profile_modes.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
// 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/action_profile_modes.h"

#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>

#include "absl/status/statusor.h"
#include "absl/strings/ascii.h"
#include "absl/strings/match.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/string_view.h"
#include "google/protobuf/util/message_differencer.h"
#include "p4/config/v1/p4info.pb.h"
#include "p4/config/v1/p4types.pb.h"
#include "p4_pdpi/action_profile_mode.pb.h"
#include "p4_pdpi/annotation_parser.h"

namespace pdpi {
namespace {

std::string CleanToken(absl::string_view s) {
s = absl::StripAsciiWhitespace(s);
while (s.size() >= 2 && ((s.front() == '"' && s.back() == '"') ||
(s.front() == '{' && s.back() == '}') ||
(s.front() == '\'' && s.back() == '\''))) {
s = s.substr(1, s.size() - 2);
s = absl::StripAsciiWhitespace(s);
}
return std::string(s);
}

void ParseSizeSemantics(absl::string_view raw_val, ActionProfileMode& mode) {
std::string cleaned = CleanToken(raw_val);
if (cleaned.empty()) return;

absl::StatusOr<std::vector<std::string>> parts =
pdpi::annotation::ParseAsArgList(
absl::StrReplaceAll(cleaned, {{":", ","}}));
if (!parts.ok() || parts->empty()) {
parts = std::vector<std::string>{cleaned};
}

for (size_t i = 0; i < parts->size(); ++i) {
std::string part = std::string(absl::StripAsciiWhitespace((*parts)[i]));
size_t eq_pos = part.find('=');
if (eq_pos != std::string::npos) {
std::string key = CleanToken(part.substr(0, eq_pos));
std::string val = CleanToken(part.substr(eq_pos + 1));
if (key == "semantics" || key == "size_semantics") {
std::string upper_val = absl::AsciiStrToUpper(val);
if (upper_val == "SUM_OF_WEIGHTS") {
mode.mutable_sum_of_weights();
} else if (upper_val == "SUM_OF_MEMBERS") {
mode.mutable_sum_of_members();
}
} else if (key == "max_weight" || key == "max_member_weight") {
int64_t w;
if (absl::SimpleAtoi(val, &w)) {
if (!mode.has_sum_of_weights() && !mode.has_sum_of_members()) {
mode.mutable_sum_of_members();
}
if (mode.has_sum_of_members()) {
mode.mutable_sum_of_members()->set_max_member_weight(w);
}
}
} else if (key == "member_multiplier" ||
key == "member_usage_multiplier") {
int64_t mult;
if (absl::SimpleAtoi(val, &mult)) {
mode.mutable_resource_usage_multipliers()
->set_member_usage_multiplier(mult);
}
}
} else {
std::string token = CleanToken(part);
std::string upper_token = absl::AsciiStrToUpper(token);
if (upper_token == "SUM_OF_WEIGHTS") {
mode.mutable_sum_of_weights();
} else if (upper_token == "SUM_OF_MEMBERS") {
mode.mutable_sum_of_members();
} else if (i > 0) {
int64_t val;
if (absl::SimpleAtoi(token, &val)) {
if (mode.has_sum_of_members()) {
if (val > 100) {
mode.mutable_sum_of_members()->set_max_member_weight(val);
} else {
mode.mutable_resource_usage_multipliers()
->set_member_usage_multiplier(val);
}
}
}
}
}
}
}

ActionProfileMode ParseActionProfileModeFromKvList(
const p4::config::v1::KeyValuePairList& kv_list) {
ActionProfileMode mode;
for (const auto& kv : kv_list.kv_pairs()) {
if (kv.key() == "action_selection_mode") {
std::string val =
absl::AsciiStrToUpper(CleanToken(kv.value().string_value()));
if (val == "HASH") {
mode.set_action_selection_mode(ActionProfileMode::HASH);
} else if (val == "RANDOM") {
mode.set_action_selection_mode(ActionProfileMode::RANDOM);
}
} else if (kv.key() == "size_semantics") {
ParseSizeSemantics(kv.value().string_value(), mode);
} else if (kv.key() == "sum_of_weights") {
mode.mutable_sum_of_weights();
} else if (kv.key() == "sum_of_members") {
mode.mutable_sum_of_members();
} else if (kv.key() == "member_multiplier" ||
kv.key() == "member_usage_multiplier") {
if (kv.value().has_int64_value()) {
mode.mutable_resource_usage_multipliers()->set_member_usage_multiplier(
kv.value().int64_value());
}
} else if (kv.key() == "max_member_weight" || kv.key() == "max_weight") {
if (kv.value().has_int64_value()) {
if (!mode.has_sum_of_weights() && !mode.has_sum_of_members()) {
mode.mutable_sum_of_members();
}
if (mode.has_sum_of_members()) {
mode.mutable_sum_of_members()->set_max_member_weight(
kv.value().int64_value());
}
}
}
}
return mode;
}

std::vector<ActionProfileMode> ParseActionProfileModesFromExpressionList(
const p4::config::v1::ExpressionList& expression_list) {
std::vector<ActionProfileMode> modes;
const auto& exprs = expression_list.expressions();

ActionProfileMode mode;
for (size_t i = 0; i < exprs.size(); i += 3) {
mode.Clear();

std::string action_selection_mode =
absl::AsciiStrToUpper(CleanToken(exprs[i].string_value()));
if (action_selection_mode == "HASH") {
mode.set_action_selection_mode(ActionProfileMode::HASH);
} else if (action_selection_mode == "RANDOM") {
mode.set_action_selection_mode(ActionProfileMode::RANDOM);
}

if (i + 1 < exprs.size()) {
ParseSizeSemantics(exprs[i + 1].string_value(), mode);
}

if (i + 2 < exprs.size()) {
int64_t val;
if (absl::SimpleAtoi(CleanToken(exprs[i + 2].string_value()), &val)) {
if (mode.has_sum_of_members()) {
mode.mutable_sum_of_members()->set_max_member_weight(val);
} else if (mode.has_sum_of_weights()) {
mode.mutable_resource_usage_multipliers()
->set_member_usage_multiplier(val);
}
}
}

modes.push_back(mode);
}

return modes;
}

} // namespace

absl::StatusOr<std::vector<ActionProfileMode>>
ParseRequiredModesFromActionProfile(
const p4::config::v1::ActionProfile& action_profile) {
std::vector<ActionProfileMode> modes;

for (const auto& sa : action_profile.preamble().structured_annotations()) {
if (!absl::StartsWith(sa.name(), "required_mode") &&
!absl::StartsWith(sa.name(), "required_modes")) {
continue;
}
if (sa.has_kv_pair_list()) {
modes.push_back(ParseActionProfileModeFromKvList(sa.kv_pair_list()));
} else if (sa.has_expression_list()) {
std::vector<ActionProfileMode> parsed_modes =
ParseActionProfileModesFromExpressionList(sa.expression_list());
modes.insert(modes.end(), parsed_modes.begin(), parsed_modes.end());
}
}

std::vector<ActionProfileMode> deduplicated_modes;
for (const auto& mode : modes) {
bool exists = false;
for (const auto& existing : deduplicated_modes) {
if (google::protobuf::util::MessageDifferencer::Equals(mode, existing)) {
exists = true;
break;
}
}
if (!exists) {
deduplicated_modes.push_back(mode);
}
}

return deduplicated_modes;
}

} // namespace pdpi
35 changes: 35 additions & 0 deletions p4_pdpi/action_profile_modes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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_ACTION_PROFILE_MODES_H_
#define P4_INFRA_P4_PDPI_ACTION_PROFILE_MODES_H_

#include <vector>

#include "absl/status/statusor.h"
#include "p4/config/v1/p4info.pb.h"
#include "p4_pdpi/action_profile_mode.pb.h"

namespace pdpi {

// Parses required ActionProfile modes from `@required_mode(...)`/
// `@required_modes(...)` structured annotations in the ActionProfile's
// preamble.
absl::StatusOr<std::vector<ActionProfileMode>>
ParseRequiredModesFromActionProfile(
const p4::config::v1::ActionProfile& action_profile);

} // namespace pdpi

#endif // P4_INFRA_P4_PDPI_ACTION_PROFILE_MODES_H_
Loading
Loading