Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
3624be3
docs: add sorry reduction plan for pass 5
claude Mar 26, 2026
e4c3cd4
prove stmtListGenericCore_of_requireClausesOnly and 2 return variants
claude Mar 26, 2026
0844803
chore: auto-refresh derived artifacts
github-actions[bot] Mar 26, 2026
560a760
fix(collectStmtNames): remove field names from storage-mutating cases
claude Mar 26, 2026
394adb0
prove compiledStmtStep_setMappingUint_singleSlot_of_slotSafety_preserves
claude Mar 26, 2026
3fbf0af
chore: auto-refresh derived artifacts
github-actions[bot] Mar 26, 2026
f9dafe7
prove compiledStmtStep_setMapping_singleSlot_of_slotSafety_preserves
claude Mar 26, 2026
22bd74f
prove compiledStmtStep + cascade for setMappingUint and setMapping
claude Mar 26, 2026
0c4d840
chore: auto-refresh derived artifacts
github-actions[bot] Mar 26, 2026
5cefbc4
prove compiledStmtStep + cascade for mstore and tstore (39→37 sorries)
claude Mar 26, 2026
647fa0d
chore: auto-refresh derived artifacts
github-actions[bot] Mar 26, 2026
606771d
add isMapping_false_of_findFieldWithResolvedSlot_address lemma
claude Mar 26, 2026
2adf22f
prove setMappingChain + setMapping2 preserves + cascades (37→33 sorries)
claude Mar 26, 2026
a4d79b2
prove compiledStmtStep_setStorage_singleSlot + singleton cascade (33→…
claude Mar 26, 2026
48aac16
chore: auto-refresh derived artifacts
github-actions[bot] Mar 26, 2026
7bda5f9
docs: update sorry reduction plan with pass 5 progress (44→32)
claude Mar 26, 2026
d393690
move 6 requireClausesThen*SetStorage cascade theorems after deps (32→…
claude Mar 26, 2026
acc683f
chore: auto-refresh derived artifacts
github-actions[bot] Mar 26, 2026
aa4cbd1
prove scope-independence of compileStmt/compileStmtList success (26→2…
claude Mar 26, 2026
07ff14c
prove generic induction theorems by removing inScopeNames indirection…
claude Mar 26, 2026
d47c51f
prove stmtListGenericCore_of_supportedStmtList_of_surface by inductio…
claude Mar 26, 2026
6c61c3d
prove setMappingWord preserves (wordOffset=0), activate singleton the…
claude Mar 26, 2026
1026015
Prove setStructMember_preserves (wordOffset=0) and activate singleton
claude Mar 26, 2026
926cabb
Prove setMapping2Word and setStructMember2 preserves (wordOffset=0), …
claude Mar 26, 2026
bfd7047
Prove helper-ir exec chain: step, extraFuel, raw, body theorem (14→13…
claude Mar 26, 2026
739fbd7
chore: auto-refresh derived artifacts
github-actions[bot] Mar 26, 2026
a4c2900
Uncomment bridge theorems and prove 3 downstream body-level sorries (…
claude Mar 26, 2026
04d38b0
chore: auto-refresh derived artifacts
github-actions[bot] Mar 26, 2026
a468849
docs: update sorry reduction plan to reflect current 10-sorry state
claude Mar 26, 2026
13fd9fa
fix: allowlist 6 long proofs from sorry-reduction pass 5 + add elan c…
Th0rgal Mar 26, 2026
66236a1
Add keccak axiom and prove 4 mapping slot wordOffset≠0 sorries (10→6)
claude Mar 26, 2026
5430d87
chore: auto-refresh derived artifacts
github-actions[bot] Mar 26, 2026
7dee40c
Prove aliasSlots TYPESIG_SORRY chain: 3 theorems (6→5 sorries)
claude Mar 26, 2026
c3b2ca5
chore: auto-refresh derived artifacts
github-actions[bot] Mar 26, 2026
d712394
docs: update sorry reduction plan to reflect 10→5 sorry state
claude Mar 27, 2026
08b717e
Add ExprCompileCore constructors for bitwise ops, prove sorry #1 (5→4…
claude Mar 27, 2026
90d2f6b
chore: auto-refresh derived artifacts
github-actions[bot] Mar 27, 2026
d52b735
docs: update sorry reduction plan to reflect 4-sorry state with detai…
claude Mar 27, 2026
6edf976
Uncomment 4 TYPESIG_SORRY blocks in FunctionBody.lean with fixed type…
claude Mar 27, 2026
5683a8d
Uncomment 6 tailExtraFuel TYPESIG_SORRY blocks in FunctionBody.lean w…
claude Mar 27, 2026
5dc508c
chore: auto-refresh derived artifacts
github-actions[bot] Mar 27, 2026
c3eafcd
Uncomment 2 TYPESIG_SORRY blocks in GenericInduction.lean: setStorage…
claude Mar 27, 2026
faaf81e
Remove 7 dead-code TYPESIG_SORRY blocks superseded by proved variants
claude Mar 27, 2026
b459fb3
chore: auto-refresh derived artifacts
github-actions[bot] Mar 27, 2026
d004f5c
Uncomment 2 TYPESIG_SORRY blocks + add helper lemma for ExceptMapping…
claude Mar 27, 2026
8a589c0
Prove compiledStmtStep_setMappingPackedWord compileOk + eliminate sor…
claude Mar 27, 2026
16e3af7
chore: auto-refresh derived artifacts
github-actions[bot] Mar 27, 2026
97b64b3
Update SORRY_REDUCTION_PLAN.md: 4 → 3 sorries (93% reduction)
claude Mar 27, 2026
7f290b0
docs: register 2 new mapping-slot axioms in AXIOMS.md (#1672)
Th0rgal Mar 27, 2026
53f1666
fix mapping-slot axiom sync and address write masking
Th0rgal Mar 27, 2026
d0178c1
chore: auto-refresh derived artifacts
github-actions[bot] Mar 27, 2026
4164973
Close packed mapping proof hole
claude Mar 27, 2026
c2f325a
proofs: remove remaining GenericInduction sorrys
claude Mar 27, 2026
4545f84
chore: auto-refresh derived artifacts
github-actions[bot] Mar 27, 2026
c046d67
Finish packed mapping preservation proof
claude Mar 27, 2026
fa39679
chore: auto-refresh derived artifacts
github-actions[bot] Mar 27, 2026
3702f08
fix: CI gates — update warning baseline, proof-length allowlist, and …
claude Mar 28, 2026
9299563
chore: restore docstrings from stray TYPESIG_SORRY comment blocks
claude Mar 28, 2026
22795c9
fix: address mask for packed storage writes and generalize isMapping …
claude Mar 29, 2026
569014a
feat: add shl/shr (shift-left/shift-right) to the proven fragment
claude Mar 29, 2026
2aecbae
chore: auto-refresh derived artifacts
github-actions[bot] Mar 29, 2026
59ce58e
feat: unlock bitAnd/bitOr/bitXor/bitNot in the proven fragment
claude Mar 29, 2026
ec89bf0
feat: add min/max to the proven fragment
claude Mar 29, 2026
d12a2bd
chore: auto-refresh derived artifacts
github-actions[bot] Mar 29, 2026
9e3c259
feat: add ite (ternary conditional) to the proven fragment
claude Mar 29, 2026
18b84e1
chore: auto-refresh derived artifacts
github-actions[bot] Mar 29, 2026
d56c9db
feat: add ceilDiv to the proven fragment
claude Mar 29, 2026
6080e60
chore: auto-refresh derived artifacts
github-actions[bot] Mar 29, 2026
6a083d8
feat: add wMulDown, wDivUp, mulDivDown, mulDivUp to the proven fragment
claude Mar 29, 2026
d2c06fc
chore: auto-refresh derived artifacts
github-actions[bot] Mar 29, 2026
c477d66
ci: trigger CI for auto-refresh verification
claude Mar 29, 2026
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
2 changes: 1 addition & 1 deletion .github/actions/setup-lean/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ runs:
if [ -x "$HOME/.elan/bin/elan" ]; then
exit 0
fi
curl -sSfL "https://raw.githubusercontent.com/leanprover/elan/${ELAN_VERSION}/elan-init.sh" -o elan-init.sh
curl -sSfL --retry 3 --retry-delay 5 "https://raw.githubusercontent.com/leanprover/elan/${ELAN_VERSION}/elan-init.sh" -o elan-init.sh
echo "${ELAN_INIT_SHA256} elan-init.sh" | sha256sum -c -
bash elan-init.sh -y --default-toolchain none
rm elan-init.sh
Expand Down
31 changes: 29 additions & 2 deletions AXIOMS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,33 @@ Instead, Verity treats it as a black box and validates its outputs in CI.

**Risk**: Low.

### 2. `solidityMappingSlot_lt_evmModulus`

**Location**: `Compiler/Proofs/MappingSlot.lean:125`

**Statement**:
```lean
axiom solidityMappingSlot_lt_evmModulus (baseSlot key : Nat) :
solidityMappingSlot baseSlot key < Compiler.Constants.evmModulus
```

**Purpose**:
Asserts that the keccak256 hash used to compute a Solidity mapping slot fits
in 256 bits (i.e. is less than 2^256). This is mathematically true because
keccak256 produces exactly 32 bytes, but unprovable in Lean because `ffi.KEC`
is an opaque FFI call that does not expose the output length.

**Why this is currently an axiom**:
The FFI boundary hides the byte-length guarantee. Proving it would require
internalising the keccak256 spec or exposing output-length metadata from the
FFI layer.

**Soundness controls**:
- End-to-end regression suites exercise mapping reads/writes.
- Mapping-slot abstraction boundary checks in CI.

**Risk**: Low.

## Trusted Cryptographic Primitive (Non-Axiom)

### `ffi.KEC` (keccak256 via FFI)
Expand Down Expand Up @@ -134,7 +161,7 @@ specification.

## Trust Summary

- Active axioms: 1
- Active axioms: 2
- Production blockers from axioms: 0
- Enforcement: `scripts/check_axioms.py` ensures this file tracks exact source locations.
- All internal compiler functions are proven to terminate (no axioms involved).
Expand All @@ -147,4 +174,4 @@ Any commit that adds, removes, renames, or moves an axiom must update this file

If this file is stale, trust analysis is stale.

**Last Updated**: 2026-03-11
**Last Updated**: 2026-03-27
13 changes: 9 additions & 4 deletions Compiler/CompilationModel/StorageWrites.lean
Original file line number Diff line number Diff line change
Expand Up @@ -85,28 +85,33 @@ def compileSetStorage (fields : List Field) (dynamicSource : DynamicDataSource)
throw s!"Compilation error: field '{field}' is not address-typed; use Stmt.setStorage instead"
let slots := slot :: f.aliasSlots
let valueExpr ← compileExpr fields dynamicSource value
let storedValueExpr :=
if requireAddressField then
YulExpr.call "and" [valueExpr, YulExpr.hex Compiler.Constants.addressMask]
Comment on lines +88 to +90
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply address masking in packed setStorageAddr paths

When requireAddressField is true, this change introduces storedValueExpr masked by addressMask, but only the non-packed branches use it; the packed branches still pass raw valueExpr to compilePackedStorageWrite/compileCompatPackedStorageWrites. Since packed bits are allowed on non-mapping fields, Stmt.setStorageAddr can still persist high bits in packed address fields (notably if packed width is >160), which makes packed and non-packed address writes semantically inconsistent. Thread the masked expression through packed branches too.

Useful? React with 👍 / 👎.

else
valueExpr
match slots with
| [] =>
throw s!"Compilation error: internal invariant failure: no write slots for field '{field}' in setStorage"
| [singleSlot] =>
match f.packedBits with
| none =>
pure [YulStmt.expr (YulExpr.call "sstore" [YulExpr.lit singleSlot, valueExpr])]
pure [YulStmt.expr (YulExpr.call "sstore" [YulExpr.lit singleSlot, storedValueExpr])]
Comment thread
cursor[bot] marked this conversation as resolved.
| some packed =>
pure (compilePackedStorageWrite (YulExpr.lit singleSlot) valueExpr packed)
pure (compilePackedStorageWrite (YulExpr.lit singleSlot) storedValueExpr packed)
| _ =>
let writeSlots := slots.map YulExpr.lit
match f.packedBits with
| none =>
pure [
YulStmt.block (
[YulStmt.let_ "__compat_value" valueExpr] ++
[YulStmt.let_ "__compat_value" storedValueExpr] ++
writeSlots.map (fun writeSlot =>
YulStmt.expr (YulExpr.call "sstore" [writeSlot, YulExpr.ident "__compat_value"]))
)
]
| some packed =>
pure (compileCompatPackedStorageWrites writeSlots valueExpr packed)
pure (compileCompatPackedStorageWrites writeSlots storedValueExpr packed)
| none => throw s!"Compilation error: unknown storage field '{field}' in setStorage"

def compileStorageArrayPush (fields : List Field) (dynamicSource : DynamicDataSource)
Expand Down
51 changes: 51 additions & 0 deletions Compiler/CompilationModel/Types.lean
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,25 @@ theorem fieldName_eq_of_findFieldWithResolvedSlot_eq_some
case isFalse =>
exact ih (idx + 1) hgo

private theorem find_eq_of_findFieldByName_go
{name : String}
{f : Field}
{slot : Nat}
{fields : List Field}
{idx : Nat} :
findFieldByName.go name (fun f slot => (f, slot)) fields idx = some (f, slot) →
fields.find? (·.name == name) = some f := by
intro hgo
induction fields generalizing idx with
| nil => simp [findFieldByName.go] at hgo
| cons hd tl ih =>
simp only [findFieldByName.go] at hgo
by_cases hname : hd.name == name
· simp [hname] at hgo ⊢
exact hgo.1
· simp [hname] at hgo ⊢
exact ih hgo

def findFieldWriteSlots (fields : List Field) (name : String) : Option (List Nat) :=
findFieldByName fields name fun f slot => slot :: f.aliasSlots

Expand Down Expand Up @@ -674,6 +693,38 @@ def isMapping (fields : List Field) (name : String) : Bool :=
| FieldType.mappingStruct2 _ _ _ => true
| _ => false

theorem isMapping_false_of_findFieldWithResolvedSlot_address
{fields : List Field}
{name : String}
{f : Field}
{slot : Nat}
(hfind : findFieldWithResolvedSlot fields name = some (f, slot))
(hty : f.ty = FieldType.address) :
isMapping fields name = false := by
unfold isMapping
have hfound : fields.find? (·.name == name) = some f := by
unfold findFieldWithResolvedSlot at hfind
unfold findFieldByName at hfind
exact find_eq_of_findFieldByName_go hfind
rw [hfound]
simp [Option.any, hty]

theorem isMapping_false_of_findFieldWithResolvedSlot_uint256
{fields : List Field}
{name : String}
{f : Field}
{slot : Nat}
(hfind : findFieldWithResolvedSlot fields name = some (f, slot))
(hty : f.ty = FieldType.uint256) :
isMapping fields name = false := by
unfold isMapping
have hfound : fields.find? (·.name == name) = some f := by
unfold findFieldWithResolvedSlot at hfind
unfold findFieldByName at hfind
exact find_eq_of_findFieldByName_go hfind
rw [hfound]
simp [Option.any, hty]

-- Helper: Is field a double mapping?
def isMapping2 (fields : List Field) (name : String) : Bool :=
fields.find? (·.name == name) |>.any fun f =>
Expand Down
36 changes: 18 additions & 18 deletions Compiler/CompilationModel/ValidationHelpers.lean
Original file line number Diff line number Diff line change
Expand Up @@ -132,24 +132,24 @@ mutual
def collectStmtNames : Stmt → List String
| Stmt.letVar name value => name :: collectExprNames value
| Stmt.assignVar name value => name :: collectExprNames value
| Stmt.setStorage field value | Stmt.setStorageAddr field value => field :: collectExprNames value
| Stmt.storageArrayPush field value => field :: collectExprNames value
| Stmt.storageArrayPop field => [field]
| Stmt.setStorageArrayElement field index value =>
field :: collectExprNames index ++ collectExprNames value
| Stmt.setMapping field key value => field :: collectExprNames key ++ collectExprNames value
| Stmt.setMappingWord field key _ value => field :: collectExprNames key ++ collectExprNames value
| Stmt.setMappingPackedWord field key _ _ value => field :: collectExprNames key ++ collectExprNames value
| Stmt.setMapping2 field key1 key2 value =>
field :: collectExprNames key1 ++ collectExprNames key2 ++ collectExprNames value
| Stmt.setMapping2Word field key1 key2 _ value =>
field :: collectExprNames key1 ++ collectExprNames key2 ++ collectExprNames value
| Stmt.setMappingUint field key value => field :: collectExprNames key ++ collectExprNames value
| Stmt.setMappingChain field keys value =>
field :: collectExprListNames keys ++ collectExprNames value
| Stmt.setStructMember field key _ value => field :: collectExprNames key ++ collectExprNames value
| Stmt.setStructMember2 field key1 key2 _ value =>
field :: collectExprNames key1 ++ collectExprNames key2 ++ collectExprNames value
| Stmt.setStorage _ value | Stmt.setStorageAddr _ value => collectExprNames value
| Stmt.storageArrayPush _ value => collectExprNames value
| Stmt.storageArrayPop _ => []
| Stmt.setStorageArrayElement _ index value =>
collectExprNames index ++ collectExprNames value
| Stmt.setMapping _ key value => collectExprNames key ++ collectExprNames value
| Stmt.setMappingWord _ key _ value => collectExprNames key ++ collectExprNames value
| Stmt.setMappingPackedWord _ key _ _ value => collectExprNames key ++ collectExprNames value
| Stmt.setMapping2 _ key1 key2 value =>
collectExprNames key1 ++ collectExprNames key2 ++ collectExprNames value
| Stmt.setMapping2Word _ key1 key2 _ value =>
collectExprNames key1 ++ collectExprNames key2 ++ collectExprNames value
| Stmt.setMappingUint _ key value => collectExprNames key ++ collectExprNames value
| Stmt.setMappingChain _ keys value =>
collectExprListNames keys ++ collectExprNames value
| Stmt.setStructMember _ key _ value => collectExprNames key ++ collectExprNames value
| Stmt.setStructMember2 _ key1 key2 _ value =>
collectExprNames key1 ++ collectExprNames key2 ++ collectExprNames value
| Stmt.require cond _ => collectExprNames cond
| Stmt.requireError cond errorName args => errorName :: collectExprNames cond ++ collectExprListNames args
| Stmt.revertError errorName args => errorName :: collectExprListNames args
Expand Down
18 changes: 18 additions & 0 deletions Compiler/CompilationModelFeatureTest.lean
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,21 @@ def initializeExecutableRunsOnce : Bool :=

example : initializeExecutableRunsOnce = true := by native_decide

def compileSetStorageAddrMasksAddressWrites : Bool :=
let fields : List Compiler.CompilationModel.Field :=
[{ name := "owner", ty := Compiler.CompilationModel.FieldType.address }]
match Compiler.CompilationModel.compileSetStorage fields .calldata "owner" (Expr.literal 42) true with
| .ok [Compiler.Yul.YulStmt.expr
(Compiler.Yul.YulExpr.call "sstore"
[Compiler.Yul.YulExpr.lit 0,
Compiler.Yul.YulExpr.call "and"
[Compiler.Yul.YulExpr.lit 42,
Compiler.Yul.YulExpr.hex mask]])] =>
mask == Compiler.Constants.addressMask
| _ => false

example : compileSetStorageAddrMasksAddressWrites = true := by native_decide

def initializeExecutableSecondCallReverts : Bool :=
let seedOwner := wordToAddress 77
match MacroInitializer.initOwner seedOwner Verity.defaultState with
Expand Down Expand Up @@ -2622,6 +2637,9 @@ set_option maxRecDepth 4096 in
expectTrue
"macro initializer executable path seeds storage on the first call"
MacroInitializerSmoke.initializeExecutableRunsOnce
expectTrue
"setStorageAddr compilation masks stored address words"
MacroInitializerSmoke.compileSetStorageAddrMasksAddressWrites
expectTrue
"macro initializer executable path rejects a second call"
MacroInitializerSmoke.initializeExecutableSecondCallReverts
Expand Down
14 changes: 7 additions & 7 deletions Compiler/Proofs/IRGeneration/Contract.lean
Original file line number Diff line number Diff line change
Expand Up @@ -2885,13 +2885,13 @@ theorem compile_preserves_semantics
-- SORRY'D: (hparamsSupported := hparamsSupported)
-- SORRY'D: (hfunction := hfunction)

-- SORRY'D: /-- Helper-proof-carrying whole-contract Layer 2 theorem.
-- SORRY'D: This theorem family is the intended stable public interface for the helper
-- SORRY'D: composition step tracked by `#1630`: callers can already pass explicit
-- SORRY'D: summary-soundness evidence today, and once the body proof consumes it this
-- TYPESIG_SORRY: theorem can strengthen without another theorem-shape rewrite. The current proof
-- TYPESIG_SORRY: still reduces through the legacy helper-closed path, so the trusted boundary is
-- TYPESIG_SORRY: unchanged. -/
/-- Helper-proof-carrying whole-contract Layer 2 theorem.
This theorem family is the intended stable public interface for the helper
composition step tracked by `#1630`: callers can already pass explicit
summary-soundness evidence today, and once the body proof consumes it this
theorem can strengthen without another theorem-shape rewrite. The current proof
still reduces through the legacy helper-closed path, so the trusted boundary is
unchanged. -/
theorem compile_preserves_semantics_with_helper_proofs
(model : CompilationModel)
(selectors : List Nat)
Expand Down
31 changes: 31 additions & 0 deletions Compiler/Proofs/IRGeneration/ExprCore.lean
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,37 @@ inductive ExprCompileCore : Expr → Prop where
ExprCompileCore lhs → ExprCompileCore rhs → ExprCompileCore (.logicalAnd lhs rhs)
| logicalOr {lhs rhs : Expr} :
ExprCompileCore lhs → ExprCompileCore rhs → ExprCompileCore (.logicalOr lhs rhs)
| bitAnd {lhs rhs : Expr} :
ExprCompileCore lhs → ExprCompileCore rhs → ExprCompileCore (.bitAnd lhs rhs)
| bitOr {lhs rhs : Expr} :
ExprCompileCore lhs → ExprCompileCore rhs → ExprCompileCore (.bitOr lhs rhs)
| bitXor {lhs rhs : Expr} :
ExprCompileCore lhs → ExprCompileCore rhs → ExprCompileCore (.bitXor lhs rhs)
| bitNot {expr : Expr} :
ExprCompileCore expr → ExprCompileCore (.bitNot expr)
| shl {shift value : Expr} :
ExprCompileCore shift → ExprCompileCore value → ExprCompileCore (.shl shift value)
| shr {shift value : Expr} :
ExprCompileCore shift → ExprCompileCore value → ExprCompileCore (.shr shift value)
| min {lhs rhs : Expr} :
ExprCompileCore lhs → ExprCompileCore rhs → ExprCompileCore (.min lhs rhs)
| max {lhs rhs : Expr} :
ExprCompileCore lhs → ExprCompileCore rhs → ExprCompileCore (.max lhs rhs)
| ite {cond thenVal elseVal : Expr} :
ExprCompileCore cond → ExprCompileCore thenVal → ExprCompileCore elseVal →
ExprCompileCore (.ite cond thenVal elseVal)
| ceilDiv {lhs rhs : Expr} :
ExprCompileCore lhs → ExprCompileCore rhs → ExprCompileCore (.ceilDiv lhs rhs)
| wMulDown {lhs rhs : Expr} :
ExprCompileCore lhs → ExprCompileCore rhs → ExprCompileCore (.wMulDown lhs rhs)
| wDivUp {lhs rhs : Expr} :
ExprCompileCore lhs → ExprCompileCore rhs → ExprCompileCore (.wDivUp lhs rhs)
| mulDivDown {a b c : Expr} :
ExprCompileCore a → ExprCompileCore b → ExprCompileCore c →
ExprCompileCore (.mulDivDown a b c)
| mulDivUp {a b c : Expr} :
ExprCompileCore a → ExprCompileCore b → ExprCompileCore c →
ExprCompileCore (.mulDivUp a b c)

/-! ## Scope analysis -/

Expand Down
Loading
Loading