Skip to content

Commit da780ae

Browse files
Better interpreter and stack printer (#412)
* Better interpreter and stack printer * Stack limit and deref limit
1 parent a94a82a commit da780ae

3 files changed

Lines changed: 55 additions & 28 deletions

File tree

Ix/Aiur/Interpret.lean

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,43 +35,43 @@ private partial def natToHexGo (n : Nat) (acc : String) : String :=
3535
private def natToHex (n : Nat) : String :=
3636
if n == 0 then "0" else natToHexGo n ""
3737

38-
partial def ppValue : Value → String
38+
partial def Value.pp : Value → String
3939
| .unit => "()"
4040
| .field g => toString g.val.toNat
41-
| .tuple vs => "(" ++ String.intercalate ", " (vs.toList.map ppValue) ++ ")"
42-
| .array vs => "[" ++ String.intercalate ", " (vs.toList.map ppValue) ++ "]"
41+
| .tuple vs => "(" ++ String.intercalate ", " (vs.toList.map Value.pp) ++ ")"
42+
| .array vs => "[" ++ String.intercalate ", " (vs.toList.map Value.pp) ++ "]"
4343
| .ctor g args =>
4444
let name := g.toName.toString
4545
if args.isEmpty then name
46-
else name ++ "(" ++ String.intercalate ", " (args.toList.map ppValue) ++ ")"
46+
else name ++ "(" ++ String.intercalate ", " (args.toList.map Value.pp) ++ ")"
4747
| .fn g => "fn(" ++ g.toName.toString ++ ")"
4848
| .pointer _ n => "&0x" ++ natToHex n
4949

50-
instance : ToString Value := ⟨ppValue
50+
instance : ToString Value := ⟨Value.pp
5151

5252
/-- Pretty-print a `Value` while auto-dereferencing pointers up to `depth`
5353
levels. Used by the `dbg!` interpreter helper so users see structured
5454
content like `App(Const(3, []), BVar(0))` instead of opaque `&0x123`. -/
55-
partial def ppValueDeref (store : Store) (depth : Nat) : Value → String
55+
partial def Value.ppDeref (store : Store) (depth : Nat) : Value → String
5656
| .unit => "()"
5757
| .field g => toString g.val.toNat
58-
| .tuple vs => "(" ++ String.intercalate ", " (vs.toList.map (ppValueDeref store depth)) ++ ")"
59-
| .array vs => "[" ++ String.intercalate ", " (vs.toList.map (ppValueDeref store depth)) ++ "]"
58+
| .tuple vs => "(" ++ String.intercalate ", " (vs.toList.map (Value.ppDeref store depth)) ++ ")"
59+
| .array vs => "[" ++ String.intercalate ", " (vs.toList.map (Value.ppDeref store depth)) ++ "]"
6060
| .ctor g args =>
6161
let name := g.toName.toString
6262
if args.isEmpty then name
63-
else name ++ "(" ++ String.intercalate ", " (args.toList.map (ppValueDeref store depth)) ++ ")"
63+
else name ++ "(" ++ String.intercalate ", " (args.toList.map (Value.ppDeref store depth)) ++ ")"
6464
| .fn g => "fn(" ++ g.toName.toString ++ ")"
6565
| .pointer _ n =>
66-
if depth == 0 then "&0x" ++ natToHex n
66+
if depth == 0 then "..."
6767
else
6868
match store.getByIdx n with
6969
| some (vs, _) =>
7070
-- Stored value is `Array Value`; for tagged enums it's
7171
-- typically `[ctor]` or `[tag, fields...]`. Recurse on each.
7272
match vs.toList with
73-
| [v] => ppValueDeref store (depth - 1) v
74-
| _ => "[" ++ String.intercalate ", " (vs.toList.map (ppValueDeref store (depth - 1))) ++ "]"
73+
| [v] => Value.ppDeref store (depth - 1) v
74+
| _ => "[" ++ String.intercalate ", " (vs.toList.map (Value.ppDeref store (depth - 1))) ++ "]"
7575
| none => "&0x" ++ natToHex n ++ "(unbound)"
7676

7777
-- ---------------------------------------------------------------------------
@@ -130,6 +130,30 @@ instance : ToString Interrupt where
130130
s!" in {g}({argStr})"
131131
msg ++ "\nCall stack:\n" ++ String.intercalate "\n" frames
132132

133+
/-- Pretty-print an `Interrupt`, auto-dereferencing pointer values up to
134+
`depth` pointer-follows, and showing at most `stackLimit` call-stack frames
135+
(starting from the innermost). -/
136+
def Interrupt.ppDeref (store : Store) (depth : Nat) (stackLimit : Nat) :
137+
Interrupt → String
138+
| .ret v => s!"unexpected return: {Value.ppDeref store depth v}"
139+
| .error msg [] => msg
140+
| .error msg stack =>
141+
let total := stack.length
142+
let shown := stack.take stackLimit
143+
let rule := String.ofList (List.replicate 80 '─')
144+
let frames := shown.zipIdx.map fun ((g, args), i) =>
145+
let argStr := String.intercalate ", " (args.map (Value.ppDeref store depth))
146+
let header := s!"─── #{i} " ++ g.toName.toString ++ " "
147+
let pad := String.ofList (List.replicate (max 1 (80 - header.length)) '─')
148+
header ++ pad ++ s!"\n {g}({argStr})"
149+
let trailer := if total > stackLimit
150+
then s!"\n... ({total - stackLimit} more frame(s) elided)"
151+
else ""
152+
msg ++ s!"\nCall stack ({total} frames, #0 = innermost):\n"
153+
++ String.intercalate "\n" frames
154+
++ trailer
155+
++ "\n" ++ rule
156+
133157
-- ---------------------------------------------------------------------------
134158
-- Interpreter monad
135159
-- ---------------------------------------------------------------------------
@@ -139,7 +163,7 @@ structure InterpState where
139163
ioBuffer : IOBuffer
140164
callCache : Std.HashMap (Global × List Value) Value := {}
141165

142-
abbrev InterpM := StateT InterpState (Except Interrupt)
166+
abbrev InterpM := ExceptT Interrupt (StateM InterpState)
143167

144168
private def throwErr (msg : String) : InterpM α :=
145169
throw (.error msg [])
@@ -356,7 +380,7 @@ partial def interp (decls : Decls) (bindings : Bindings) : Term → InterpM Valu
356380
| some t =>
357381
let v ← interp decls bindings t
358382
let store ← getStore
359-
dbg_trace s!"{label}: {ppValueDeref store 16 v}"
383+
dbg_trace s!"{label}: {Value.ppDeref store 16 v}"
360384
interp decls bindings cont
361385
| .ioGetInfo key => do
362386
let keyGs ← expectFieldArray (← interp decls bindings key)
@@ -392,15 +416,18 @@ end
392416

393417
def runFunction (decls : Decls) (funcName : Global) (inputs : List Value)
394418
(ioBuffer : IOBuffer := default) :
395-
Except Interrupt (Value × InterpState) := do
396-
let f ← match decls.getByKey funcName with
397-
| some (.function f) => pure f
398-
| _ => throw (.error s!"Function not found: {funcName}" [])
399-
if inputs.length != f.inputs.length then
400-
throw (.error s!"runFunction: arity mismatch for {funcName}: \
401-
expected {f.inputs.length}, got {inputs.length}" [])
402-
let bindings := f.inputs.map (·.1) |>.zip inputs
403-
StateT.run (interp decls bindings f.body) { ioBuffer }
419+
Except Interrupt Value × InterpState :=
420+
let init : InterpState := { ioBuffer }
421+
match decls.getByKey funcName with
422+
| some (.function f) =>
423+
if inputs.length != f.inputs.length then
424+
(.error (.error s!"runFunction: arity mismatch for {funcName}: \
425+
expected {f.inputs.length}, got {inputs.length}" []), init)
426+
else
427+
let bindings := f.inputs.map (·.1) |>.zip inputs
428+
StateT.run (ExceptT.run (interp decls bindings f.body)) init
429+
| _ =>
430+
(.error (.error s!"Function not found: {funcName}" []), init)
404431

405432
end Aiur
406433

Kernel.lean

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ where
7373
| _ => IO.eprintln s!"{name}: function not found in decls"; return 1
7474
let inputs := Aiur.unflattenInputs decls testCase.input inputTypes
7575
match Aiur.runFunction decls funcName inputs testCase.inputIOBuffer with
76-
| .error e =>
77-
IO.eprintln s!"{name}: interpreter error:\n{e}"
76+
| (.error e, s) =>
77+
IO.eprintln s!"{name}: interpreter error:\n{e.ppDeref s.store 1 10}"
7878
return 1
79-
| .ok (output, _state) =>
79+
| (.ok output, _) =>
8080
IO.println s!"{name}: {output}"
8181
pure 0

Tests/Aiur/Common.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ def AiurTestEnv.interpTest (env : AiurTestEnv) (testCase : AiurTestCase)
6666
let inputs := Aiur.unflattenInputs env.decls testCase.input inputTypes
6767
let funcIdx g := env.compiled.getFuncIdx g.toName
6868
match Aiur.runFunction env.decls funcName inputs testCase.inputIOBuffer with
69-
| .error e =>
69+
| (.error e, _) =>
7070
test s!"Interpret succeeds for {label}: {e}" false
71-
| .ok (output, state) =>
71+
| (.ok output, state) =>
7272
let flat := Aiur.flattenValue env.decls funcIdx output
7373
let interpOutputTest := test s!"Interpret output matches for {label}"
7474
(flat == execOutput)

0 commit comments

Comments
 (0)