Skip to content

Commit a6eb13f

Browse files
PINS Teamjonathan-dilorenzo
authored andcommitted
PUBLIC: [P4-Constraints] Set up well-formedness constraints on match fields for symbolic evaluation in Z3.
PiperOrigin-RevId: 523507929
1 parent b19ec15 commit a6eb13f

File tree

8 files changed

+807
-0
lines changed

8 files changed

+807
-0
lines changed

WORKSPACE.bazel

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,10 @@ http_archive(
8686
strip_prefix = "buildtools-4.0.0",
8787
url = "https://github.com/bazelbuild/buildtools/archive/4.0.0.tar.gz",
8888
)
89+
90+
# -- Load Rules Foreign CC -----------------------------------------------------
91+
# Used for Z3.
92+
93+
load("@rules_foreign_cc//foreign_cc:repositories.bzl", "rules_foreign_cc_dependencies")
94+
95+
rules_foreign_cc_dependencies()

bazel/BUILD.z3.bazel

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
load("@rules_foreign_cc//foreign_cc:defs.bzl", "configure_make")
2+
3+
filegroup(
4+
name = "all",
5+
srcs = glob(["**"]),
6+
)
7+
8+
configure_make(
9+
name = "api",
10+
# Bazel redacts certain cc macros such as __DATA__ and __TIMESTAMP__
11+
# since they will cause the compiled code to have timestamps or other
12+
# similar information in it, causing the compilation to be
13+
# non-deterministic.
14+
# Without such redaction, running the compilation twice with no changes in
15+
# the code will produce seemingly different binaries.
16+
# Bazel fixes this by setting the value of these macros to "redacted",
17+
# which is a valid c++ expression through CFLAGS and CXXFLAGS Toolchain
18+
# options.
19+
# See https://github.com/bazelbuild/bazel/issues/5750
20+
# However, the quotes get dropped because of some bash serialization in
21+
# rules_foreign_cc when they are passed (as bash environment variables) to
22+
# "./configure", causing __DATA__ to resolve to the unquoted token redacted,
23+
# which is usually not a valid c++ expression.
24+
# This fixes that, it makes redacted (the token not the string) an alias to
25+
# 0, which is a valid c++ expression.
26+
# This is a minor improvement on top of:
27+
# https://github.com/bazelbuild/rules_foreign_cc/issues/239
28+
configure_env_vars = {
29+
"CFLAGS": "-Dredacted=0",
30+
"CXXFLAGS": "-Dredacted=0",
31+
},
32+
configure_in_place = True,
33+
lib_source = ":all",
34+
make_commands = [
35+
"cd build",
36+
"make -j8", # Use -j8, hardcoded but otherwise it will be too slow.
37+
"make install",
38+
],
39+
out_binaries = ["z3"],
40+
out_shared_libs = ["libz3.so"],
41+
visibility = ["//visibility:public"],
42+
)

p4_constraints/backend/BUILD.bazel

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ cc_library(
8888
"@com_google_absl//absl/status",
8989
"@com_google_absl//absl/status:statusor",
9090
"@com_google_absl//absl/strings",
91+
"@com_google_absl//absl/strings:str_format",
9192
"@com_google_absl//absl/types:optional",
9293
"@com_googlesource_code_re2//:re2",
9394
],
@@ -139,3 +140,40 @@ cc_test(
139140
"@com_google_googletest//:gtest_main",
140141
],
141142
)
143+
144+
cc_library(
145+
name = "symbolic_interpreter",
146+
srcs = [
147+
"symbolic_interpreter.cc",
148+
],
149+
hdrs = [
150+
"symbolic_interpreter.h",
151+
],
152+
deps = [
153+
":constraint_info",
154+
"//gutils:overload",
155+
"//gutils:source_location",
156+
"//gutils:status",
157+
"//p4_constraints:ast",
158+
"//p4_constraints:ast_cc_proto",
159+
"@com_github_z3prover_z3//:api",
160+
"@com_google_absl//absl/status:statusor",
161+
"@com_google_absl//absl/strings",
162+
"@com_google_absl//absl/strings:str_format",
163+
],
164+
)
165+
166+
cc_test(
167+
name = "symbolic_interpreter_test",
168+
srcs = ["symbolic_interpreter_test.cc"],
169+
deps = [
170+
":constraint_info",
171+
":symbolic_interpreter",
172+
"//gutils:parse_text_proto",
173+
"//gutils:status_matchers",
174+
"//p4_constraints:ast_cc_proto",
175+
"@com_github_z3prover_z3//:api",
176+
"@com_google_absl//absl/status",
177+
"@com_google_googletest//:gtest_main",
178+
],
179+
)

