Skip to content
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

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
bf955e7
First version of the expression simplification phase. Still a couple of
jeshecdom Nov 6, 2024
b297ab2
Further fixes to computing the new AST after expression simplifications.
jeshecdom Nov 15, 2024
50af1d5
Merge branch 'main' into closes970
jeshecdom Nov 15, 2024
c5d8678
Added positive tests.
jeshecdom Nov 18, 2024
e9f02f5
Lint, prettier, spell, knip check
jeshecdom Nov 18, 2024
34b82b6
Merge branch 'main' into closes970
jeshecdom Nov 18, 2024
90bcde3
Addressed issues in review.
jeshecdom Dec 16, 2024
39ce816
Merge branch 'main' into closes970
jeshecdom Jan 16, 2025
d0e55d7
Merge branch 'main' into closes970
jeshecdom Jan 19, 2025
274182e
fix: Failures in tests and yarn gen after merging the refactoring of …
jeshecdom Jan 21, 2025
0c47ed9
Merge branch 'main' into closes970
jeshecdom Jan 21, 2025
c9e02f3
fix(CLI): Optimization phase was showing stack trace in non-verbose m…
jeshecdom Jan 21, 2025
deb9e74
fix: Undo cspell version change.
jeshecdom Jan 21, 2025
d9e7549
fix(CLI): renamed skipTactOptimizationPhase for skipPartialEval. Also…
jeshecdom Jan 21, 2025
e33d97e
fix: Use path.join and VirtualFileSystem.
jeshecdom Jan 21, 2025
21578bc
fix: use Boolean(t).
jeshecdom Jan 21, 2025
9fb7586
refactor: Move all ensureX functions to ast.ts.
jeshecdom Jan 21, 2025
4ee15ce
Merge branch 'main' into closes970
jeshecdom Jan 21, 2025
d60dd12
fix: undo cspell version number.
jeshecdom Jan 21, 2025
270b867
Merge branch 'main' into closes970
jeshecdom Jan 21, 2025
f859692
Merge branch 'main' into closes970
jeshecdom Jan 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

irrelevant change

"eslint": "^8.56.0",
"glob": "^8.1.0",
"husky": "^9.1.5",
Expand Down
70 changes: 15 additions & 55 deletions src/abi/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,7 @@ import { sha256_sync } from "@ton/crypto";
import path from "path";
import { cwd } from "process";
import { posixNormalize } from "../utils/filePath";
import {
ensureSimplifiedString,
ensureString,
interpretEscapeSequences,
} from "../optimizer/interpreter";
import { ensureSimplifiedString } from "../optimizer/interpreter";
import { isLiteral } from "../ast/ast";

export const GlobalFunctions: Map<string, AbiFunction> = new Map([
Expand Down Expand Up @@ -56,12 +52,8 @@ export const GlobalFunctions: Map<string, AbiFunction> = new Map([
);
}
const resolved0 = resolved[0]!;
// FIXME: When optimizer step added, change the following line to:
// const str = ensureSimplifiedString(resolved0).value;
const str = interpretEscapeSequences(
ensureString(resolved0).value,
resolved0.loc,
);
const str = ensureSimplifiedString(resolved0).value;

return toNano(str).toString(10);
},
},
Expand Down Expand Up @@ -113,12 +105,8 @@ export const GlobalFunctions: Map<string, AbiFunction> = new Map([
);
}
const resolved1 = resolved[1]!;
// FIXME: When optimizer step added, change the following line to:
// const str = ensureSimplifiedString(resolved1).value;
const str = interpretEscapeSequences(
ensureString(resolved1).value,
resolved1.loc,
);
const str = ensureSimplifiedString(resolved1).value;

return `throw_unless(${getErrorId(str, ctx.ctx)}, ${writeExpression(resolved[0]!, ctx)})`;
},
},
Expand Down Expand Up @@ -157,12 +145,8 @@ export const GlobalFunctions: Map<string, AbiFunction> = new Map([
);
}
const resolved0 = resolved[0]!;
// FIXME: When optimizer step added, change the following line to:
// const str = ensureSimplifiedString(resolved0).value;
const str = interpretEscapeSequences(
ensureString(resolved0).value,
resolved0.loc,
);
const str = ensureSimplifiedString(resolved0).value;

