Skip to content

Commit 17ba0ba

Browse files
committed
Call trace for failing evaluation via new emitter mode
1 parent ee15d36 commit 17ba0ba

27 files changed

+377
-13
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
### Added
2+
3+
- Added a new emitter mode `logWithCallTraceEmitter` which uses trace messages generated by `PlutusTx.Plugin:profile-all` flag of plutus-tx-plugin to create call trace of the functions that led to the evaluation failure. If script passes or script is not compiled with `profile-all` flag, `logWithCallTraceEmitter` will behave as regular `logEmitter`.
4+

plutus-core/executables/src/PlutusCore/Executable/Types.hs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,13 @@ data TimingMode = NoTiming | Timing Integer deriving stock (Eq) -- Report progra
5555
data CekModel = Default | Unit -- Which cost model should we use for CEK machine steps?
5656
data PrintMode = Classic | Simple | Readable | ReadableSimple deriving stock (Show, Read)
5757
data NameFormat = IdNames | DeBruijnNames -- Format for textual output of names
58-
data TraceMode = None | Logs | LogsWithTimestamps | LogsWithBudgets deriving stock (Show, Read)
58+
data TraceMode
59+
= None
60+
| Logs
61+
| LogsWithTimestamps
62+
| LogsWithBudgets
63+
| LogsWithCallTrace
64+
deriving stock (Show, Read)
5965
type ExampleName = T.Text
6066
data ExampleMode = ExampleSingle ExampleName | ExampleAvailable
6167

@@ -95,4 +101,3 @@ pirFormatToFormat FlatNamed = Flat Named
95101

96102
-- | Output types for some pir commands
97103
data Language = PLC | UPLC
98-

plutus-core/untyped-plutus-core/src/UntypedPlutusCore/Evaluation/Machine/Cek.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ module UntypedPlutusCore.Evaluation.Machine.Cek
4040
, logEmitter
4141
, logWithTimeEmitter
4242
, logWithBudgetEmitter
43+
, logWithCallTraceEmitter
4344
-- * Misc
4445
, BuiltinsRuntime (..)
4546
, CekValue (..)

