Skip to content

Commit

Permalink
PUBLIC: [p4-constraints] Add support for action restrictions in the i…
Browse files Browse the repository at this point in the history
…nterpreter. (#115)

In this CL, ReasonEntryViolatesConstraint was extended to check action constraints for a given table entry for actions and action profile action sets. EvaluationContext has been refactored to include both TableEntry and ActionInvocation information for evaluation. Please see go/supporting-actions-in-p4-constraints for more details.

PiperOrigin-RevId: 579986405

Co-authored-by: PINS Team <[email protected]>
  • Loading branch information
angelazhang8 and PINS Team authored Nov 21, 2023
1 parent 9bf7a6c commit 071abdc
Show file tree
Hide file tree
Showing 7 changed files with 689 additions and 119 deletions.
1 change: 1 addition & 0 deletions p4_constraints/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ cc_library(
":ast_cc_proto",
":constraint_source",
"//gutils:proto",
"//gutils:source_location",
"//gutils:status",
"@com_google_absl//absl/log",
"@com_google_absl//absl/status:statusor",
Expand Down
1 change: 1 addition & 0 deletions p4_constraints/backend/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ cc_test(
linkstatic = True,
deps = [
":constraint_info",
":errors",
":interpreter",
"//gutils:ordered_map",
"//gutils:parse_text_proto",
Expand Down
306 changes: 234 additions & 72 deletions p4_constraints/backend/interpreter.cc

Large diffs are not rendered by default.

61 changes: 40 additions & 21 deletions p4_constraints/backend/interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,14 @@

namespace p4_constraints {

// Checks if a given table entry satisfies the entry constraint attached to its
// associated table. Returns the empty string if this is the case, or a
// human-readable nonempty string explaining why it is not the case otherwise.
// This function has the same runtime on successes as `EntryMeetsConstraint`
// and longer runtimes on failure (by a constant factor) and should generally be
// preferred. Returns an InvalidArgument Status if the entry belongs to a table
// not present in ConstraintInfo, or if it is inconsistent with the table
// Checks if a given table entry satisfies the entry/action(s) constraint(s)
// attached to its associated table/action(s). Returns the empty string if this
// is the case or a human-readable nonempty string explaining why it is not the
// case otherwise. Returns an InvalidArgument Status if the entry/action(s)
// don't belong in ConstraintInfo or is inconsistent with the table/action(s)
// definition in ConstraintInfo.
absl::StatusOr<std::string> ReasonEntryViolatesConstraint(
const p4::v1::TableEntry& entry, const ConstraintInfo& context);
const p4::v1::TableEntry& entry, const ConstraintInfo& constraint_info);

// -- END OF PUBLIC INTERFACE --------------------------------------------------

Expand Down Expand Up @@ -141,17 +139,23 @@ struct TableEntry {
// In contrast to p4::v1::TableEntry, all keys must be present, i.e. this must
// be a total map from key names to values.
absl::flat_hash_map<std::string, EvalResult> keys;
// TODO(smolkaj): once we support actions, they will be added here.
};

// Parses p4::v1::TableEntry
absl::StatusOr<TableEntry> ParseEntry(const p4::v1::TableEntry& entry,
const TableInfo& table_info);
// Parsed representation of p4::v1::Action.
struct ActionInvocation {
uint32_t action_id;
std::string action_name;
// Map of param names to param values.
absl::flat_hash_map<std::string, Integer> action_parameters;
};

// Context under which an `Expression` is evaluated. An `EvaluationContext` is
// "valid" for a given `Expression` iff the following holds:
// -`entry` must contain all fields in the expression, with correct type. If
// not, an Error Status is returned
// - If the `Expression` being evaluated is a Table constraint, then
// `constraint_context` is a TableEntry type. If the `Expression` being
// evaluated is an Action constraint, then `constraint_context` is an
// ActionInvocation type. `constraint_context` must contain all fields in the
// expression, with correct type. If not, an Error Status is returned.
// -`source` must be the source from which the expression was parsed. If not,
// behaviour is undefined (depending on the source, either an InternalError
// will be given or a non-sense quote will be returned)
Expand All @@ -160,17 +164,32 @@ absl::StatusOr<TableEntry> ParseEntry(const p4::v1::TableEntry& entry,
// expensive copies. This leads to the possibility of dangling references, use
// with caution.
struct EvaluationContext {
const TableEntry& entry;
const ConstraintSource& source;
std::variant<ActionInvocation, TableEntry> constraint_context;
const ConstraintSource& constraint_source;
};

// Parses p4::v1::TableEntry into an EvaluationContext using keys, table name,
// and constraint source from table_info. Returns InvalidArgument if it is not
// possible to parse entry fields into keys or if an exact match key is not
// present in the entry.
absl::StatusOr<EvaluationContext> ParseTableEntry(
const p4::v1::TableEntry& entry, const TableInfo& table_info);

// Parses p4::v1::Action into an EvaluationContext using action parameters,
// action name, and constraint source from action_info. Returns InvalidArgument
// if an Action parameter cannot be found in action_info or if there are
// duplicate action parameters.
absl::StatusOr<EvaluationContext> ParseAction(const p4::v1::Action& action,
const ActionInfo& action_info);

// Used to memoize evaluation results to avoid re-computation.
using EvaluationCache = absl::flat_hash_map<const ast::Expression*, bool>;

// Evaluates `expr` over `context.entry` to an `EvalResult`. Returns error
// status if AST is malformed and uses `context.source` to quote constraint.
// `eval_cache` holds boolean results, useful for avoiding recomputation when an
// explanation is desired. Passing a nullptr for `eval-cache` disables caching.
// status if AST is malformed and uses `context.constraint_source` to quote
// constraint. `eval_cache` holds boolean results, useful for avoiding
// recomputation when an explanation is desired. Passing a nullptr for
// `eval-cache` disables caching.
absl::StatusOr<EvalResult> Eval(const ast::Expression& expr,
const EvaluationContext& context,
EvaluationCache* eval_cache);
Expand All @@ -188,8 +207,8 @@ absl::StatusOr<EvalResult> Eval(const ast::Expression& expr,
// Uses `eval_cache` and `size_cache` to avoid recomputation, allowing it to run
// in linear time. Given current language specification, search only requires
// traversal of nodes with type boolean. Traversal of non-boolean nodes or an
// invalid AST will return InternalError Status. Uses `context.source` to quote
// constraint on error.
// invalid AST will return InternalError Status. Uses
// `context.constraint_source` to quote constraint on error.
absl::StatusOr<const ast::Expression*> MinimalSubexpressionLeadingToEvalResult(
const ast::Expression& expression, const EvaluationContext& context,
EvaluationCache& eval_cache, ast::SizeCache& size_cache);
Expand Down
32 changes: 25 additions & 7 deletions p4_constraints/backend/interpreter_golden_test_runner.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include <iostream>
#include <string>
#include <variant>
#include <vector>

#include "absl/log/check.h"
Expand All @@ -29,6 +30,7 @@
#include "gutils/parse_text_proto.h"
#include "gutils/status_macros.h"
#include "p4_constraints/backend/constraint_info.h"
#include "p4_constraints/backend/errors.h"
#include "p4_constraints/backend/interpreter.h"
#include "p4_constraints/backend/type_checker.h"
#include "p4_constraints/constraint_source.h"
Expand Down Expand Up @@ -228,17 +230,28 @@ std::vector<TestCase> TestCases() {
return test_cases;
}

std::string EntryToString(const TableEntry& entry) {
absl::StatusOr<std::string> EntryToString(const EvaluationContext& context,
const TableInfo& table_info) {
const TableEntry* table_entry =
std::get_if<TableEntry>(&context.constraint_context);
if (table_entry == nullptr) {
return RuntimeTypeError(context.constraint_source,
table_info.constraint->start_location(),
table_info.constraint->end_location())
<< "The constraint context does not contain a TableEntry.";
}

std::string key_info = absl::StrJoin(
gutils::Ordered(entry.keys), "\n", [](std::string* out, auto pair) {
gutils::Ordered(table_entry->keys), "\n",
[](std::string* out, auto pair) {
absl::StrAppend(out, "Key:\"", pair.first,
"\" -> Value: ", EvalResultToString(pair.second));
});
return absl::StrFormat(
"Table Name: \"%s\"\n"
"Priority:%d\n"
"%s\n",
entry.table_name, entry.priority, key_info);
table_entry->table_name, table_entry->priority, key_info);
}

absl::Status main() {
Expand All @@ -249,15 +262,20 @@ absl::Status main() {
if (table_info == nullptr) {
return absl::InvalidArgumentError("No table info");
}
ASSIGN_OR_RETURN(TableEntry input,
ParseEntry(test_case.table_entry, *table_info));
ASSIGN_OR_RETURN(const EvaluationContext context,
ParseTableEntry(test_case.table_entry, *table_info));

absl::StatusOr<std::string> entry = EntryToString(context, *table_info);
if (!entry.ok()) {
std::cout << "=== ERROR ===\n" << entry.status();
return absl::InvalidArgumentError(entry.status().ToString());
}
std::cout << "### ReasonEntryViolatestConstraint Test ###################\n"
<< "=== INPUT ===\n"
<< "--- Constraint ---\n"
<< test_case.constraint << "\n"
<< "--- Table Entry ---\n"
<< EntryToString(input) << "\n";

<< *entry << "\n";
absl::StatusOr<std::string> result =
ReasonEntryViolatesConstraint(test_case.table_entry, constraint_info);
if (result.ok()) {
Expand Down
Loading

0 comments on commit 071abdc

Please sign in to comment.