Skip to content

Commit 464320b

Browse files
P4 Infra Teamcopybara-github
authored andcommitted
[Modular Group Programming] Parse ActionProfile @group_mode annotations and union preambles in PDPI.
PiperOrigin-RevId: 957220111
1 parent 15a4d2c commit 464320b

6 files changed

Lines changed: 670 additions & 4 deletions

File tree

‎p4_pdpi/BUILD.bazel‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,37 @@ cc_proto_library(
559559
deps = [":action_profile_mode_proto"],
560560
)
561561

562+
cc_library(
563+
name = "action_profile_modes",
564+
srcs = [
565+
"action_profile_modes.cc",
566+
],
567+
hdrs = [
568+
"action_profile_modes.h",
569+
],
570+
deps = [
571+
":action_profile_mode_cc_proto",
572+
":annotation_parser",
573+
"@abseil-cpp//absl/status:statusor",
574+
"@abseil-cpp//absl/strings",
575+
"@p4runtime//proto/p4/config/v1:p4info_cc_proto",
576+
"@p4runtime//proto/p4/config/v1:p4types_cc_proto",
577+
"@protobuf",
578+
],
579+
)
580+
581+
cc_test(
582+
name = "action_profile_modes_test",
583+
srcs = ["action_profile_modes_test.cc"],
584+
deps = [
585+
":action_profile_mode_cc_proto",
586+
":action_profile_modes",
587+
"@googletest//:gtest_main",
588+
"@p4runtime//proto/p4/config/v1:p4info_cc_proto",
589+
"@p4runtime//proto/p4/config/v1:p4types_cc_proto",
590+
],
591+
)
592+
562593
cc_library(
563594
name = "annotation_parser",
564595
srcs = [

‎p4_pdpi/action_profile_modes.cc‎

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "p4_pdpi/action_profile_modes.h"
16+
17+
#include <cstddef>
18+
#include <cstdint>
19+
#include <string>
20+
#include <vector>
21+
22+
#include "absl/status/statusor.h"
23+
#include "absl/strings/ascii.h"
24+
#include "absl/strings/numbers.h"
25+
#include "absl/strings/str_replace.h"
26+
#include "absl/strings/string_view.h"
27+
#include "google/protobuf/util/message_differencer.h"
28+
#include "p4/config/v1/p4info.pb.h"
29+
#include "p4/config/v1/p4types.pb.h"
30+
#include "p4_pdpi/action_profile_mode.pb.h"
31+
#include "p4_pdpi/annotation_parser.h"
32+
33+
namespace pdpi {
34+
namespace {
35+
36+
std::string CleanToken(absl::string_view s) {
37+
s = absl::StripAsciiWhitespace(s);
38+
while (s.size() >= 2 && ((s.front() == '"' && s.back() == '"') ||
39+
(s.front() == '{' && s.back() == '}') ||
40+
(s.front() == '\'' && s.back() == '\''))) {
41+
s = s.substr(1, s.size() - 2);
42+
s = absl::StripAsciiWhitespace(s);
43+
}
44+
return std::string(s);
45+
}
46+
47+
void ParseSizeSemantics(absl::string_view raw_val, ActionProfileMode& mode) {
48+
std::string cleaned = CleanToken(raw_val);
49+
if (cleaned.empty()) return;
50+
51+
absl::StatusOr<std::vector<std::string>> parts =
52+
pdpi::annotation::ParseAsArgList(
53+
absl::StrReplaceAll(cleaned, {{":", ","}}));
54+
if (!parts.ok() || parts->empty()) {
55+
parts = std::vector<std::string>{cleaned};
56+
}
57+
58+
for (size_t i = 0; i < parts->size(); ++i) {
59+
std::string part = std::string(absl::StripAsciiWhitespace((*parts)[i]));
60+
size_t eq_pos = part.find('=');
61+
if (eq_pos != std::string::npos) {
62+
std::string key = CleanToken(part.substr(0, eq_pos));
63+
std::string val = CleanToken(part.substr(eq_pos + 1));
64+
if (key == "semantics" || key == "size_semantics") {
65+
std::string upper_val = absl::AsciiStrToUpper(val);
66+
if (upper_val == "SUM_OF_WEIGHTS") {
67+
mode.mutable_sum_of_weights();
68+
} else if (upper_val == "SUM_OF_MEMBERS") {
69+
mode.mutable_sum_of_members();
70+
}
71+
} else if (key == "max_weight" || key == "max_member_weight") {
72+
int64_t w;
73+
if (absl::SimpleAtoi(val, &w)) {
74+
if (!mode.has_sum_of_weights() && !mode.has_sum_of_members()) {
75+
mode.mutable_sum_of_members();
76+
}
77+
if (mode.has_sum_of_members()) {
78+
mode.mutable_sum_of_members()->set_max_member_weight(w);
79+
}
80+
}
81+
} else if (key == "member_multiplier" ||
82+
key == "member_usage_multiplier") {
83+
int64_t mult;
84+
if (absl::SimpleAtoi(val, &mult)) {
85+
mode.mutable_resource_usage_multipliers()
86+
->set_member_usage_multiplier(mult);
87+
}
88+
}
89+
} else {
90+
std::string token = CleanToken(part);
91+
std::string upper_token = absl::AsciiStrToUpper(token);
92+
if (upper_token == "SUM_OF_WEIGHTS") {
93+
mode.mutable_sum_of_weights();
94+
} else if (upper_token == "SUM_OF_MEMBERS") {
95+
mode.mutable_sum_of_members();
96+
} else if (i > 0) {
97+
int64_t val;
98+
if (absl::SimpleAtoi(token, &val)) {
99+
if (mode.has_sum_of_members()) {
100+
if (val > 100) {
101+
mode.mutable_sum_of_members()->set_max_member_weight(val);
102+
} else {
103+
mode.mutable_resource_usage_multipliers()
104+
->set_member_usage_multiplier(val);
105+
}
106+
}
107+
}
108+
}
109+
}
110+
}
111+
}
112+
113+
ActionProfileMode ParseActionProfileModeFromKvList(
114+
const p4::config::v1::KeyValuePairList& kv_list) {
115+
ActionProfileMode mode;
116+
for (const auto& kv : kv_list.kv_pairs()) {
117+
if (kv.key() == "action_selection_mode") {
118+
std::string val =
119+
absl::AsciiStrToUpper(CleanToken(kv.value().string_value()));
120+
if (val == "HASH") {
121+
mode.set_action_selection_mode(ActionProfileMode::HASH);
122+
} else if (val == "RANDOM") {
123+
mode.set_action_selection_mode(ActionProfileMode::RANDOM);
124+
}
125+
} else if (kv.key() == "size_semantics") {
126+
ParseSizeSemantics(kv.value().string_value(), mode);
127+
} else if (kv.key() == "sum_of_weights") {
128+
mode.mutable_sum_of_weights();
129+
} else if (kv.key() == "sum_of_members") {
130+
mode.mutable_sum_of_members();
131+
} else if (kv.key() == "member_multiplier" ||
132+
kv.key() == "member_usage_multiplier") {
133+
if (kv.value().has_int64_value()) {
134+
mode.mutable_resource_usage_multipliers()->set_member_usage_multiplier(
135+
kv.value().int64_value());
136+
}
137+
} else if (kv.key() == "max_member_weight" || kv.key() == "max_weight") {
138+
if (kv.value().has_int64_value()) {
139+
if (!mode.has_sum_of_weights() && !mode.has_sum_of_members()) {
140+
mode.mutable_sum_of_members();
141+
}
142+
if (mode.has_sum_of_members()) {
143+
mode.mutable_sum_of_members()->set_max_member_weight(
144+
kv.value().int64_value());
145+
}
146+
}
147+
}
148+
}
149+
return mode;
150+
}
151+
152+
std::vector<ActionProfileMode> ParseActionProfileModesFromExpressionList(
153+
const p4::config::v1::ExpressionList& expression_list) {
154+
std::vector<ActionProfileMode> modes;
155+
const auto& exprs = expression_list.expressions();
156+
157+
ActionProfileMode mode;
158+
for (size_t i = 0; i < exprs.size(); i += 3) {
159+
mode.Clear();
160+
161+
std::string action_selection_mode =
162+
absl::AsciiStrToUpper(CleanToken(exprs[i].string_value()));
163+
if (action_selection_mode == "HASH") {
164+
mode.set_action_selection_mode(ActionProfileMode::HASH);
165+
} else if (action_selection_mode == "RANDOM") {
166+
mode.set_action_selection_mode(ActionProfileMode::RANDOM);
167+
}
168+
169+
if (i + 1 < exprs.size()) {
170+
ParseSizeSemantics(exprs[i + 1].string_value(), mode);
171+
}
172+
173+
if (i + 2 < exprs.size()) {
174+
int64_t val;
175+
if (absl::SimpleAtoi(CleanToken(exprs[i + 2].string_value()), &val)) {
176+
if (mode.has_sum_of_members()) {
177+
mode.mutable_sum_of_members()->set_max_member_weight(val);
178+
} else if (mode.has_sum_of_weights()) {
179+
mode.mutable_resource_usage_multipliers()
180+
->set_member_usage_multiplier(val);
181+
}
182+
}
183+
}
184+
185+
modes.push_back(mode);
186+
}
187+
188+
return modes;
189+
}
190+
191+
} // namespace
192+
193+
absl::StatusOr<std::vector<ActionProfileMode>>
194+
ParseRequiredModesFromActionProfile(
195+
const p4::config::v1::ActionProfile& action_profile) {
196+
std::vector<ActionProfileMode> modes;
197+
198+
for (const auto& sa : action_profile.preamble().structured_annotations()) {
199+
if (sa.name() != "group_mode") {
200+
continue;
201+
}
202+
if (sa.has_kv_pair_list()) {
203+
modes.push_back(ParseActionProfileModeFromKvList(sa.kv_pair_list()));
204+
} else if (sa.has_expression_list()) {
205+
std::vector<ActionProfileMode> parsed_modes =
206+
ParseActionProfileModesFromExpressionList(sa.expression_list());
207+
modes.insert(modes.end(), parsed_modes.begin(), parsed_modes.end());
208+
}
209+
}
210+
211+
if (auto bodies = GetAllAnnotationBodies(
212+
"group_mode", action_profile.preamble().annotations());
213+
bodies.ok()) {
214+
p4::config::v1::KeyValuePairList kv_list;
215+
for (const auto& raw_body : *bodies) {
216+
kv_list.Clear();
217+
std::string body = CleanToken(raw_body);
218+
auto parts = annotation::ParseAsArgList(body);
219+
if (!parts.ok()) continue;
220+
for (const auto& part : *parts) {
221+
size_t eq_pos = part.find('=');
222+
if (eq_pos != std::string::npos) {
223+
std::string key = CleanToken(part.substr(0, eq_pos));
224+
std::string val = CleanToken(part.substr(eq_pos + 1));
225+
auto* kv = kv_list.add_kv_pairs();
226+
kv->set_key(key);
227+
int64_t int_val;
228+
if (absl::SimpleAtoi(val, &int_val)) {
229+
kv->mutable_value()->set_int64_value(int_val);
230+
} else {
231+
kv->mutable_value()->set_string_value(val);
232+
}
233+
}
234+
}
235+
modes.push_back(ParseActionProfileModeFromKvList(kv_list));
236+
}
237+
}
238+
239+
std::vector<ActionProfileMode> deduplicated_modes;
240+
for (const auto& mode : modes) {
241+
bool exists = false;
242+
for (const auto& existing : deduplicated_modes) {
243+
if (google::protobuf::util::MessageDifferencer::Equals(mode, existing)) {
244+
exists = true;
245+
break;
246+
}
247+
}
248+
if (!exists) {
249+
deduplicated_modes.push_back(mode);
250+
}
251+
}
252+
253+
return deduplicated_modes;
254+
}
255+
256+
} // namespace pdpi

‎p4_pdpi/action_profile_modes.h‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef P4_INFRA_P4_PDPI_ACTION_PROFILE_MODES_H_
16+
#define P4_INFRA_P4_PDPI_ACTION_PROFILE_MODES_H_
17+
18+
#include <vector>
19+
20+
#include "absl/status/statusor.h"
21+
#include "p4/config/v1/p4info.pb.h"
22+
#include "p4_pdpi/action_profile_mode.pb.h"
23+
24+
namespace pdpi {
25+
26+
// Parses ActionProfile modes from `@group_mode(...)` annotations in the
27+
// ActionProfile's preamble.
28+
absl::StatusOr<std::vector<ActionProfileMode>>
29+
ParseRequiredModesFromActionProfile(
30+
const p4::config::v1::ActionProfile& action_profile);
31+
32+
} // namespace pdpi
33+
34+
#endif // P4_INFRA_P4_PDPI_ACTION_PROFILE_MODES_H_

0 commit comments

Comments
 (0)