plutus-core/untyped-plutus-core/src/UntypedPlutusCore/Evaluation/Machine/Cek/EmitterMode.hs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
{-# LANGUAGE OverloadedStrings #-}
12
{-# OPTIONS_GHC -Wno-orphans #-}
23

34
module UntypedPlutusCore.Evaluation.Machine.Cek.EmitterMode
45
( noEmitter
56
, logEmitter
67
, logWithTimeEmitter
78
, logWithBudgetEmitter
9+
, logWithCallTraceEmitter
810
) where
911

1012
import UntypedPlutusCore.Evaluation.Machine.Cek.Internal
@@ -68,3 +70,35 @@ logWithBudgetEmitter = EmitterMode $ \getBudget -> do
6870
let withBudget = logs <&> \str -> encodeRecord (str, exCpu, exMemory)
6971
modifySTRef logsRef (`DList.append` withBudget)
7072
pure $ CekEmitterInfo emitter (DList.toList <$> readSTRef logsRef)
73+
74+
{- | Emits log and, when script evaluation fails, call trace.
75+
76+
This requires script to be compiled with `PlutusTx.Plugin:profile-all` turned on because this relies
77+
on compiler-generated trace calls that notifies entrance and exit of a function call. These traces
78+
that mark entrance and exit are ordinary traces like "entering rob:Example.hs:3:1-3:15" and "exiting
79+
bob:Example.hs:1:1-1:13" with "entering" and "exiting" prefixies, where "bob" and "rob" is the name
80+
of the function with source span. If regular script with no entrance/exit marker is given, this
81+
emitter will behave identically to 'logEmitter'.
82+
83+
When script evaluation fails, this emitter will give call trace of the functions that led to the
84+
evaluation failure. This is useful for pin-pointing specific area of the codebase that caused
85+
failure when debugging a script. When script evaluation passes, every trace message generated by
86+
`profile-all` flag will be removed, and this emitter will behave identically to 'logEmitter'.
87+
-}
88+
logWithCallTraceEmitter :: EmitterMode uni fun
89+
logWithCallTraceEmitter = EmitterMode $ \_ -> do
90+
logsRef <- newSTRef DList.empty
91+
let
92+
addTrace DList.Nil logs = logs
93+
addTrace newLogs DList.Nil = newLogs
94+
addTrace newLogs logs = DList.fromList $ go (DList.toList newLogs) (DList.toList logs)
95+
where
96+
go l [] = l
97+
go [] l = l
98+
go (x:xs) l =
99+
case (T.words (last l), T.words x) of
100+
("entering":enterRest, "exiting":exitRest) | enterRest == exitRest -> go xs (init l)
101+
_ -> go xs (l <> [x])
102+
103+
emitter logs = CekM $ modifySTRef logsRef (addTrace logs)
104+
pure $ CekEmitterInfo emitter (DList.toList <$> readSTRef logsRef)

plutus-core/untyped-plutus-core/src/UntypedPlutusCore/Evaluation/Machine/CommonAPI.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ module UntypedPlutusCore.Evaluation.Machine.CommonAPI
4242
, logEmitter
4343
, logWithTimeEmitter
4444
, logWithBudgetEmitter
45+
, logWithCallTraceEmitter
4546
-- * Misc
4647
, CekValue(..)
4748
, readKnownCek

plutus-core/untyped-plutus-core/src/UntypedPlutusCore/Evaluation/Machine/SteppableCek.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ module UntypedPlutusCore.Evaluation.Machine.SteppableCek
3838
, logEmitter
3939
, logWithTimeEmitter
4040
, logWithBudgetEmitter
41+
, logWithCallTraceEmitter
4142
-- * Misc
4243
, CekValue(..)
4344
, readKnownCek

plutus-executables/executables/uplc/Main.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ runEval (EvalOptions inp ifmt printMode nameFormat budgetMode traceMode
384384
Logs -> Cek.logEmitter
385385
LogsWithTimestamps -> Cek.logWithTimeEmitter
386386
LogsWithBudgets -> Cek.logWithBudgetEmitter
387+
LogsWithCallTrace -> Cek.logWithCallTraceEmitter
387388
-- Need the existential cost type in scope
388389
let budgetM = case budgetMode of
389390
Silent -> SomeBudgetMode Cek.restrictingEnormous

plutus-tx-plugin/plutus-tx-plugin.cabal

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ test-suite plutus-tx-plugin-tests
136136
BuiltinList.Budget.Spec
137137
ByteStringLiterals.Lib
138138
ByteStringLiterals.Spec
139+
CallTrace.OtherModule
140+
CallTrace.Spec
141+
CallTrace.Utils
139142
DataList.Budget.Spec
140143
Inline.Spec
141144
IntegerLiterals.NoStrict.NegativeLiterals.Spec
@@ -186,6 +189,7 @@ test-suite plutus-tx-plugin-tests
186189
, bytestring
187190
, containers
188191
, deepseq
192+
, dlist
189193
, filepath
190194
, flat ^>=0.6
191195
, hedgehog
@@ -196,6 +200,7 @@ test-suite plutus-tx-plugin-tests
196200
, plutus-tx ^>=1.48
197201
, plutus-tx-plugin ^>=1.48
198202
, plutus-tx:plutus-tx-testlib
203+
, prettyprinter
199204
, serialise
200205
, tasty
201206
, tasty-golden

plutus-tx-plugin/src/PlutusTx/Compiler/Expr.hs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ import PlutusIR.Purity qualified as PIR
6464
import PlutusCore qualified as PLC
6565
import PlutusCore.Data qualified as PLC
6666
import PlutusCore.MkPlc qualified as PLC
67-
import PlutusCore.Pretty qualified as PP
6867
import PlutusCore.StdLib.Data.Function qualified
6968
import PlutusCore.Subst qualified as PLC
7069

@@ -650,19 +649,31 @@ hoistExpr var t = do
650649
(PIR.Def var' (PIR.mkVar var', PIR.Strict))
651650
mempty
652651

653-
t' <- maybeProfileRhs var' =<< addSpan (compileExpr t)
652+
t' <- maybeProfileRhs var var' =<< addSpan (compileExpr t)
654653
-- See Note [Non-strict let-bindings]
655654
PIR.modifyTermDef lexName (const $ PIR.Def var' (t', PIR.NonStrict))
656655
pure $ PIR.mkVar var'
657656

657+
-- 'GHC.Var' in argument is only for extracting srcspan and accurate name.
658658
maybeProfileRhs
659-
:: (CompilingDefault uni fun m ann) => PLCVar uni -> PIRTerm uni fun -> m (PIRTerm uni fun)
660-
maybeProfileRhs var t = do
659+
:: (CompilingDefault uni fun m ann)
660+
=> GHC.Var
661+
-> PLCVar uni
662+
-> PIRTerm uni fun
663+
-> m (PIRTerm uni fun)
664+
maybeProfileRhs ghcVar var t = do
661665
CompileContext{ccOpts = compileOpts} <- ask
662-
let ty = PLC._varDeclType var
663-
varName = PLC._varDeclName var
664-
displayName = T.pack $ PP.displayPlcSimple varName
665-
isFunctionOrAbstraction = case ty of PLC.TyFun{} -> True; PLC.TyForall{} -> True; _ -> False
666+
let
667+
nameStr = GHC.occNameString $ GHC.occName $ GHC.varName $ ghcVar
668+
displayName = T.pack $
669+
case getVarSourceSpan ghcVar of
670+
-- FIXME: Variables will miss span information when the module they are defined
671+
-- in is loaded from cache instead of getting compiled.
672+
Nothing -> nameStr
673+
Just src -> nameStr <> ":" <> show (src ^. srcSpanIso)
674+
675+
ty = PLC._varDeclType var
676+
isFunctionOrAbstraction = case ty of PLC.TyFun{} -> True; PLC.TyForall{} -> True; _ -> False
666677
-- Trace only if profiling is on *and* the thing being defined is a function
667678
if coProfile compileOpts == All && isFunctionOrAbstraction
668679
then do
@@ -1159,16 +1170,16 @@ compileExpr e = traceCompilation 2 ("Compiling expr:" GHC.<+> GHC.ppr e) $ do
11591170
_ -> compileTypeNorm $ GHC.varType b
11601171
-- See Note [Non-strict let-bindings]
11611172
withVarTyScoped b ty $ \v -> do
1162-
rhs'' <- maybeProfileRhs v rhs'
1173+
rhs'' <- maybeProfileRhs b v rhs'
11631174
let binds = pure $ PIR.TermBind annMayInline PIR.NonStrict v rhs''
11641175
body' <- compileExpr body
11651176
pure $ PIR.Let annMayInline PIR.NonRec binds body'
11661177
GHC.Let (GHC.Rec bs) body ->
11671178
withVarsScoped (fmap (second (const Nothing)) bs) $ \vars -> do
11681179
-- the bindings are scope in both the body and the args
11691180
-- TODO: this is a bit inelegant matching the vars back up
1170-
binds <- for (zip vars bs) $ \(v, (_, rhs)) -> do
1171-
rhs' <- maybeProfileRhs v =<< compileExpr rhs
1181+
binds <- for (zip vars bs) $ \(v, (ghcVar, rhs)) -> do
1182+
rhs' <- maybeProfileRhs ghcVar v =<< compileExpr rhs
11721183
-- See Note [Non-strict let-bindings]
11731184
pure $ PIR.TermBind annMayInline PIR.NonStrict v rhs'
11741185
body' <- compileExpr body
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
An error has occurred:
2+
The machine terminated because of an error, either from a built-in function or from an explicit use of 'error'.
3+
Caused by: error
4+
5+
Trace:
6+
entering func:test/CallTrace/Spec.hs:69:1-69:4
7+
func 1
8+
entering nestedLinear:test/CallTrace/Spec.hs:62:1-62:12
9+
entering nestedLinear2:test/CallTrace/Spec.hs:63:1-63:13
10+
entering nestedLinear3:test/CallTrace/Spec.hs:64:1-64:13
11+
entering nestedLinear4:test/CallTrace/Spec.hs:65:1-65:13
12+
entering error

0 commit comments

Comments
 (0)