Skip to content

Commit 5bdbb8d

Browse files
authored
Call trace for failing evaluation via new emitter mode (#7178)
* Call trace for failing evaluation via new emitter mode * Update goldens for `Plugin.Profiling` * Change enter/exit marker with arrows * Update srcspan formatting, formatting * Review changes * Add note about profiling marker * Use NoImplicitPrelude * Add link to issue for source span problem
1 parent fb11a0e commit 5bdbb8d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+609
-223
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/executables/traceToStacks/Common.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@ parseProfileEvent valIx (LogRow str vals) =
6464
let val = vals !! (valIx-1)
6565
in case words str of
6666
[transition,var] ->
67+
-- See Note [Profiling Marker]
6768
case transition of
68-
"entering" -> MkProfileEvent val Enter (T.pack var)
69-
"exiting" -> MkProfileEvent val Exit (T.pack var)
69+
"->" -> MkProfileEvent val Enter (T.pack var)
70+
"<-" -> MkProfileEvent val Exit (T.pack var)
7071
badLog -> error $
7172
"parseProfileEvent: expecting \"entering\" or \"exiting\" but got "
7273
<> show badLog
@@ -105,4 +106,3 @@ getStacks = go []
105106
go [] [] = []
106107
go stacks [] = error $
107108
"getStacks; go: stack " <> show stacks <> " isn't empty but the log is."
108-

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: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
{-# LANGUAGE OverloadedStrings #-}
12
{-# OPTIONS_GHC -Wno-orphans #-}
23

3-
module UntypedPlutusCore.Evaluation.Machine.Cek.EmitterMode
4-
( noEmitter
5-
, logEmitter
6-
, logWithTimeEmitter
7-
, logWithBudgetEmitter
8-
) where
4+
module UntypedPlutusCore.Evaluation.Machine.Cek.EmitterMode (
5+
noEmitter,
6+
logEmitter,
7+
logWithTimeEmitter,
8+
logWithBudgetEmitter,
9+
logWithCallTraceEmitter,
10+
) where
911

1012
import UntypedPlutusCore.Evaluation.Machine.Cek.Internal
1113

@@ -68,3 +70,36 @@ 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 "-> rob:Example.hs:3:1-3:15" and "<-
79+
bob:Example.hs:1:1-1:13" with "->" and "<-" 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+
-- See Note [Profiling Marker]
100+
case (T.words (last l), T.words x) of
101+
("->" : enterRest, "<-" : exitRest) | enterRest == exitRest -> go xs (init l)
102+
_ -> go xs (l <> [x])
103+
104+
emitter logs = CekM $ modifySTRef logsRef (addTrace logs)
105+
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: 4 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.Lib
140+
CallTrace.OtherModule
141+
CallTrace.Spec
139142
DataList.Budget.Spec
140143
Inline.Spec
141144
IntegerLiterals.NoStrict.NegativeLiterals.Spec
@@ -196,6 +199,7 @@ test-suite plutus-tx-plugin-tests
196199
, plutus-tx ^>=1.49
197200
, plutus-tx-plugin ^>=1.49
198201
, plutus-tx:plutus-tx-testlib
202+
, prettyprinter
199203
, serialise
200204
, tasty
201205
, tasty-golden

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

Lines changed: 38 additions & 13 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,32 @@ 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+
-- When module is not compiled and GHC is using cached build from previous build, it will
671+
-- lack source span. There's nothing much we can do about this here since this is GHC
672+
-- behavior. Issue #7203
673+
Nothing -> nameStr
674+
Just src -> nameStr <> " (" <> show (src ^. srcSpanIso) <> ")"
675+
676+
ty = PLC._varDeclType var
677+
isFunctionOrAbstraction = case ty of PLC.TyFun{} -> True; PLC.TyForall{} -> True; _ -> False
666678
-- Trace only if profiling is on *and* the thing being defined is a function
667679
if coProfile compileOpts == All && isFunctionOrAbstraction
668680
then do
@@ -765,6 +777,18 @@ entryExitTracingInside lamName displayName = go mempty
765777
let ty' = PLC.typeSubstTyNames (\tn -> Map.lookup tn subst) ty
766778
in entryExitTracing lamName displayName e ty'
767779

780+
{- Note [Profiling Markers]
781+
The @profile-all@ will insert trarces when entering and exciting functions. These
782+
traces have a string marker to indicate that a given traces message is for enter/exit
783+
marking. Markers are just simple strings: "->" and "<-". So for any reason in the
784+
future this marker needs to be changed, all of utilities that uses this marker will
785+
need to be updated.
786+
787+
This list will track of all of the utilities that uses this marker:
788+
- plutus-core:traceToStacks
789+
- @UntypedPlutusCore.Evaluation.Machine.Cek.EmitterMode.logWithCallTraceEmitter@
790+
-}
791+
768792
-- | Add tracing before entering and after exiting a term.
769793
entryExitTracing
770794
:: PLC.Name
@@ -780,9 +804,10 @@ entryExitTracing lamName displayName e ty =
780804
annMayInline
781805
( mkTrace
782806
(PLC.TyFun annMayInline defaultUnitTy ty) -- ()-> ty
783-
("entering " <> displayName)
807+
-- See Note [Profiling Marker]
808+
("-> " <> displayName)
784809
-- \() -> trace @c "exiting f" e
785-
(LamAbs annMayInline lamName defaultUnitTy (mkTrace ty ("exiting " <> displayName) e))
810+
(LamAbs annMayInline lamName defaultUnitTy (mkTrace ty ("<- " <> displayName) e))
786811
)
787812
defaultUnit
788813

@@ -1159,16 +1184,16 @@ compileExpr e = traceCompilation 2 ("Compiling expr:" GHC.<+> GHC.ppr e) $ do
11591184
_ -> compileTypeNorm $ GHC.varType b
11601185
-- See Note [Non-strict let-bindings]
11611186
withVarTyScoped b ty $ \v -> do
1162-
rhs'' <- maybeProfileRhs v rhs'
1187+
rhs'' <- maybeProfileRhs b v rhs'
11631188
let binds = pure $ PIR.TermBind annMayInline PIR.NonStrict v rhs''
11641189
body' <- compileExpr body
11651190
pure $ PIR.Let annMayInline PIR.NonRec binds body'
11661191
GHC.Let (GHC.Rec bs) body ->
11671192
withVarsScoped (fmap (second (const Nothing)) bs) $ \vars -> do
11681193
-- the bindings are scope in both the body and the args
11691194
-- 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
1195+
binds <- for (zip vars bs) $ \(v, (ghcVar, rhs)) -> do
1196+
rhs' <- maybeProfileRhs ghcVar v =<< compileExpr rhs
11721197
-- See Note [Non-strict let-bindings]
11731198
pure $ PIR.TermBind annMayInline PIR.NonStrict v rhs'
11741199
body' <- compileExpr body

0 commit comments

Comments
 (0)