Skip to content

Commit 26344a0

Browse files
authored
feat: checked-arithmetic helper emission (#1993 emission half) (#2028)
* Support dynamic internal helper args * feat: allow runtime scalar custom error args * feat: add source-shaped dynamic event proof surface * Emit checked arithmetic helpers * feat(Codegen): factor solidityPanicPayload for Panic(uint256) emission (#1999) (#2030) Extract the Panic(uint256) revert payload (selector 0x4e487b71 + one ABI word holding the panic code) into a reusable solidityPanicPayload builder so panic emission is compiler-owned and reusable for checked-arithmetic paths; assert the encoded code words (mstore(4, 17/18)) in the arithmetic-panic smoke test. * feat: self-delegate multicall helper (#1997) (#2029) * feat: add self-delegate multicall helper * fix(verity): multicall offset-wrap bound check + documented trusted ECM (#2029) * fix(merge): drop duplicate internal-call helpers from Compile.lean The #1997 refactor moved findInternalFunctionForCall?, compileInternalCallArg, compileInternalCallArgsWithParams, and compileInternalCallArgs into ExpressionCompile.lean. The merge of origin/main kept the stale copies in Compile.lean too, producing "already been declared" errors. Remove the stale copies; ExpressionCompile.lean is the single source. * fix(proofs): set noCheckedArithmetic on scalarEventSmoke supported spec The merge brought in main's scalar-events supported-spec smoke test, which the SupportedSpecSurfaceWithScalarEvents struct now requires to discharge the noCheckedArithmetic obligation added in this PR. Prove it by simp over the spec definitions. * fix(merge): restore CompilationModelFeatureTest content dropped by botched origin/main merge The eb9fadb merge of origin/main into task/1993a-checked-arith dropped ~3385 lines of origin/main test content and left the namespace structure broken (InternalHelperDynamicArgs never closed), causing the compiler-regressions CI job to fail with a parse error. Restore origin/main's full feature-test file and re-graft task/1993a's unique checked-arithmetic helper assertions on top, so the union of both sides' test cases is preserved.
1 parent d611daa commit 26344a0

20 files changed

Lines changed: 745 additions & 16 deletions

AXIOMS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ call-buffer-disjoint-from-heap theorem live at `Verity.EVM.Layout`.
245245
| `Calls.withReturn` | `external_call_abi_interface` | Target contract function matches declared selector and ABI |
246246
| `Calls.callWithValue` / `Calls.callWithValueBytes` | `generic_call_with_value_interface` | Target accepts caller-provided calldata and ETH value; failures bubble returndata |
247247
| `Calls.bubblingValueCall` / `Calls.bubblingValueCallNoOutput` | `generic_low_level_value_call_interface` | Generic low-level `call` mechanics are emitted; calldata and successful returndata meaning remain package assumptions |
248+
| `Calls.selfDelegateMulticallBytes` | `self_delegate_multicall_bytes_revert_bubbling` | Trusted ECM boundary for Solidity-style `multicall(bytes[])`: generated Yul is audited to reject malformed/wrapping offsets, self-`delegatecall` each calldata payload, and bubble revert returndata exactly; full non-empty multicall semantics remain assumed |
248249
| `Hashing.abiEncodeStaticWords` | `keccak256_memory_slice_matches_evm`, `abi_standard_static_word_layout` | Static ABI words are laid out contiguously before Keccak |
249250
| `Hashing.abiEncodePackedWords` / `Hashing.abiEncodePacked` | `keccak256_memory_slice_matches_evm`, `abi_packed_static_word_layout` | Static packed words are laid out contiguously before Keccak |
250251
| `Hashing.abiEncodeStaticArray` | `keccak256_memory_slice_matches_evm`, `abi_standard_dynamic_array_static_element_layout` | Single dynamic-array ABI encoding with static-width elements is laid out before Keccak |

Compiler/Codegen.lean

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,13 @@ private def patchBackend : Compiler.CodegenCommon.PatchBackend :=
5050
patchReport := mergedPatchReport } }
5151

