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
20 changes: 20 additions & 0 deletions include/p4mlir/Dialect/P4CoreLib/P4CoreLib_Ops.td
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,24 @@ def PacketExtractVariableOp : P4CoreLib_Op<"extract_header_variable"> {
}];
}

def StaticAssertOp : P4CoreLib_Op<"static_assert"> {
let summary = "Compile-time assertion";
let description = [{
Represents a compile-time assertion. The condition must eventually
evaluate to a constant boolean. If the condition is true, this op
can be folded away. If false, a separate pass will emit an error.

Optionally includes a message string for error reporting.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once the folding is removed (see other comment), let's use something along the lines of:

        Represents a compile-time assertion with an optional error message.
        The condition must eventually evaluate to a constant boolean.
        An error will be emitted if the condition evaluates to false.

}];

let arguments = (ins BooleanType:$cond,
OptionalAttr<StrAttr>:$message);
let results = (outs BooleanType:$result);
let assemblyFormat = [{
$cond (`message` $message^)? attr-dict `:` type($cond) `->` type($result)
}];

let hasFolder = 1;
}

#endif //P4MLIR_DIALECT_P4CORELIB_P4CORELIB_OPS_TD
2 changes: 2 additions & 0 deletions include/p4mlir/Transforms/Passes.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ namespace P4::P4MLIR {
#define GEN_PASS_DECL_INLINECONTROLS
#define GEN_PASS_DECL_EXPANDEMIT
#define GEN_PASS_DECL_SYMBOLDCE
#define GEN_PASS_DECL_EVALUATESTATICASSERT
#include "p4mlir/Transforms/Passes.h.inc"

std::unique_ptr<mlir::Pass> createPrintParsersGraphPass();
Expand All @@ -40,6 +41,7 @@ std::unique_ptr<mlir::Pass> createInlineParsersPass();
std::unique_ptr<mlir::Pass> createInlineControlsPass();
std::unique_ptr<mlir::Pass> createExpandEmitPass();
std::unique_ptr<mlir::Pass> createSymbolDCEPass();
std::unique_ptr<mlir::Pass> createEvaluateStaticAssertPass();

#define GEN_PASS_REGISTRATION
#include "p4mlir/Transforms/Passes.h.inc"
Expand Down
20 changes: 20 additions & 0 deletions include/p4mlir/Transforms/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -344,4 +344,24 @@ def SymbolDCE : Pass<"p4hir-symbol-dce"> {
];
}

//===----------------------------------------------------------------------===//
// EvaluateStaticAssert
//===----------------------------------------------------------------------===//

def EvaluateStaticAssert : Pass<"p4hir-evaluate-static-assert", "mlir::ModuleOp"> {
let summary = "Evaluate static_assert operations";
let description = [{
Evaluates p4corelib.static_assert operations at compile time:
- If condition is constant true: removes the assertion (replaces with constant true)
- If condition is constant false: emits compile-time error
- If condition is not yet constant: leaves for later evaluation
}];

let constructor = "P4MLIR::createEvaluateStaticAssertPass()";
let dependentDialects = [
"P4MLIR::P4CoreLib::P4CoreLibDialect",
"P4MLIR::P4HIR::P4HIRDialect"
];
}

