Skip to content

Commit 8fa6b23

Browse files
[Verif][LEC] Make LECOp result optional to avoid unsafe conversion (#8701)
#8497 introduced a lowering pattern for the LogicEquivalenceCheckingOp which changes behavior depending on whether the result of the operation is used. This is generally not safe and can cause lowerings to erroneously consider the result to be used/unused. The pattern must only depend on the operation itself, not on its def-use context. By making the result of the operation optional, we should be able to retain the current behavior. In the pattern op.use_empty() is simply replaced by op.getNumResults() == 0. However, we now have to decide at the point of creating the LogicEquivalenceCheckingOp whether the result should be made accessible to the IR. Co-authored-by: Bea Healy <57840981+TaoBi22@users.noreply.github.com>
1 parent e9cde40 commit 8fa6b23

File tree

5 files changed

+35
-20
lines changed

5 files changed

+35
-20
lines changed

include/circt/Dialect/Verif/VerifOps.td

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,20 +236,30 @@ def LogicEquivalenceCheckingOp : VerifOp<"lec", [
236236
done by constructing a miter circuit and testing whether the result is
237237
satisfiable (can be non-zero for some input), or two canonical BDDs could be
238238
constructed and compared for identity, etc.
239-
239+
240240
The number and types of the inputs and outputs of the two circuits (and thus
241241
also the block arguments and yielded values of both regions) have to match.
242242
Otherwise, the two should be considered trivially non-equivalent.
243+
244+
The operation can return an boolean result that is `true` iff equivalence
245+
of the two circuits has been proven. The result can be omitted for use-cases
246+
which do not allow further processing (e.g., SMT-LIB exporting).
243247
}];
244248

245-
let results = (outs I1:$areEquivalent);
249+
let results = (outs Optional<I1>:$areEquivalent);
246250
let regions = (region SizedRegion<1>:$firstCircuit,
247251
SizedRegion<1>:$secondCircuit);
248252

249253
let assemblyFormat = [{
250-
attr-dict `first` $firstCircuit `second` $secondCircuit
254+
attr-dict (`:` type($areEquivalent)^)?
255+
`first` $firstCircuit
256+
`second` $secondCircuit
251257
}];
252258

259+
let builders = [OpBuilder<(ins "bool":$hasResult), [{
260+
build($_builder, $_state, hasResult ? $_builder.getI1Type() : Type{} );
261+
}]>];
262+
253263
let hasRegionVerifier = true;
254264
}
255265

lib/Conversion/VerifToSMT/VerifToSMT.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,12 @@ struct LogicEquivalenceCheckingOpConversion
9393
return success();
9494
}
9595

96-
auto unusedResult = op.use_empty();
96+
auto hasNoResult = op.getNumResults() == 0;
9797

9898
// Solver will only return a result when it is used to check the returned
9999
// value.
100100
smt::SolverOp solver;
101-
if (unusedResult)
101+
if (hasNoResult)
102102
solver = rewriter.create<smt::SolverOp>(loc, TypeRange{}, ValueRange{});
103103
else
104104
solver = rewriter.create<smt::SolverOp>(loc, rewriter.getI1Type(),
@@ -163,7 +163,7 @@ struct LogicEquivalenceCheckingOpConversion
163163
// operations empty. If the result is used, we create a check operation with
164164
// the result type of the operation and yield the result of the check
165165
// operation.
166-
if (unusedResult) {
166+
if (hasNoResult) {
167167
auto checkOp = rewriter.create<smt::CheckOp>(loc, TypeRange{});
168168
rewriter.createBlock(&checkOp.getSatRegion());
169169
rewriter.create<smt::YieldOp>(loc);

lib/Tools/circt-lec/ConstructLEC.cpp

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ struct ConstructLECPass
3535
void runOnOperation() override;
3636
hw::HWModuleOp lookupModule(StringRef name);
3737
Value constructMiter(OpBuilder builder, Location loc, hw::HWModuleOp moduleA,
38-
hw::HWModuleOp moduleB);
38+
hw::HWModuleOp moduleB, bool withResult);
3939
};
4040
} // namespace
4141