let address: Address;
try {
address = Address.parse(str);
Expand Down Expand Up @@ -213,12 +197,8 @@ export const GlobalFunctions: Map<string, AbiFunction> = new Map([

// Load cell data
const resolved0 = resolved[0]!;
// FIXME: When optimizer step added, change the following line to:
// const str = ensureSimplifiedString(resolved0).value;
const str = interpretEscapeSequences(
ensureString(resolved0).value,
resolved0.loc,
);
const str = ensureSimplifiedString(resolved0).value;

let c: Cell;
try {
c = Cell.fromBase64(str);
Expand Down Expand Up @@ -380,8 +360,6 @@ export const GlobalFunctions: Map<string, AbiFunction> = new Map([
const resolved0 = resolved[0]!;

if (isLiteral(resolved0)) {
// FIXME: This one does not need fixing, because it is carried out inside a "isLiteral" check.
// Remove this comment once the optimization step is added
const str = ensureSimplifiedString(resolved0).value;
return BigInt(
"0x" + sha256_sync(str).toString("hex"),
Expand Down Expand Up @@ -436,12 +414,8 @@ export const GlobalFunctions: Map<string, AbiFunction> = new Map([

// Load slice data
const resolved0 = resolved[0]!;
// FIXME: When optimizer step added, change the following line to:
// const str = ensureSimplifiedString(resolved0).value;
const str = interpretEscapeSequences(
ensureString(resolved0).value,
resolved0.loc,
);
const str = ensureSimplifiedString(resolved0).value;

let c: Cell;
try {
c = Cell.fromBase64(str);
Expand Down Expand Up @@ -491,12 +465,8 @@ export const GlobalFunctions: Map<string, AbiFunction> = new Map([

// Load slice data
const resolved0 = resolved[0]!;
// FIXME: When optimizer step added, change the following line to:
// const str = ensureSimplifiedString(resolved0).value;
const str = interpretEscapeSequences(
ensureString(resolved0).value,
resolved0.loc,
);
const str = ensureSimplifiedString(resolved0).value;

let c: Cell;
try {
c = beginCell().storeBuffer(Buffer.from(str)).endCell();
Expand Down Expand Up @@ -540,12 +510,7 @@ export const GlobalFunctions: Map<string, AbiFunction> = new Map([

// Load slice data
const resolved0 = resolved[0]!;
// FIXME: When optimizer step added, change the following line to:
// const str = ensureSimplifiedString(resolved0).value;
const str = interpretEscapeSequences(
ensureString(resolved0).value,
resolved0.loc,
);
const str = ensureSimplifiedString(resolved0).value;

if (str.length > 32) {
throwCompilationError(
Expand Down Expand Up @@ -588,12 +553,7 @@ export const GlobalFunctions: Map<string, AbiFunction> = new Map([

// Load slice data
const resolved0 = resolved[0]!;
// FIXME: When optimizer step added, change the following line to:
// const str = ensureSimplifiedString(resolved0).value;
const str = interpretEscapeSequences(
ensureString(resolved0).value,
resolved0.loc,
);
const str = ensureSimplifiedString(resolved0).value;

return `"${str}"c`;
},
Expand Down
82 changes: 82 additions & 0 deletions src/ast/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1119,3 +1119,85 @@ export function checkLiteral<T>(
throwInternalCompilerError("Unrecognized expression kind");
}
}

export function isAstExpression(ast: AstNode): ast is AstExpression {
return checkAstExpression(
ast,
() => true,
() => false,
);
}

export function checkAstExpression<T>(
ast: AstNode,
t: (node: AstExpression) => T,
f: (node: Exclude<AstNode, AstExpression>) => T,
): T {
switch (ast.kind) {
case "null":
case "boolean":
case "number":
case "string":
case "id":
case "struct_instance":
case "method_call":
case "init_of":
case "op_unary":
case "op_binary":
case "conditional":
case "field_access":
case "static_call":
case "address":
case "cell":
case "comment_value":
case "simplified_string":
case "slice":
case "struct_value":
return t(ast);

case "asm_function_def":
case "bounced_message_type":
case "constant_decl":
case "constant_def":
case "contract":
case "contract_init":
case "destruct_end":
case "destruct_mapping":
case "field_decl":
case "func_id":
case "function_attribute":
case "function_decl":
case "function_def":
case "import":
case "map_type":
case "message_decl":
case "module":
case "native_function_decl":
case "optional_type":
case "primitive_type_decl":
case "receiver":
case "statement_assign":
case "statement_augmentedassign":
case "statement_condition":
case "statement_destruct":
case "statement_expression":
case "statement_foreach":
case "statement_let":
case "statement_repeat":
case "statement_return":
case "statement_try":
case "statement_until":
case "statement_while":
case "statement_block":
case "struct_decl":
case "struct_field_initializer":
case "struct_field_value":
case "trait":
case "type_id":
case "typed_parameter":
return f(ast);

default:
throwInternalCompilerError("Unrecognized ast node kind");
}
}
10 changes: 10 additions & 0 deletions src/ast/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ export const getAstUtil = ({ createNode }: FactoryAst) => {
return result as AstExpression;
}

function makeIdExpression(name: string, baseSrc: SrcInfo): AstId {
const result = createNode({
kind: "id",
text: name,
loc: baseSrc,
});
return result as AstId;
}

function makeNumberLiteral(n: bigint, loc: SrcInfo): AstNumber {
const result = createNode({
kind: "number",
Expand Down Expand Up @@ -159,6 +168,7 @@ export const getAstUtil = ({ createNode }: FactoryAst) => {
return {
makeUnaryExpression,
makeBinaryExpression,
makeIdExpression,
makeNumberLiteral,
makeBooleanLiteral,
makeSimplifiedStringLiteral,
Expand Down
10 changes: 10 additions & 0 deletions src/config/configSchema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"skipTactOptimizationPhase": {
"skipPartialEval": {

Copy link
Contributor Author

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?

Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

"type": "boolean",
"default": false,
"description": "False by default. If set to true, skips the Tact code optimization phase."
},
"dumpCodeBeforeAndAfterTactOptimizationPhase": {
Copy link
Member

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

"type": "boolean",
"default": false,
"description": "False by default. If set to true, dumps the code produced before and after the Tact code optimization phase."
},
"experimental": {
"type": "object",
"description": "Experimental options that might be removed in the future. Use with caution!",
Expand Down
8 changes: 8 additions & 0 deletions src/config/parseConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ export const optionsSchema = z
* Read more: https://docs.tact-lang.org/book/contracts#interfaces
*/
interfacesGetter: z.boolean().optional(),
/**
* If set to true, skips the Tact code optimization phase. Default is false.
*/
skipTactOptimizationPhase: z.boolean().optional(),
/**
* If set to true, dumps the code produced before and after the Tact code optimization phase. Default is false.
*/
dumpCodeBeforeAndAfterTactOptimizationPhase: z.boolean().optional(),
/**
* If set to "new", uses new parser. If set to "old", uses legacy parser. Default is "old".
*/
Expand Down
9 changes: 9 additions & 0 deletions src/generator/writers/writeExpression.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import { CompilerContext } from "../../context/context";
import { getParser } from "../../grammar";
import { getAstFactory } from "../../ast/ast";
import { defaultParser } from "../../grammar/grammar";
import {
prepareAstForOptimization,
updateCompilerContext,
} from "../../optimizer/optimization-phase";
import { simplifyAllExpressions } from "../../optimizer/expr-simplification";

const code = `

Expand Down Expand Up @@ -80,6 +85,10 @@ describe("writeExpression", () => {
);
ctx = resolveDescriptors(ctx, ast);
ctx = resolveStatements(ctx, ast);
const optimizationCtx = prepareAstForOptimization(ctx, ast, true);
simplifyAllExpressions(optimizationCtx);
updateCompilerContext(optimizationCtx);
ctx = optimizationCtx.ctx;
const main = getStaticFunction(ctx, "main");
if (main.ast.kind !== "function_def") {
throw Error("Unexpected function kind");
Expand Down
25 changes: 3 additions & 22 deletions src/generator/writers/writeExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import {
AstId,
AstLiteral,
eqNames,
getAstFactory,
idText,
isLiteral,
tryExtractPath,
} from "../../ast/ast";
import {
idTextErr,
TactConstEvalError,
throwCompilationError,
throwInternalCompilerError,
} from "../../error/errors";
Expand Down Expand Up @@ -42,8 +41,6 @@ import {
import { ops } from "./ops";
import { writeCastedExpression } from "./writeFunction";
import { isLvalue } from "../../types/resolveStatements";
import { evalConstantExpression } from "../../optimizer/constEval";
import { getAstUtil } from "../../ast/util";

function isNull(wCtx: WriterContext, expr: AstExpression): boolean {
return getExpType(wCtx.ctx, expr).kind === "null";
Expand Down Expand Up @@ -173,24 +170,8 @@ export function writePathExpression(path: AstId[]): string {

export function writeExpression(f: AstExpression, wCtx: WriterContext): string {
// literals and constant expressions are covered here

// FIXME: Once optimization step is added, remove this try and replace it with this
// conditional:
// if (isLiteral(f)) {
// return writeValue(f, wCtx);
// }
try {
const util = getAstUtil(getAstFactory());
// Let us put a limit of 2 ^ 12 = 4096 iterations on loops to increase compiler responsiveness.
// If a loop takes more than such number of iterations, the interpreter will fail evaluation.
// I think maxLoopIterations should be a command line option in case a user wants to wait more
// during evaluation.
const value = evalConstantExpression(f, wCtx.ctx, util, {
maxLoopIterations: 2n ** 12n,
});
return writeValue(value, wCtx);
} catch (error) {
if (!(error instanceof TactConstEvalError) || error.fatal) throw error;
if (isLiteral(f)) {
return writeValue(f, wCtx);
}

//
Expand Down
Loading
Loading