p4_constraints/backend/constraint_info.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,13 @@
2525

2626
#include <stdint.h>
2727

28+
#include <ostream>
2829
#include <string>
2930

3031
#include "absl/container/flat_hash_map.h"
3132
#include "absl/status/statusor.h"
33+
#include "absl/strings/str_cat.h"
34+
#include "absl/strings/str_format.h"
3235
#include "absl/strings/string_view.h"
3336
#include "absl/types/optional.h"
3437
#include "p4/config/v1/p4info.pb.h"
@@ -46,6 +49,12 @@ struct KeyInfo {
4649
ast::Type type;
4750
};
4851

52+
template <typename Sink>
53+
void AbslStringify(Sink& sink, const KeyInfo& info) {
54+
absl::Format(&sink, "KeyInfo{ id: %d; name: \"%s\"; type: { %s }; }", info.id,
55+
info.name, info.type.ShortDebugString());
56+
}
57+
4958
struct TableInfo {
5059
uint32_t id; // Same as Table.preamble.id in p4info.proto.
5160
std::string name; // Same as Table.preamble.name in p4info.proto.
@@ -84,6 +93,12 @@ struct MetadataInfo {
8493
// metadata.
8594
std::optional<MetadataInfo> GetMetadataInfo(absl::string_view metadata_name);
8695

96+
// -- Pretty Printers ----------------------------------------------------------
97+
98+
inline std::ostream& operator<<(std::ostream& os, const KeyInfo& info) {
99+
return os << absl::StrCat(info);
100+
}
101+
87102
} // namespace p4_constraints
88103

89104
#endif // P4_CONSTRAINTS_BACKEND_CONSTRAINT_INFO_H_
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright 2023 The P4-Constraints Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "p4_constraints/backend/symbolic_interpreter.h"
18+
19+
#include <string>
20+
#include <variant>
21+
22+
#include "absl/status/statusor.h"
23+
#include "absl/strings/str_cat.h"
24+
#include "absl/strings/string_view.h"
25+
#include "gutils/overload.h"
26+
#include "gutils/source_location.h"
27+
#include "gutils/status_builder.h"
28+
#include "gutils/status_macros.h"
29+
#include "p4_constraints/ast.h"
30+
#include "p4_constraints/ast.pb.h"
31+
#include "p4_constraints/backend/constraint_info.h"
32+
#include "z3++.h"
33+
34+
namespace p4_constraints {
35+
namespace {
36+
37+
absl::StatusOr<z3::expr> GetFieldAccess(const SymbolicKey& symbolic_key,
38+
absl::string_view field) {
39+
return std::visit(
40+
gutils::Overload{
41+
[&](const SymbolicExact& exact) -> absl::StatusOr<z3::expr> {
42+
if (field == "value") return exact.value;
43+
return gutils::InvalidArgumentErrorBuilder(GUTILS_LOC)
44+
<< "Exact has no field '" << field << "'";
45+
},
46+
[&](const SymbolicTernary& ternary) -> absl::StatusOr<z3::expr> {
47+
if (field == "value") return ternary.value;
48+
if (field == "mask") return ternary.mask;
49+
return gutils::InvalidArgumentErrorBuilder(GUTILS_LOC)
50+
<< "Ternary has no field \"" << field << "\"";
51+
},
52+
[&](const SymbolicLpm& lpm) -> absl::StatusOr<z3::expr> {
53+
if (field == "value") return lpm.value;
54+
if (field == "prefix_length") return lpm.prefix_length;
55+
return gutils::InvalidArgumentErrorBuilder(GUTILS_LOC)
56+
<< "LPM has no field \"" << field << "\"";
57+
},
58+
},
59+
symbolic_key);
60+
}
61+
62+
} // namespace
63+
64+
absl::StatusOr<SymbolicKey> AddSymbolicKey(const KeyInfo& key,
65+
z3::solver& solver) {
66+
ASSIGN_OR_RETURN(int bitwidth, ast::TypeBitwidthOrStatus(key.type));
67+
if (bitwidth == 0) {
68+
return gutils::InvalidArgumentErrorBuilder(GUTILS_LOC)
69+
<< "expected a key type with bitwidth > 0, but got: " << key;
70+
}
71+
switch (key.type.type_case()) {
72+
case ast::Type::kExact: {
73+
return SymbolicExact{
74+
.value = solver.ctx().bv_const(key.name.c_str(), bitwidth),
75+
};
76+
}
77+
case ast::Type::kOptionalMatch:
78+
case ast::Type::kTernary: {
79+
// Optionals and ternaries are both encoded as ternaries.
80+
z3::expr value = solver.ctx().bv_const(key.name.c_str(), bitwidth);
81+
z3::expr mask = solver.ctx().bv_const(
82+
absl::StrCat(key.name, "_mask").c_str(), bitwidth);
83+
// This is a P4RT canonicity constraint ensuring that masked-off bits must
84+
// be zero.
85+
solver.add((mask & value) == value);
86+
if (key.type.has_optional_match()) {
87+
// For optionals in P4RT, the mask must be either 0 (denoting a
88+
// wildcard) or all ones (denoting an exact match). '-1' is equivalent
89+
// to an all_one bitvector in Z3.
90+
solver.add(mask == 0 || mask == -1);
91+
}
92+
return SymbolicTernary{
93+
.value = value,
94+
.mask = mask,
95+
};
96+
}
97+
case ast::Type::kLpm: {
98+
z3::expr value = solver.ctx().bv_const(key.name.c_str(), bitwidth);
99+
z3::expr prefix_length = solver.ctx().int_const(
100+
absl::StrCat(key.name, "_prefix_length").c_str());
101+
z3::expr suffix_length = z3::int2bv(
102+
/*bitwidth=*/bitwidth, /*z3_int_expr=*/bitwidth - prefix_length);
103+
// For LPMs, the prefix length must be no larger than the bitwidth, and
104+
// only `prefix_length` bits of the value should be set. We capture the
105+
// second constraint by saying that the value is unchanged after two bit
106+
// shifts.
107+
solver.add(prefix_length >= 0 && prefix_length <= bitwidth &&
108+
z3::shl(z3::lshr(value, suffix_length), suffix_length) ==
109+
value);
110+
return SymbolicLpm{
111+
.value = value,
112+
.prefix_length = prefix_length,
113+
};
114+
}
115+
116+
// TODO(b/291779521): Range matches are not currently supported.
117+
case ast::Type::kRange:
118+
return gutils::UnimplementedErrorBuilder(GUTILS_LOC)
119+
<< "Range matches are not currently supported by the "
120+
"p4-constraints symbolic representation.";
121+
122+
// Non-match types.
123+
case ast::Type::kUnknown:
124+
case ast::Type::kUnsupported:
125+
case ast::Type::kBoolean:
126+
case ast::Type::kArbitraryInt:
127+
case ast::Type::kFixedUnsigned:
128+
case ast::Type::TYPE_NOT_SET:
129+
return gutils::InvalidArgumentErrorBuilder(GUTILS_LOC)
130+
<< "expected a match type, but got: " << key;
131+
}
132+
return gutils::InvalidArgumentErrorBuilder(GUTILS_LOC)
133+
<< "got invalid type: " << key;
134+
}
135+
136+
absl::StatusOr<z3::expr> GetValue(const SymbolicKey& symbolic_key) {
137+
return GetFieldAccess(symbolic_key, "value");
138+
}
139+
140+
absl::StatusOr<z3::expr> GetMask(const SymbolicKey& symbolic_key) {
141+
return GetFieldAccess(symbolic_key, "mask");
142+
}
143+
144+
absl::StatusOr<z3::expr> GetPrefixLength(const SymbolicKey& symbolic_key) {
145+
return GetFieldAccess(symbolic_key, "prefix_length");
146+
}
147+
148+
} // namespace p4_constraints

0 commit comments

Comments
 (0)