5252
def emitYulWithOptions (contract : IRContract) (options : YulEmitOptions) : YulObject :=
53-
Compiler.CodegenCommon.emitYulWithOptions patchBackend contract options
53+
Compiler.CodegenCommon.optimizeCheckedArithmeticObjectIfAvailable contract
54+
(Compiler.CodegenCommon.emitYulWithOptions patchBackend contract options)
5455

5556
def emitYulWithOptionsReport (contract : IRContract) (options : YulEmitOptions) :
5657
YulObject × Yul.PatchPassReport :=
57-
Compiler.CodegenCommon.emitYulWithOptionsReport patchBackend contract options
58+
let report := Compiler.CodegenCommon.emitYulWithOptionsReport patchBackend contract options
59+
(Compiler.CodegenCommon.optimizeCheckedArithmeticObjectIfAvailable contract report.1, report.2)
5860

5961
/-- Regression guard:
6062
expression/statement/block patching remains runtime-scoped (deploy is unchanged),

Compiler/CodegenCommon.lean

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import Compiler.Constants
2+
import Compiler.CompilationModel.AbiHelpers
3+
import Compiler.CompilationModel.DynamicData
24
import Compiler.IR
35
import Compiler.Yul.PrettyPrint
46
import Compiler.Yul.PatchFramework
@@ -7,6 +9,7 @@ import Verity.Core.Intrinsics
79
namespace Compiler.CodegenCommon
810

911
open Compiler.Constants (selectorShift)
12+
open Compiler.CompilationModel
1013
open Compiler.Yul
1114

1215
inductive BackendProfile where
@@ -144,6 +147,133 @@ def buildSwitch
144147
[YulStmt.switch selectorExpr cases (some defaultCase)]
145148
]
146149

