-
Notifications
You must be signed in to change notification settings - Fork 122
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
refactor: add optimization phase #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.
schemas/configSchema.json
Outdated
"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.
src/interpreter.ts
Outdated
@@ -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 ¯\_(ツ)_/¯
src/optimizer/optimization_phase.ts
Outdated
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', ... })
.
src/optimizer/optimization_phase.ts
Outdated
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!
src/optimizer/expr_simplification.ts
Outdated
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.
!
src/optimizer/expr_simplification.ts
Outdated
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.
!
src/optimizer/expr_simplification.ts
Outdated
} | ||
|
||
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.
src/optimizer/expr_simplification.ts
Outdated
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
.
src/optimizer/expr_simplification.ts
Outdated
// 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.
Pending changes related to Value type. These will be addressed once Value type gets refactored in issue #1190.
I'm wondering what is going on with this PR and a bit surprised with the lack of progress. |
It required the refactor of the Value type, but now that it is solved, I will finish it ASAP. |
…Value type and removal of interpreter calls in Func code generation.
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.
The first batch of review comments
package.json
Outdated
@@ -95,7 +95,7 @@ | |||
"@typescript-eslint/parser": "^7.0.2", | |||
"ajv-cli": "^5.0.0", | |||
"cross-env": "^7.0.3", | |||
"cspell": "^8.8.3", | |||
"cspell": "^8.16.0", |
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.
irrelevant change
src/config/configSchema.json
Outdated
@@ -51,6 +51,16 @@ | |||
"default": false, | |||
"description": "False by default. If set to true, enables generation of a getter the information on the interfaces provided by the contract.\n\nRead more about supported interfaces: https://docs.tact-lang.org/ref/evolution/OTP-001." | |||
}, | |||
"skipTactOptimizationPhase": { |
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.
"skipTactOptimizationPhase": { | |
"skipPartialEval": { |
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.
Renaming it to skipPartialEval would imply that the optimization phase only includes partial evaluation, but it will also include other steps, like constant propagation in the future (and probably other techniques as well). Is there a way to keep the "skipTactOptimizationPhase" for the entire phase, but to include sub-options for each of the steps in the optimization phase?
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 need to come up with a plan for those optimizations and design a config for those then
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.
I guess it is enough to have an option for each step inside the optimization phase and avoid the complication of having an option for the entire phase.
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 need to come up with a plan for those optimizations and design a config for those then
Yeah. All right. I will change the option to skipPartialEval, and once we have a plan, I will carry out the changes in a separate issue/PR.
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.
I'll open an issue-task regarding this.
src/optimizer/optimization-phase.ts
Outdated
export function dumpTactCode(ast: AstModule, file: string) { | ||
const program = prettyPrint(ast); | ||
|
||
void writeFile(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.
you cannot use Node's file system here as it will break when Tact runs in the browser, use VFS instead
src/config/configSchema.json
Outdated
"default": false, | ||
"description": "False by default. If set to true, skips the Tact code optimization phase." | ||
}, | ||
"dumpCodeBeforeAndAfterTactOptimizationPhase": { |
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 needs to be split into two options, dumpAst
and dumpAfterPartialEval
yarn.lock
Outdated
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.
undo this change
src/pipeline/build.ts
Outdated
if (config.options?.dumpCodeBeforeAndAfterTactOptimizationPhase) { | ||
dumpTactCode( | ||
optimizationCtx.originalAst, | ||
config.output + `/${config.name}-unoptimized-tact-dump.tact`, |
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.
use path.join
instead of +
src/pipeline/build.ts
Outdated
if (config.options?.dumpCodeBeforeAndAfterTactOptimizationPhase) { | ||
dumpTactCode( | ||
optimizationCtx.modifiedAst, | ||
config.output + `/${config.name}-optimized-tact-dump.tact`, |
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.
use path.join
instead of +
import { prepareAstForOptimization } from "../optimization-phase"; | ||
|
||
describe("expression-simplification", () => { | ||
for (const r of loadCases(__dirname + "/success/")) { |
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.
use path.join
instead of +
expect(getAllExpressionTypes(optCtx.ctx)).toMatchSnapshot(); | ||
}); | ||
} | ||
for (const r of loadCases(__dirname + "/failed/")) { |
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.
use path.join
instead of +
src/optimizer/optimization-phase.ts
Outdated
} else { | ||
throwInternalCompilerError(`kind ${ast.kind} is not a receiver`); | ||
} | ||
} |
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.
all ensureX
functions need to go to ast/...
…, split dumpCodeBeforeAndAfterTactOptimizationPhase into dumpAst and dumpAfterPartialEval.
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 should not unfreeze the AST. Produce a new AST instead.
Issue
Closes #1449.
Closes #970. (As a byproduct, a test was added).
This PR adds an optimization phase in between the typechecking and FunC code generation phases. Currently, the optimization phase only carries out expression simplification 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.
IMPORTANT: This PR removes calls to the interpreter that were previously being carried out during the FunC code generation phase. The FunC generation phase should NOT be calling constant evaluation (this is a job of the optimization phase).
The PR adds two CLI compiler options:
NOTE: This PR does not modify the AST one gets from calling
getRawAST
on theCompilerContext
objectctx
. 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
.Checklist