#endif // P4MLIR_TRANSFORMS_PASSES_TD
28 changes: 28 additions & 0 deletions lib/Conversion/P4HIRToCoreLib/P4HIRToCoreLib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/LogicalResult.h"
#include "mlir/IR/Matchers.h"
#include "mlir/IR/SymbolTable.h"
#include "mlir/IR/Types.h"
#include "mlir/Interfaces/FunctionInterfaces.h"
Expand Down Expand Up @@ -84,6 +85,33 @@ struct CallOpConversionPattern : public OpConversionPattern<P4HIR::CallOp> {
operands.getArgOperands());
return success();
}
// Handle static_assert: lower to P4CoreLib::StaticAssertOp
if (callee.getLeafReference().getValue().starts_with("static_assert")) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an insufficient check (e.g. there could be a user named function static_assert_condition that would pass this). static assert will be only present in an overload set that looks like this:

  p4hir.overload_set @static_assert {
    p4hir.func @static_assert_0(!p4hir.bool {p4hir.dir = #undir, p4hir.param_name = "check"}, !string {p4hir.dir = #undir, p4hir.param_name = "message"}) -> !p4hir.bool
    p4hir.func @static_assert_1(!p4hir.bool {p4hir.dir = #undir, p4hir.param_name = "check"}) -> !p4hir.bool
  }

So you should get the overload_set and then check if it's static_assert.

auto args = operands.getArgOperands();

if (args.empty())
return op.emitError("static_assert requires a condition");

Value cond = args[0];
StringAttr message = nullptr;
if (args.size() == 2) {
if (auto constOp = args[1].getDefiningOp<P4HIR::ConstOp>()) {
message = llvm::dyn_cast<StringAttr>(constOp.getValue());
}
if (!message) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this allowed to happen? (i.e. a non constant string argument).
If not, use assert/llvm_unreachable

op.emitWarning("static_assert message is not a compile-time constant; ignoring");
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove empty line, plus formatting above is off, have you run clang-format?
Also, as the llvm code styling guideline suggests, please don't use brackets for simple one-line if statements.

}

rewriter.replaceOpWithNewOp<P4CoreLib::StaticAssertOp>(
op,
op.getResult().getType(),
cond,
message
);
return success();
}

return failure();
}
Expand Down
22 changes: 22 additions & 0 deletions lib/Dialect/P4CoreLib/P4CoreLib_Ops.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "p4mlir/Dialect/P4CoreLib/P4CoreLib_Ops.h"

#include "p4mlir/Dialect/P4CoreLib/P4CoreLib_Dialect.h"
#include "p4mlir/Dialect/P4HIR/P4HIR_Attrs.h"
#include "p4mlir/Dialect/P4HIR/P4HIR_Dialect.h"

using namespace mlir;
Expand All @@ -14,6 +15,27 @@ void P4CoreLib::PacketLookAheadOp::getAsmResultNames(mlir::OpAsmSetValueNameFn s
setNameFn(getResult(), "lookahead");
}

OpFoldResult P4CoreLib::StaticAssertOp::fold(FoldAdaptor adaptor) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say we shouldn't have this folding pattern. It basically implements half of the pass, and creates redundancy while not providing any clear benefit as we'd have to run the pass anyway. Was this intended to be an optimization?

// This fold handles the true case early during canonicalization, without
// needing the full EvaluateStaticAssertPass pipeline. False assertions are
// intentionally left unhandled here so the pass can emit proper error messages.
auto condAttr = adaptor.getCond();
if (!condAttr)
return {}; // Condition not constant yet

auto boolAttr = llvm::dyn_cast<P4HIR::BoolAttr>(condAttr);
if (!boolAttr)
return {}; // Not a boolean

if (boolAttr.getValue()) {
// Assertion passed -> fold to constant true
return P4HIR::BoolAttr::get(getContext(), true);
}

// Assertion failed -> seperate pass will handle the error
return {};
}

void P4CoreLib::P4CoreLibDialect::initialize() {
registerTypes();
addOperations<
Expand Down
1 change: 1 addition & 0 deletions lib/Transforms/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
add_mlir_dialect_library(P4MLIRTransforms
EnumElimination.cpp
EvaluateStaticAssert.cpp
FlattenCFG.cpp
IRUtils.cpp
InlineParserControl.cpp
Expand Down
69 changes: 69 additions & 0 deletions lib/Transforms/EvaluateStaticAssert.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#include "p4mlir/Transforms/Passes.h"

#include "mlir/IR/PatternMatch.h"
#include "mlir/Pass/Pass.h"
#include "p4mlir/Dialect/P4CoreLib/P4CoreLib_Dialect.h"
#include "p4mlir/Dialect/P4CoreLib/P4CoreLib_Ops.h"
#include "p4mlir/Dialect/P4HIR/P4HIR_Attrs.h"
#include "p4mlir/Dialect/P4HIR/P4HIR_Dialect.h"
#include "p4mlir/Dialect/P4HIR/P4HIR_Ops.h"

using namespace mlir;

namespace P4::P4MLIR {

#define GEN_PASS_DEF_EVALUATESTATICASSERT
#include "p4mlir/Transforms/Passes.cpp.inc"

namespace {

struct EvaluateStaticAssertPass
: public impl::EvaluateStaticAssertBase<EvaluateStaticAssertPass> {

void runOnOperation() override {
auto moduleOp = getOperation();
bool hasError = false;

SmallVector<P4CoreLib::StaticAssertOp> assertOps;
moduleOp->walk([&](P4CoreLib::StaticAssertOp op) {
assertOps.push_back(op);
});

for (auto op : assertOps) {
Value cond = op.getCond();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for single use variable here, just do op.getCond().getDefiningOp... below


auto constOp = cond.getDefiningOp<P4HIR::ConstOp>();
if (!constOp)
continue;

auto boolAttr = llvm::dyn_cast<P4HIR::BoolAttr>(constOp.getValue());
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since cond is BooleanType, let's use llvm::cast here and remove if (!boolAttr) continue;

if (!boolAttr)
continue;

if (boolAttr.getValue()) {
OpBuilder builder(op);
auto trueAttr = P4HIR::BoolAttr::get(op.getContext(), true);
auto replacement = builder.create<P4HIR::ConstOp>(op.getLoc(), trueAttr);
op.replaceAllUsesWith(replacement.getResult());
op.erase();
} else {
if (auto msg = op.getMessage())
op.emitError() << "static assertion failed: " << msg.value();
else
op.emitError("static assertion failed");
hasError = true;
}
}

if (hasError)
signalPassFailure();
}
};

} // namespace

std::unique_ptr<Pass> createEvaluateStaticAssertPass() {
return std::make_unique<EvaluateStaticAssertPass>();
}

} // namespace P4::P4MLIR
44 changes: 44 additions & 0 deletions test/Conversion/P4CoreLib/static_assert.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// RUN: p4mlir-opt %s --lower-to-p4corelib | FileCheck %s

// Test lowering of static_assert calls to p4corelib.static_assert op

!string = !p4hir.string
#true = #p4hir.bool<true> : !p4hir.bool
#false = #p4hir.bool<false> : !p4hir.bool
#undir = #p4hir<dir undir>

module {
p4hir.func @static_assert_0(!p4hir.bool {p4hir.dir = #undir, p4hir.param_name = "check"}, !string {p4hir.dir = #undir, p4hir.param_name = "message"}) -> !p4hir.bool annotations {corelib}
p4hir.func @static_assert_1(!p4hir.bool {p4hir.dir = #undir, p4hir.param_name = "check"}) -> !p4hir.bool annotations {corelib}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the advice at the top, these must be in a overload_set, as it would be generated by translating the core.p4 definitions.


// Test: static_assert(bool) - single argument form
// CHECK-LABEL: @test_single_arg
// CHECK: %[[C:.*]] = p4hir.const
// CHECK: %[[R:.*]] = p4corelib.static_assert %[[C]]
p4hir.func @test_single_arg() -> !p4hir.bool {
%true = p4hir.const #true
%result = p4hir.call @static_assert_1(%true) : (!p4hir.bool) -> !p4hir.bool
p4hir.return %result : !p4hir.bool
}

// Test: static_assert(bool, string) - two argument form
// CHECK-LABEL: @test_with_message
// CHECK: %[[C:.*]] = p4hir.const
// CHECK: %[[R:.*]] = p4corelib.static_assert %[[C]]
p4hir.func @test_with_message() -> !p4hir.bool {
%true = p4hir.const #true
%msg = p4hir.const "compile-time check" : !string
%result = p4hir.call @static_assert_0(%true, %msg) : (!p4hir.bool, !string) -> !p4hir.bool
p4hir.return %result : !p4hir.bool
}

// Test: lowering works regardless of condition value
// CHECK-LABEL: @test_false_lowering
// CHECK: %[[C:.*]] = p4hir.const
// CHECK: %[[R:.*]] = p4corelib.static_assert %[[C]]
p4hir.func @test_false_lowering() -> !p4hir.bool {
%false = p4hir.const #false
%result = p4hir.call @static_assert_1(%false) : (!p4hir.bool) -> !p4hir.bool
p4hir.return %result : !p4hir.bool
}
}
14 changes: 14 additions & 0 deletions test/Transforms/Passes/evaluate-static-assert-error.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// RUN: not p4mlir-opt %s --p4hir-evaluate-static-assert 2>&1 | FileCheck %s

// Test: static_assert(false) emits compile-time error

#false = #p4hir.bool<false> : !p4hir.bool

module {
// CHECK: error: static assertion failed
p4hir.func @test_false_error() -> !p4hir.bool {
%false = p4hir.const #false
%result = p4corelib.static_assert %false : !p4hir.bool -> !p4hir.bool
p4hir.return %result : !p4hir.bool
}
}
25 changes: 25 additions & 0 deletions test/Transforms/Passes/evaluate-static-assert.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// RUN: p4mlir-opt %s --p4hir-evaluate-static-assert | FileCheck %s

// Test: static_assert(true) is removed by EvaluateStaticAssertPass

#true = #p4hir.bool<true> : !p4hir.bool

module {
// CHECK-LABEL: @test_true_removed
// CHECK-NOT: p4corelib.static_assert
// CHECK: p4hir.return
p4hir.func @test_true_removed() -> !p4hir.bool {
%true = p4hir.const #true
%result = p4corelib.static_assert %true : !p4hir.bool -> !p4hir.bool
p4hir.return %result : !p4hir.bool
}

// Test: non-constant condition is left unchanged
// CHECK-LABEL: @test_non_constant_unchanged
// CHECK: p4corelib.static_assert
p4hir.func @test_non_constant_unchanged(%arg0: !p4hir.bool) -> !p4hir.bool {
%result = p4corelib.static_assert %arg0 : !p4hir.bool -> !p4hir.bool
p4hir.return %result : !p4hir.bool
}
}

Loading