150+
private def yulExprSame (a b : YulExpr) : Bool :=
151+
toString (repr a) == toString (repr b)
152+
153+
private def yulStmtListSame (a b : List YulStmt) : Bool :=
154+
toString (repr a) == toString (repr b)
155+
156+
private def isRevertMessageBody (message : String) (body : List YulStmt) : Bool :=
157+
yulStmtListSame body (revertWithMessage message)
158+
159+
private def checkedAddFailCond (a b : YulExpr) : YulExpr :=
160+
YulExpr.call "lt" [YulExpr.call "add" [a, b], a]
161+
162+
private def checkedSubFailCond (a b : YulExpr) : YulExpr :=
163+
YulExpr.call "lt" [a, b]
164+
165+
private def checkedMulFailCond (a b : YulExpr) : YulExpr :=
166+
YulExpr.call "iszero" [
167+
YulExpr.call "or" [
168+
YulExpr.call "iszero" [YulExpr.call "iszero" [
169+
YulExpr.call "eq" [b, YulExpr.lit 0]
170+
]],
171+
YulExpr.call "iszero" [YulExpr.call "iszero" [
172+
YulExpr.call "eq" [
173+
YulExpr.call "div" [YulExpr.call "mul" [a, b], b],
174+
a
175+
]
176+
]]
177+
]
178+
]
179+
180+
private def checkedDivFailCond (b : YulExpr) : YulExpr :=
181+
YulExpr.call "iszero" [YulExpr.call "iszero" [
182+
YulExpr.call "eq" [b, YulExpr.lit 0]
183+
]]
184+
185+
private def checkedArithmeticReplacement? (prev cur : YulStmt) : Option YulStmt :=
186+
match prev, cur with
187+
| YulStmt.if_ cond body, YulStmt.let_ name (YulExpr.call "add" [a, b]) =>
188+
if yulExprSame cond (checkedAddFailCond a b) &&
189+
isRevertMessageBody "Panic(0x11): arithmetic overflow" body then
190+
some (YulStmt.let_ name (YulExpr.call checkedAddUint256HelperName [a, b]))
191+
else
192+
none
193+
| YulStmt.if_ cond body, YulStmt.let_ name (YulExpr.call "sub" [a, b]) =>
194+
if yulExprSame cond (checkedSubFailCond a b) &&
195+
isRevertMessageBody "Panic(0x11): arithmetic underflow" body then
196+
some (YulStmt.let_ name (YulExpr.call checkedSubUint256HelperName [a, b]))
197+
else
198+
none
199+
| YulStmt.if_ cond body, YulStmt.let_ name (YulExpr.call "mul" [a, b]) =>
200+
if yulExprSame cond (checkedMulFailCond a b) &&
201+
isRevertMessageBody "Panic(0x11): arithmetic overflow" body then
202+
some (YulStmt.let_ name (YulExpr.call checkedMulUint256HelperName [a, b]))
203+
else
204+
none
205+
| YulStmt.if_ cond body, YulStmt.let_ name (YulExpr.call "div" [a, b]) =>
206+
if yulExprSame cond (checkedDivFailCond b) &&
207+
isRevertMessageBody "Panic(0x12): division by zero" body then
208+
some (YulStmt.let_ name (YulExpr.call checkedDivUint256HelperName [a, b]))
209+
else
210+
none
211+
| _, _ => none
212+
213+
mutual
214+
215+
private def optimizeCheckedArithmeticStmtFuel : Nat → YulStmt → YulStmt
216+
| 0, stmt => stmt
217+
| fuel + 1, YulStmt.if_ cond body =>
218+
YulStmt.if_ cond (optimizeCheckedArithmeticStmtsFuel fuel body)
219+
| fuel + 1, YulStmt.for_ init cond post body =>
220+
YulStmt.for_
221+
(optimizeCheckedArithmeticStmtsFuel fuel init)
222+
cond
223+
(optimizeCheckedArithmeticStmtsFuel fuel post)
224+
(optimizeCheckedArithmeticStmtsFuel fuel body)
225+
| fuel + 1, YulStmt.switch expr cases default =>
226+
YulStmt.switch expr
227+
(cases.map fun (tag, body) => (tag, optimizeCheckedArithmeticStmtsFuel fuel body))
228+
(default.map (optimizeCheckedArithmeticStmtsFuel fuel))
229+
| fuel + 1, YulStmt.block stmts =>
230+
YulStmt.block (optimizeCheckedArithmeticStmtsFuel fuel stmts)
231+
| fuel + 1, YulStmt.funcDef name params rets body =>
232+
YulStmt.funcDef name params rets (optimizeCheckedArithmeticStmtsFuel fuel body)
233+
| _fuel + 1, stmt => stmt
234+
235+
private def optimizeCheckedArithmeticStmtsFuel : Nat → List YulStmt → List YulStmt
236+
| 0, stmts => stmts
237+
| _fuel + 1, [] => []
238+
| fuel + 1, [stmt] => [optimizeCheckedArithmeticStmtFuel fuel stmt]
239+
| fuel + 1, prev :: cur :: rest =>
240+
let prev' := optimizeCheckedArithmeticStmtFuel fuel prev
241+
let cur' := optimizeCheckedArithmeticStmtFuel fuel cur
242+
match checkedArithmeticReplacement? prev' cur' with
243+
| some replacement => replacement :: optimizeCheckedArithmeticStmtsFuel fuel rest
244+
| none => prev' :: optimizeCheckedArithmeticStmtsFuel fuel (cur :: rest)
245+
246+
end
247+
248+
private def yulStmtListFuel (stmts : List YulStmt) : Nat :=
249+
(toString (repr stmts)).length + 1
250+
251+
private def optimizeCheckedArithmeticStmts (stmts : List YulStmt) : List YulStmt :=
252+
optimizeCheckedArithmeticStmtsFuel (yulStmtListFuel stmts) stmts
253+
254+
private def internalHelperNamed (name : String) : YulStmt → Bool
255+
| YulStmt.funcDef fnName _ _ _ => fnName == name
256+
| _ => false
257+
258+
private def hasCheckedArithmeticHelpers (contract : IRContract) : Bool :=
259+
contract.internalFunctions.any (internalHelperNamed checkedAddUint256HelperName) &&
260+
contract.internalFunctions.any (internalHelperNamed checkedSubUint256HelperName) &&
261+
contract.internalFunctions.any (internalHelperNamed checkedMulUint256HelperName) &&
262+
contract.internalFunctions.any (internalHelperNamed checkedDivUint256HelperName)
263+
264+
private def optimizeCheckedArithmeticIfAvailable (contract : IRContract) (stmts : List YulStmt) :
265+
List YulStmt :=
266+
if hasCheckedArithmeticHelpers contract then
267+
optimizeCheckedArithmeticStmts stmts
268+
else
269+
stmts
270+
271+
def optimizeCheckedArithmeticObjectIfAvailable (contract : IRContract) (object : YulObject) :
272+
YulObject :=
273+
{ object with
274+
deployCode := optimizeCheckedArithmeticIfAvailable contract object.deployCode
275+
runtimeCode := optimizeCheckedArithmeticIfAvailable contract object.runtimeCode }
276+
147277
def runtimeCode (contract : IRContract) : List YulStmt :=
148278
let mapping := if contract.usesMapping then [mappingSlotFuncAt 0] else []
149279
let internals := contract.internalFunctions