@@ -69,10 +69,13 @@ hw::HWModuleOp ConstructLECPass::lookupModule(StringRef name) {
6969

7070
Value ConstructLECPass::constructMiter(OpBuilder builder, Location loc,
7171
hw::HWModuleOp moduleA,
72-
hw::HWModuleOp moduleB) {
72+
hw::HWModuleOp moduleB,
73+
bool withResult) {
74+
7375
// Create the miter circuit that return equivalence result.
74-
auto lecOp = builder.create<verif::LogicEquivalenceCheckingOp>(loc);
75-
Value areEquivalent = lecOp.getAreEquivalent();
76+
auto lecOp =
77+
builder.create<verif::LogicEquivalenceCheckingOp>(loc, withResult);
78+
7679
builder.cloneRegionBefore(moduleA.getBody(), lecOp.getFirstCircuit(),
7780
lecOp.getFirstCircuit().end());
7881
builder.cloneRegionBefore(moduleB.getBody(), lecOp.getSecondCircuit(),
@@ -97,7 +100,7 @@ Value ConstructLECPass::constructMiter(OpBuilder builder, Location loc,
97100
sortTopologically(&lecOp.getFirstCircuit().front());
98101
sortTopologically(&lecOp.getSecondCircuit().front());
99102

100-
return areEquivalent;
103+
return withResult ? lecOp.getAreEquivalent() : Value{};
101104
}
102105

103106
void ConstructLECPass::runOnOperation() {
@@ -121,7 +124,7 @@ void ConstructLECPass::runOnOperation() {
121124

122125
// Only construct the miter with no additional insertions.
123126
if (insertMode == lec::InsertAdditionalModeEnum::None) {
124-
constructMiter(builder, loc, moduleA, moduleB);
127+
constructMiter(builder, loc, moduleA, moduleB, /*withResult*/ false);
125128
return;
126129
}
127130

@@ -157,7 +160,9 @@ void ConstructLECPass::runOnOperation() {
157160
builder.createBlock(&entryFunc.getBody());
158161

159162
// Create the miter circuit that returns equivalence result.
160-
auto areEquivalent = constructMiter(builder, loc, moduleA, moduleB);
163+
auto areEquivalent =
164+
constructMiter(builder, loc, moduleA, moduleB, /*withResult*/ true);
165+
assert(!!areEquivalent && "Expected LEC operation with result.");
161166

162167
// TODO: we should find a more elegant way of reporting the result than
163168
// already inserting some LLVM here

test/Conversion/VerifToSMT/verif-to-smt.mlir

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func.func @test_lec(%arg0: !smt.bv<1>) -> (i1, i1, i1) {
5454
// CHECK: smt.yield [[FALSE]]
5555
// CHECK: smt.yield [[TRUE]]
5656
// CHECK: smt.yield [[V8]] :
57-
%1 = verif.lec first {
57+
%1 = verif.lec : i1 first {
5858
^bb0(%arg1: i32, %arg2: i32):
5959
verif.yield %arg1, %arg2 : i32, i32
6060
} second {
@@ -67,23 +67,23 @@ func.func @test_lec(%arg0: !smt.bv<1>) -> (i1, i1, i1) {
6767
// CHECK: [[V9:%.+]] = smt.declare_fun : !smt.bv<32>
6868
// CHECK: [[V10:%.+]] = smt.distinct [[V9]], [[V9]] : !smt.bv<32>
6969
// CHECK: smt.assert [[V10]]
70-
%2 = verif.lec first {
70+
%2 = verif.lec : i1 first {
7171
^bb0(%arg1: i32):
7272
verif.yield %arg1 : i32
7373
} second {
7474
^bb0(%arg1: i32):
7575
verif.yield %arg1 : i32
7676
}
7777

78-
%3 = verif.lec first {
78+
%3 = verif.lec : i1 first {
7979
^bb0(%arg1: i32):
8080
verif.yield
8181
} second {
8282
^bb0(%arg1: i32):
8383
verif.yield
8484
}
8585

86-
%4 = verif.lec first {
86+
verif.lec first {
8787
^bb0(%arg1: i32):
8888
verif.yield %arg1 : i32
8989
} second {

test/Tools/circt-lec/construct-lec.mlir

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ hw.module @modB0(in %in0: i32, in %in1: i32, out out: i32) {
1111
}
1212

1313
// CHECK: func.func @modA0() {
14-
// CHECK: [[V0:%.+]] = verif.lec first {
14+
// CHECK: [[V0:%.+]] = verif.lec : i1 first {
1515
// CHECK: ^bb0([[ARG0:%.+]]: i32, [[ARG1:%.+]]: i32):
1616
// CHECK: [[V1:%.+]] = comb.add [[ARG0]], [[ARG1]]
1717
// CHECK: verif.yield [[V1]]
@@ -48,7 +48,7 @@ hw.module @modB1() {
4848
}
4949

5050
// CHECK1: func.func @modA1() {
51-
// CHECK1: [[V0:%.+]] = verif.lec first {
51+
// CHECK1: [[V0:%.+]] = verif.lec : i1 first {
5252
// CHECK1: } second {
5353
// CHECK1: }
5454
// CHECK1: [[V1:%.+]] = llvm.mlir.addressof @"c1 == c2\0A" : !llvm.ptr
@@ -61,5 +61,5 @@ hw.module @modB1() {
6161
// RUN: circt-opt --construct-lec="first-module=modA0 second-module=modB0 insert-mode=none" %s | FileCheck %s --check-prefix=CHECK2
6262

6363
// CHECK2-NOT: func.func
64-
// CHECK2: verif.lec
64+
// CHECK2: verif.lec first
6565
// CHECK2-NOT: llvm.mlir.global

0 commit comments

Comments
 (0)