-
Notifications
You must be signed in to change notification settings - Fork 111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Issue #970: interpreter called only when contract present #1047
base: main
Are you sure you want to change the base?
Conversation
TODOs, like adding the CLI option to activate/deactivate optimization phase.
Added CLI options for skipping optimization phase and for dumping optimized tact code.
Further fixes: - subexpressions inside struct instances did not have their types registered correctly. - makeValueExpression now receives a SrcInfo object.
"dumpOptimizedTactCode": { | ||
"type": "boolean", | ||
"default": false, | ||
"description": "False by default. If set to true, dumps the code produced by the Tact code optimization phase. In case the optimization phase is skipped, this option is ignored." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Smooth" (in math sense) behavior would be to dump the code anyway, yet unoptimized. "Dump the optimized code" is just another separate optional compilation step that happens to be in the list after "optimize" step.
@@ -960,7 +960,7 @@ export class Interpreter { | |||
if (foundContractConst.value !== undefined) { | |||
return foundContractConst.value; | |||
} else { | |||
throwErrorConstEval( | |||
throwNonFatalErrorConstEval( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I don't understand this change, and I suspect a future reader wouldn't either.
When do we throw non-fatal errors in general?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When do we throw non-fatal errors in general?
When we want to stop the constant evaluation, but continue compiling the rest of the contract. Like, when not being able to compute something at compile-time with ability to fallback to run-time implementation for that something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stopEvaluation()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but the current pipeline kinda relies on throwing and catching errors and not on errors as values ¯\_(ツ)_/¯
return ctx; | ||
} | ||
|
||
export function dump_tact_code(ctx: CompilerContext, file: string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's odd that the function calls prettyPrint
several times.
prettyPrint
is supposed to print the whole AST node, whatever its type is.
Either we're missing another type of AST node, or this is actually something in the lines of prettyPrint({ kind: 'module', ... })
.
program += `${prettyPrint(t.ast)}\n\n`; | ||
} | ||
|
||
writeFileSync(file, program, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
writeFile
does w
by default, doesn't it?
Also *Sync
functions are synchronous and stop the whole JS virtual machine until they're done with what they were doing. It's fine to use a few of these in tests or in build scripts, but I think they can never be used from any of src/**/*.ts
.
Eventually we might want to read several files during compilation, and in case of Web IDE probably even download those files over wire. *Sync
is impossible to emulate in web (barring some unfortunate deprecated APIs that make the whole browser tab unresponsive), and there'll be no easy way to unSync
the code in the future.
This should probably use import fs from "node:fs/promises"
@@ -0,0 +1,184 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
|
|||
exports[`expression-simplification should fail expression simplification for interpreter-called-when-no-contract 1`] = ` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very, very wise use of Jest snapshots.
I was thinking to investigate how to do this, but you've already done it. Thanks!
case "constant_def": { | ||
const newCode = newConstantsMap.get( | ||
idText(decl.name), | ||
)!.ast; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!
case "field_decl": { | ||
const newCode = newFieldsMap.get( | ||
idText(decl.name), | ||
)!.ast; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!
} | ||
|
||
newTypes.set(t.name, { | ||
...t, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spread operator typechecks incorrectly if used on a union type. All the fields that are not modified by the code should be listed explicitly.
The only valid use-case for spread operator is when the type of an object is Record<string, ...>
. Otherwise the program might compile, but work incorrectly.
return { stmts: newStatements, ctx: ctx }; | ||
} | ||
|
||
function process_statement( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a visitor, it's not unique to optimizer, and likely should go into ast.ts
.
// Register the new expression in the context | ||
ctx = registerAllSubExpTypes(ctx, newExpr, getExpType(ctx, expr)); | ||
} catch (_) { | ||
// This means that transforming the value into an AST node is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could have any kind of exception here, including stack overflow exception.
If there is a specific kind of exception that should be handled here, there has to be an instanceof
check, and all other exceptions have to be rethrown up the stack.
Issue
Closes #970.
Solves the issue by adding a new step in the build pipeline: the tact optimization phase immediately after typechecking (i.e., immediately after
precompile
).Currently, the optimization phase only carries out expression simplification in the AST by running the interpreter over all expressions in the AST (later, this will be replaced by the partial evaluator). I did this in preparation for other optimization techniques that will be added in the future (for example, constant propagation will be moved to the optimization phase later).
The PR adds two CLI compiler options:
NOTE: This PR does not modify the calls to the interpreter that are currently done in the FunC generator code. This means that the Tact interpreter is unnecessarily called twice. These second calls to the interpreter will be removed later in a separate issue.
NOTE 2: This PR does not modify the AST one gets from calling
getRawAST
on the context objectctx
. I did a quick check and the FunC generation part seems to make use of the functionsgetAllTypes
,getAllStaticFunctions
and the like. This PR DOES modify the ASTs produced by those functions. The FunC generator part seems to use only the "sources" fromgetRawAST
, but I still do not understand that part of the code.Checklist