Compiler/CompilationModel/Dispatch.lean

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,11 +515,13 @@ def validateCompileInputs (spec : CompilationModel) (selectors : List Nat)
515515
let mulDiv512HelpersRequired := contractUsesMulDiv512 spec
516516
let storageArrayHelpersRequired := contractUsesStorageArrayElement spec
517517
let dynamicBytesEqHelpersRequired := contractUsesDynamicBytesEq spec
518+
let checkedArithmeticHelpersRequired := contractUsesCheckedArithmetic spec
518519
match firstReservedExternalCollision
519520
spec mappingHelpersRequired arrayHelpersRequired arrayElementWordHelpersRequired
520521
paramDynamicHeadWordHelpersRequired
521522
mulDiv512HelpersRequired
522-
storageArrayHelpersRequired dynamicBytesEqHelpersRequired with
523+
storageArrayHelpersRequired dynamicBytesEqHelpersRequired
524+
checkedArithmeticHelpersRequired with
523525
| some name =>
524526
if name.startsWith internalFunctionPrefix then
525527
throw s!"Compilation error: external declaration '{name}' uses reserved prefix '{internalFunctionPrefix}' ({issue756Ref})."
@@ -562,6 +564,7 @@ def compileValidatedCore (spec : CompilationModel) (selectors : List Nat) : Exce
562564
let mulDiv512HelpersRequired := contractUsesMulDiv512 spec
563565
let storageArrayHelpersRequired := contractUsesStorageArrayElement spec
564566
let dynamicBytesEqHelpersRequired := contractUsesDynamicBytesEq spec
567+
let checkedArithmeticHelpersRequired := contractUsesCheckedArithmetic spec
565568
let fallbackSpec ← pickUniqueFunctionByName "fallback" spec.functions
566569
let receiveSpec ← pickUniqueFunctionByName "receive" spec.functions
567570
let functions ← (externalFns.zip selectors).mapM fun entry =>
@@ -627,6 +630,17 @@ def compileValidatedCore (spec : CompilationModel) (selectors : List Nat) : Exce
627630
[dynamicBytesEqCalldataHelper, dynamicBytesEqMemoryHelper]
628631
else
629632
[]
633+
let checkedArithmeticHelpers :=
634+
if checkedArithmeticHelpersRequired then
635+
[ panicError0x11Helper
636+
, panicError0x12Helper
637+
, checkedAddUint256Helper
638+
, checkedSubUint256Helper
639+
, checkedMulUint256Helper
640+
, checkedDivUint256Helper
641+
]
642+
else
643+
[]
630644
let fallbackEntrypoint ← fallbackSpec.mapM (compileSpecialEntrypoint fields spec.events spec.errors spec.adtTypes internalFns)
631645
let receiveEntrypoint ← receiveSpec.mapM (compileSpecialEntrypoint fields spec.events spec.errors spec.adtTypes internalFns)
632646
return {
@@ -637,7 +651,7 @@ def compileValidatedCore (spec : CompilationModel) (selectors : List Nat) : Exce
637651
fallbackEntrypoint := fallbackEntrypoint
638652
receiveEntrypoint := receiveEntrypoint
639653
usesMapping := mappingHelpersRequired
640-
internalFunctions := arrayElementHelpers ++ storageArrayElementHelpers ++ dynamicBytesEqHelpers ++ internalFuncDefs
654+
internalFunctions := arrayElementHelpers ++ storageArrayElementHelpers ++ dynamicBytesEqHelpers ++ checkedArithmeticHelpers ++ internalFuncDefs
641655
}
642656

