Skip to content

Commit 789c2aa

Browse files
authored
fix(riscv): access constant values from the LLVM dialect correctly (#1473)
continuing the effort to correctly deal with subtleties in MLIR constant values, sigh
1 parent 9120821 commit 789c2aa

8 files changed

Lines changed: 95 additions & 15 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: veir-opt %s -p=isel-sdag-riscv64 | filecheck %s
2+
3+
"builtin.module"() ({
4+
"func.func"() <{sym_name = "binop_constants", function_type = (i64, i32) -> (i64, i64, i64, i32)}> ({
5+
^bb0(%x: i64, %y: i32):
6+
%minusOne = "llvm.mlir.constant"() <{value = 255 : i8}> : () -> i64
7+
%one = "llvm.mlir.constant"() <{value = -1 : i1}> : () -> i64
8+
%sum = "llvm.add"(%x, %minusOne) : (i64, i64) -> i64
9+
%masked = "llvm.and"(%x, %minusOne) : (i64, i64) -> i64
10+
%shifted = "llvm.shl"(%x, %one) : (i64, i64) -> i64
11+
%truncatedOne = "llvm.mlir.constant"() <{value = 4294967297 : i64}> : () -> i32
12+
%sum32 = "llvm.add"(%y, %truncatedOne) : (i32, i32) -> i32
13+
"func.return"(%sum, %masked, %shifted, %sum32) : (i64, i64, i64, i32) -> ()
14+
}) : () -> ()
15+
}) : () -> ()
16+
17+
// CHECK-LABEL: func.func @binop_constants
18+
// CHECK: "riscv.addi"({{.*}}) <{"value" = -1 : i64}>
19+
// CHECK: "riscv.andi"({{.*}}) <{"value" = -1 : i64}>
20+
// CHECK: "riscv.slli"({{.*}}) <{"value" = 1 : i64}>
21+
// CHECK: "riscv.addiw"({{.*}}) <{"value" = 1 : i64}>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// RUN: veir-opt %s -p=riscv-combine | filecheck %s
2+
3+
// 1 * 255 is defined unsigned, but 0 - 1 underflows. Multiplication by all-ones
4+
// can become negation only if nuw is dropped. Signed negation preserves nsw.
5+
// overflowFlags: 1 = nsw (no signed wrap), 2 = nuw (no unsigned wrap), 3 = both.
6+
"builtin.module"() ({
7+
"func.func"() <{sym_name = "mul_neg_one", function_type = (i8) -> (i8, i8, i8, i8)}> ({
8+
^bb0(%x: i8):
9+
%unsigned = "llvm.mlir.constant"() <{value = 255 : i8}> : () -> i8
10+
%signed = "llvm.mlir.constant"() <{value = -1 : i8}> : () -> i8
11+
%a = "llvm.mul"(%x, %unsigned) <{overflowFlags = 2 : i32}> : (i8, i8) -> i8
12+
%b = "llvm.mul"(%x, %signed) <{overflowFlags = 2 : i32}> : (i8, i8) -> i8
13+
%c = "llvm.mul"(%x, %signed) <{overflowFlags = 1 : i32}> : (i8, i8) -> i8
14+
%d = "llvm.mul"(%x, %signed) <{overflowFlags = 3 : i32}> : (i8, i8) -> i8
15+
"func.return"(%a, %b, %c, %d) : (i8, i8, i8, i8) -> ()
16+
}) : () -> ()
17+
}) : () -> ()
18+
19+
// CHECK-LABEL: func.func @mul_neg_one
20+
// CHECK-SAME: (%[[X:.*]]: i8)
21+
// CHECK-NEXT: %[[Z0:.*]] = "llvm.mlir.constant"() <{"value" = 0 : i8}>
22+
// CHECK-NEXT: %[[A:.*]] = "llvm.sub"(%[[Z0]], %[[X]]) : (i8, i8) -> i8
23+
// CHECK-NEXT: %[[Z1:.*]] = "llvm.mlir.constant"() <{"value" = 0 : i8}>
24+
// CHECK-NEXT: %[[B:.*]] = "llvm.sub"(%[[Z1]], %[[X]]) : (i8, i8) -> i8
25+
// CHECK-NEXT: %[[Z2:.*]] = "llvm.mlir.constant"() <{"value" = 0 : i8}>
26+
// CHECK-NEXT: %[[C:.*]] = "llvm.sub"(%[[Z2]], %[[X]]) <{"overflowFlags" = 1 : i32}> : (i8, i8) -> i8
27+
// CHECK-NEXT: %[[Z3:.*]] = "llvm.mlir.constant"() <{"value" = 0 : i8}>
28+
// CHECK-NEXT: %[[D:.*]] = "llvm.sub"(%[[Z3]], %[[X]]) <{"overflowFlags" = 1 : i32}> : (i8, i8) -> i8
29+
// CHECK-NEXT: "func.return"(%[[A]], %[[B]], %[[C]], %[[D]]) : (i8, i8, i8, i8) -> ()
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// RUN: veir-opt %s -p=riscv-combine | filecheck %s
2+
3+
// A boolean result needs the condition or its inverse, without an extension.
4+
"builtin.module"() ({
5+
"func.func"() <{sym_name = "select_i1", function_type = (i1) -> (i1, i1)}> ({
6+
^bb0(%cond: i1):
7+
%one = "llvm.mlir.constant"() <{value = 1 : i1}> : () -> i1
8+
%minusOne = "llvm.mlir.constant"() <{value = -1 : i1}> : () -> i1
9+
%zero = "llvm.mlir.constant"() <{value = 0 : i1}> : () -> i1
10+
%a = "llvm.select"(%cond, %one, %zero) : (i1, i1, i1) -> i1
11+
%b = "llvm.select"(%cond, %zero, %minusOne) : (i1, i1, i1) -> i1
12+
"func.return"(%a, %b) : (i1, i1) -> ()
13+
}) : () -> ()
14+
}) : () -> ()
15+
16+
// CHECK-LABEL: func.func @select_i1
17+
// CHECK-SAME: (%[[COND:.*]]: i1)
18+
// CHECK-NEXT: %[[ONE:.*]] = "llvm.mlir.constant"() <{"value" = -1 : i1}> : () -> i1
19+
// CHECK-NEXT: %[[NOT:.*]] = "llvm.xor"(%[[COND]], %[[ONE]]) : (i1, i1) -> i1
20+
// CHECK-NEXT: "func.return"(%[[COND]], %[[NOT]]) : (i1, i1) -> ()

Veir/Passes/InstCombine.lean

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,7 @@ def mulIOneToX_local (ctx : WfIRContext OpCode) (op : OperationPtr) :
5757
Option (WfIRContext OpCode × Option (Array OperationPtr × Array ValuePtr)) := do
5858
let some (lhs, rhs, _) := matchMuli op ctx.raw
5959
| return (ctx, none)
60-
let some cst := matchConstantIntVal rhs ctx.raw
61-
| return (ctx, none)
62-
if cst ≠ 1 then
60+
if !isConstantOne rhs ctx.raw then
6361
return (ctx, none)
6462
some (ctx, some (#[], #[lhs]))
6563

Veir/Passes/Matching/LLVM/Basic.lean

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,21 +51,28 @@ def matchXori (op : OperationPtr) (ctx : IRContext OpCode) :
5151
let (op, _) ← matchOp op ctx (Llvm.xor) 2
5252
return (op[0]!, op[1]!)
5353

54+
/-- Match the raw integer attribute; use `matchConstantIntVal` for the result's value. -/
5455
def matchConstantIntOp (op : OperationPtr) (ctx : IRContext OpCode) :
5556
Option IntegerAttr := do
5657
let Llvm.mlir__constant := toDialect? Llvm (op.getOpType! ctx) | none
5758
let properties := op.getProperties! ctx Llvm.mlir__constant
5859
let .integer intAttr := properties.value | none
5960
return intAttr
6061

61-
/-- Match the raw integer attribute value of an LLVM constant, without adjusting
62-
it to the attribute or result width. -/
6362
def matchConstantIntVal (val : ValuePtr) (ctx : IRContext OpCode) :
6463
Option Int := do
6564
let .opResult opResultPtr := val | none
6665
let op := opResultPtr.op
6766
let attr ← matchConstantIntOp op ctx
68-
return attr.value
67+
let .integerType type := (val.getType! ctx).val | none
68+
return (BitVec.ofInt type.bitwidth (decodeLLVMIntegerConstant attr)).toInt
69+
70+
/-- Recognize the one bit pattern, including i1 true whose signed value is -1. -/
71+
def isConstantOne (val : ValuePtr) (ctx : IRContext OpCode) : Bool :=
72+
match matchConstantIntVal val ctx, (val.getType! ctx).val with
73+
| some value, .integerType type =>
74+
(BitVec.ofInt type.bitwidth value).toNat == 1
75+
| _, _ => false
6976

7077
/-- Match a constant integer with value zero, returning `val` itself. -/
7178
def matchConstantZero (val : ValuePtr) (ctx : IRContext OpCode) : Option ValuePtr := do

Veir/Passes/Matching/LLVM/Lemmas.lean

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,10 @@ theorem matchConstantIntOp_implies {op : OperationPtr} {ctx : IRContext OpCode}
110110
/-- What matching a constant integer value (via `matchConstantIntVal`) syntactically guarantees. -/
111111
theorem matchConstantIntVal_implies {val : ValuePtr} {ctx : IRContext OpCode} {value} :
112112
matchConstantIntVal val ctx = some value →
113-
∃ opResultPtr intAttr, val = .opResult opResultPtr ∧
114-
matchConstantIntOp opResultPtr.op ctx = some intAttr ∧
115-
intAttr.value = value := by
113+
∃ opResultPtr attr type, val = .opResult opResultPtr ∧
114+
matchConstantIntOp opResultPtr.op ctx = some attr ∧
115+
(val.getType! ctx).val = .integerType type ∧
116+
value = (BitVec.ofInt type.bitwidth (decodeLLVMIntegerConstant attr)).toInt := by
116117
intro hmatch
117118
simp only [matchConstantIntVal, bind, Option.bind, pure] at hmatch
118119
grind

Veir/Passes/RISCVCombines/Combine.lean

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,7 @@ def select_same_val_self (rewriter : PatternRewriter OpCode) (op : OperationPtr)
6363
def select_constant_cmp_true_local (ctx : WfIRContext OpCode) (op : OperationPtr) :
6464
Option (WfIRContext OpCode × Option (Array OperationPtr × Array ValuePtr)) := do
6565
let some (cond, tval, _fval) := matchSelect op ctx.raw | return (ctx, none)
66-
let some cst := matchConstantIntVal cond ctx.raw | return (ctx, none)
67-
if cst ≠ 1 then return (ctx, none)
66+
if !isConstantOne cond ctx.raw then return (ctx, none)
6867
some (ctx, some (#[], #[tval]))
6968

7069
def select_constant_cmp_true (rewriter : PatternRewriter OpCode) (op : OperationPtr)
@@ -958,6 +957,9 @@ def select_neg1_0_local (ctx : WfIRContext OpCode) (op : OperationPtr) :
958957
if ct ≠ -1 then return (ctx, none)
959958
let some cf := matchConstantIntVal fv ctx.raw | return (ctx, none)
960959
if cf ≠ 0 then return (ctx, none)
960+
-- At i1, -1 is true and no extension is needed.
961+
if (op.getResult 0 : ValuePtr).getType! ctx.raw = IntegerType.mk 1 then
962+
return (ctx, some (#[], #[cond]))
961963
let (ctx, newOp) ← WfRewriter.createOp! ctx Llvm.sext #[(op.getResult 0 : ValuePtr).getType! ctx.raw] #[cond]
962964
#[] #[] () none
963965
some (ctx, some (#[newOp], #[newOp.getResult 0]))
@@ -1002,6 +1004,8 @@ def select_0_neg1_local (ctx : WfIRContext OpCode) (op : OperationPtr) :
10021004
#[] #[] m1 none
10031005
let (ctx, ncond) ← WfRewriter.createOp! ctx Llvm.xor #[cond.getType! ctx.raw] #[cond, (c1.getResult 0)]
10041006
#[] #[] () none
1007+
if (op.getResult 0 : ValuePtr).getType! ctx.raw = IntegerType.mk 1 then
1008+
return (ctx, some (#[c1, ncond], #[ncond.getResult 0]))
10051009
let (ctx, newOp) ← WfRewriter.createOp! ctx Llvm.sext #[(op.getResult 0 : ValuePtr).getType! ctx.raw] #[(ncond.getResult 0)]
10061010
#[] #[] () none
10071011
some (ctx, some (#[c1, ncond, newOp], #[newOp.getResult 0]))

Veir/Passes/RISCVCombines/MIRCombinesVeir.lean

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,7 @@ def right_identity_zero_6 (rewriter : PatternRewriter OpCode) (op : OperationPtr
107107
def right_identity_one_int_local (ctx : WfIRContext OpCode) (op : OperationPtr) :
108108
Option (WfIRContext OpCode × Option (Array OperationPtr × Array ValuePtr)) := do
109109
let some (x, rhs, _props) := matchMul op ctx.raw | return (ctx, none)
110-
let some cst := matchConstantIntVal rhs ctx.raw | return (ctx, none)
111-
if cst ≠ 1 then return (ctx, none)
110+
if !isConstantOne rhs ctx.raw then return (ctx, none)
112111
some (ctx, some (#[], #[x]))
113112

114113
def right_identity_one_int (rewriter : PatternRewriter OpCode) (op : OperationPtr)
@@ -182,15 +181,16 @@ def binop_right_to_zero (rewriter : PatternRewriter OpCode) (op : OperationPtr)
182181

183182
def mul_by_neg_one_local (ctx : WfIRContext OpCode) (op : OperationPtr) :
184183
Option (WfIRContext OpCode × Option (Array OperationPtr × Array ValuePtr)) := do
185-
let some (x, rhs, _props) := matchMul op ctx.raw | return (ctx, none)
184+
let some (x, rhs, props) := matchMul op ctx.raw | return (ctx, none)
186185
let some cst := matchConstantIntVal rhs ctx.raw | return (ctx, none)
187186
if cst ≠ -1 then return (ctx, none)
188187
let .integerType ctype := (x.getType! ctx.raw).val | return (ctx, none)
189188
let cstOpProp := LLVMConstantProperties.mk (.integer (IntegerAttr.mk (0) ctype))
190189
let (ctx, cstOp) ← WfRewriter.createOp! ctx Llvm.mlir__constant #[x.getType! ctx.raw] #[]
191190
#[] #[] cstOpProp none
191+
-- Multiplying 1 by all-ones does not overflow unsigned, but 0 - 1 does.
192192
let (ctx, newOp) ← WfRewriter.createOp! ctx Llvm.sub #[x.getType! ctx.raw] #[(cstOp.getResult 0), x]
193-
#[] #[] _props none
193+
#[] #[] { props with nuw := false } none
194194
some (ctx, some (#[cstOp, newOp], #[newOp.getResult 0]))
195195

196196
def mul_by_neg_one (rewriter : PatternRewriter OpCode) (op : OperationPtr)

0 commit comments

Comments
 (0)