643657
/-- Compile a `CompilationModel` to an `IRContract`.

Compiler/CompilationModel/DynamicData.lean

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,87 @@ def dynamicBytesEqCalldataHelperName : String :=
9999
def dynamicBytesEqMemoryHelperName : String :=
100100
"__verity_dynamic_bytes_eq_memory"
101101

102+
def checkedAddUint256HelperName : String :=
103+
"checked_add_t_uint256"
104+
105+
def checkedSubUint256HelperName : String :=
106+
"checked_sub_t_uint256"
107+
108+
def checkedMulUint256HelperName : String :=
109+
"checked_mul_t_uint256"
110+
111+
def checkedDivUint256HelperName : String :=
112+
"checked_div_t_uint256"
113+
114+
def panicError0x11HelperName : String :=
115+
"panic_error_0x11"
116+
117+
def panicError0x12HelperName : String :=
118+
"panic_error_0x12"
119+
120+
/-- ABI payload for Solidity's built-in `Panic(uint256)` error.
121+
122+
The payload is exactly 36 bytes:
123+
4-byte selector `0x4e487b71` followed by one ABI word containing `code`. -/
124+
def solidityPanicPayload (code : Nat) : List YulStmt :=
125+
[
126+
YulStmt.expr (YulExpr.call "mstore" [
127+
YulExpr.lit 0,
128+
YulExpr.call "shl" [YulExpr.lit 224, YulExpr.hex 0x4e487b71]
129+
]),
130+
YulStmt.expr (YulExpr.call "mstore" [YulExpr.lit 4, YulExpr.lit code]),
131+
YulStmt.expr (YulExpr.call "revert" [YulExpr.lit 0, YulExpr.lit 36])
132+
]
133+
134+
def panicErrorHelper (helperName : String) (code : Nat) : YulStmt :=
135+
YulStmt.funcDef helperName [] [] (solidityPanicPayload code)
136+
137+
def panicError0x11Helper : YulStmt :=
138+
panicErrorHelper panicError0x11HelperName 0x11
139+
140+
def panicError0x12Helper : YulStmt :=
141+
panicErrorHelper panicError0x12HelperName 0x12
142+
143+
def checkedAddUint256Helper : YulStmt :=
144+
YulStmt.funcDef checkedAddUint256HelperName ["x", "y"] ["sum"] [
145+
YulStmt.assign "sum" (YulExpr.call "add" [YulExpr.ident "x", YulExpr.ident "y"]),
146+
YulStmt.if_ (YulExpr.call "gt" [YulExpr.ident "x", YulExpr.ident "sum"]) [
147+
YulStmt.expr (YulExpr.call panicError0x11HelperName [])
148+
]
149+
]
150+
151+
def checkedSubUint256Helper : YulStmt :=
152+
YulStmt.funcDef checkedSubUint256HelperName ["x", "y"] ["diff"] [
153+
YulStmt.if_ (YulExpr.call "gt" [YulExpr.ident "y", YulExpr.ident "x"]) [
154+
YulStmt.expr (YulExpr.call panicError0x11HelperName [])
155+
],
156+
YulStmt.assign "diff" (YulExpr.call "sub" [YulExpr.ident "x", YulExpr.ident "y"])
157+
]
158+
159+
def checkedMulUint256Helper : YulStmt :=
160+
YulStmt.funcDef checkedMulUint256HelperName ["x", "y"] ["product"] [
161+
YulStmt.assign "product" (YulExpr.call "mul" [YulExpr.ident "x", YulExpr.ident "y"]),
162+
YulStmt.if_ (YulExpr.call "iszero" [
163+
YulExpr.call "or" [
164+
YulExpr.call "iszero" [YulExpr.ident "x"],
165+
YulExpr.call "eq" [
166+
YulExpr.call "div" [YulExpr.ident "product", YulExpr.ident "x"],
167+
YulExpr.ident "y"
168+
]
169+
]
170+
]) [
171+
YulStmt.expr (YulExpr.call panicError0x11HelperName [])
172+
]
173+
]
174+
175+
def checkedDivUint256Helper : YulStmt :=
176+
YulStmt.funcDef checkedDivUint256HelperName ["x", "y"] ["quotient"] [
177+
YulStmt.if_ (YulExpr.call "iszero" [YulExpr.ident "y"]) [
178+
YulStmt.expr (YulExpr.call panicError0x12HelperName [])
179+
],
180+
YulStmt.assign "quotient" (YulExpr.call "div" [YulExpr.ident "x", YulExpr.ident "y"])
181+
]
182+
102183
private def checkedArrayElementHelper (helperName loadOp : String) : YulStmt :=
103184
YulStmt.funcDef helperName ["data_offset", "length", "index"] ["word"] [
104185
YulStmt.if_ (YulExpr.call "iszero" [

Compiler/CompilationModel/TrustSurface.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,7 @@ private def boundaryClassFromModule (mod : ECM.ExternalCallModule) : String :=
789789
| "maxDeposit" | "maxMint" | "maxWithdraw" | "maxRedeem" | "deposit" =>
790790
"tokenModel"
791791
| "externalCallWithReturn" | "externalCallNoReturn" | "callWithValue" | "callWithValueBytes"
792-
| "bubblingValueCall" | "bubblingValueCallNoOutput" =>
792+
| "bubblingValueCall" | "bubblingValueCallNoOutput" | "selfDelegateMulticallBytes" =>
793793
"abiBoundary"
794794
| "create2Deploy" | "sstore2ReadCode" =>
795795
"storageLayoutAssumption"

Compiler/CompilationModel/UsageAnalysis.lean

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,51 @@ termination_by bs => sizeOf bs
6262
decreasing_by all_goals simp_wf; all_goals omega
6363
end
6464

65+
private def isCheckedArithmeticPanicMessage (message : String) : Bool :=
66+
message == "Panic(0x11): arithmetic overflow" ||
67+
message == "Panic(0x11): arithmetic underflow" ||
68+
message == "Panic(0x12): division by zero"
69+
70+
mutual
71+
72+
def stmtMayUseCheckedArithmetic : Stmt → Bool
73+
| Stmt.require _ message =>
74+
isCheckedArithmeticPanicMessage message
75+
| Stmt.ite _ thenBranch elseBranch =>
76+
stmtListMayUseCheckedArithmetic thenBranch ||
77+
stmtListMayUseCheckedArithmetic elseBranch
78+
| Stmt.forEach _ _ body =>
79+
stmtListMayUseCheckedArithmetic body
80+
| Stmt.unsafeBlock _ body =>
81+
stmtListMayUseCheckedArithmetic body
82+
| Stmt.matchAdt _ _ branches =>
83+
matchBranchesMayUseCheckedArithmetic branches
84+
| _ => false
85+
termination_by s => sizeOf s
86+
decreasing_by all_goals simp_wf; all_goals omega
87+
88+
def stmtListMayUseCheckedArithmetic : List Stmt → Bool
89+
| [] => false
90+
| stmt :: rest =>
91+
stmtMayUseCheckedArithmetic stmt ||
92+
stmtListMayUseCheckedArithmetic rest
93+
termination_by stmts => sizeOf stmts
94+
decreasing_by all_goals simp_wf; all_goals omega
95+
96+
def matchBranchesMayUseCheckedArithmetic : List (String × List String × List Stmt) → Bool
97+
| [] => false
98+
| (_, _, body) :: rest =>
99+
stmtListMayUseCheckedArithmetic body ||
100+
matchBranchesMayUseCheckedArithmetic rest
101+
termination_by branches => sizeOf branches
102+
decreasing_by all_goals simp_wf; all_goals omega
103+
104+
end
105+
106+
def contractUsesCheckedArithmetic (spec : CompilationModel) : Bool :=
107+
(spec.constructor.map (fun ctor => stmtListMayUseCheckedArithmetic ctor.body) |>.getD false) ||
108+
spec.functions.any (fun fn => stmtListMayUseCheckedArithmetic fn.body)
109+
65110
mutual
66111
def exprUsesArrayElementKind (includePlain includeWord : Bool) : Expr → Bool
67112
| Expr.arrayElement _ index | Expr.memoryArrayElement _ index =>

0 commit comments

Comments
 (0)