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

Simple constant propagation AST-based analysis #852

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
114aa88
This commit does not introduce new functionality to the interpreter. …
jeshecdom Aug 23, 2024
2ae5c49
Variable values tracing. Currently supports different brances in cond…
jeshecdom Aug 31, 2024
1731548
Added support for variable tracing in loops, try-catch and init() met…
jeshecdom Sep 6, 2024
1bd97f8
Error messages now show the place in the code. Before, error messages…
jeshecdom Sep 6, 2024
6f0888e
Fixes failing test cases during yarn gen:
jeshecdom Sep 6, 2024
bcba0b1
Further fixes to the handling of structs and contracts.
jeshecdom Sep 9, 2024
d7ef552
Added tracking of variable mutation through a mutating function.
jeshecdom Sep 19, 2024
211b768
Merge branch 'main' into issue716
jeshecdom Sep 19, 2024
ec78d68
Run prettier.
jeshecdom Sep 19, 2024
d68a48e
Fixes after merge with main.
jeshecdom Sep 19, 2024
76f0115
Added positive test cases.
jeshecdom Sep 19, 2024
919c6ce
Added documentation
jeshecdom Sep 19, 2024
6a3a11e
Forgot to add test cases for init() function.
jeshecdom Sep 19, 2024
2107ce0
Merge branch 'monadish' into issue716
jeshecdom Oct 7, 2024
0c976dc
Still merging (does not compile).
jeshecdom Oct 10, 2024
45aba08
Preparing to merge with main (current branch does not compile).
jeshecdom Oct 10, 2024
0d926d8
Merge branch 'main' into issue716
jeshecdom Oct 17, 2024
5833c59
Further bug fixes and refactorings after running tests. Moved tests r…
jeshecdom Oct 26, 2024
d59679c
Merge branch 'main' into issue716
jeshecdom Oct 26, 2024
443720c
Small fixes after destructuring statements were merged from main.
jeshecdom Oct 27, 2024
7753ac3
Added fixpoint computation of loops.
jeshecdom Nov 9, 2024
7446d7e
Added handling of short-circuiting of || and && in constant propagation.
jeshecdom Nov 22, 2024
cecf680
Merge branch 'main' into issue716
jeshecdom Nov 22, 2024
2a38e18
Added ABI Functions for structs and maps into the analyzer.
jeshecdom Nov 22, 2024
7d6b697
Used wrong comparison when checking if name is one of the mutation AB…
jeshecdom Nov 22, 2024
d0c91dc
Prettier check.
jeshecdom Nov 22, 2024
eb0918c
Removed use of Set.intersection in constant propagation analyzer for …
jeshecdom Nov 22, 2024
f29d94d
Removed use of MapIterator.filter from Constant Propagation analyzer …
jeshecdom Nov 22, 2024
22c37a6
Removed use of MapIterator.toArray from Constant Propagation analyzer…
jeshecdom Nov 22, 2024
2608a8f
Removed another use of Set.intersection for backwards compatibility. …
jeshecdom Nov 22, 2024
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
107 changes: 80 additions & 27 deletions src/constEval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ import {
import { ExpressionTransformer } from "./optimizer/types";
import { StandardOptimizer } from "./optimizer/standardOptimizer";
import {
Interpreter,
InterpreterConfig,
ensureInt,
evalBinaryOp,
evalUnaryOp,
throwNonFatalErrorConstEval,
} from "./interpreter";
InterpreterConfig,
TactInterpreter,
} from "./interpreters/standard";
import { throwNonFatalErrorConstEval } from "./interpreters/util";

// Utility Exception class to interrupt the execution
// of functions that cannot evaluate a tree fully into a value.
Expand All @@ -46,21 +46,30 @@ function partiallyEvalUnaryOp(
operand: AstExpression,
source: SrcInfo,
ctx: CompilerContext,
interpreter: TactInterpreter,
): AstExpression {
if (operand.kind === "number" && op === "-") {
// emulating negative integer literals
return makeValueExpression(ensureInt(-operand.value, source));
return makeValueExpression(
ensureInt(-operand.value, source),
operand.loc,
);
}

const simplOperand = partiallyEvalExpression(operand, ctx);
const simplOperand = partiallyEvalExpression(operand, ctx, interpreter);

if (isValue(simplOperand)) {
const valueOperand = extractValue(simplOperand as AstValue);
const result = evalUnaryOp(op, valueOperand, simplOperand.loc, source);
const result = evalUnaryOp(
op,
() => valueOperand,
simplOperand.loc,
source,
);
// Wrap the value into a Tree to continue simplifications
return makeValueExpression(result);
return makeValueExpression(result, source);
} else {
const newAst = makeUnaryExpression(op, simplOperand);
const newAst = makeUnaryExpression(op, simplOperand, source);
return optimizer.applyRules(newAst);
}
}
Expand All @@ -71,8 +80,9 @@ function partiallyEvalBinaryOp(
right: AstExpression,
source: SrcInfo,
ctx: CompilerContext,
interpreter: TactInterpreter,
): AstExpression {
const leftOperand = partiallyEvalExpression(left, ctx);
const leftOperand = partiallyEvalExpression(left, ctx, interpreter);

if (isValue(leftOperand)) {
// Because of short-circuiting, we must delay evaluation of the right operand
Expand All @@ -84,7 +94,11 @@ function partiallyEvalBinaryOp(
valueLeftOperand,
// We delay the evaluation of the right operand inside a continuation
() => {
const rightOperand = partiallyEvalExpression(right, ctx);
const rightOperand = partiallyEvalExpression(
right,
ctx,
interpreter,
);
if (isValue(rightOperand)) {
// If the right operand reduces to a value, then we can let the function
// evalBinaryOp finish its normal execution by returning the value
Expand All @@ -104,12 +118,17 @@ function partiallyEvalBinaryOp(
source,
);

return makeValueExpression(result);
return makeValueExpression(result, source);
} catch (e) {
if (e instanceof PartiallyEvaluatedTree) {
// The right operand did not evaluate to a value. Hence,
// time to symbolically simplify the full tree.
const newAst = makeBinaryExpression(op, leftOperand, e.tree);
const newAst = makeBinaryExpression(
op,
leftOperand,
e.tree,
source,
);
return optimizer.applyRules(newAst);
} else {
throw e;
Expand All @@ -119,8 +138,13 @@ function partiallyEvalBinaryOp(
// Since the left operand does not reduce to a value, no immediate short-circuiting will occur.
// Hence, we can partially evaluate the right operand and let the rules
// simplify the tree.
const rightOperand = partiallyEvalExpression(right, ctx);
const newAst = makeBinaryExpression(op, leftOperand, rightOperand);
const rightOperand = partiallyEvalExpression(right, ctx, interpreter);
const newAst = makeBinaryExpression(
op,
leftOperand,
rightOperand,
source,
);
return optimizer.applyRules(newAst);
}
}
Expand All @@ -130,21 +154,23 @@ export function evalConstantExpression(
ctx: CompilerContext,
interpreterConfig?: InterpreterConfig,
): Value {
const interpreter = new Interpreter(ctx, interpreterConfig);
const interpreter = new TactInterpreter(ctx, interpreterConfig);
const result = interpreter.interpretExpression(ast);
return result;
}

export function partiallyEvalExpression(
ast: AstExpression,
ctx: CompilerContext,
interpreterConfig?: InterpreterConfig,
interpreter: TactInterpreter = new TactInterpreter(ctx),
): AstExpression {
const interpreter = new Interpreter(ctx, interpreterConfig);
switch (ast.kind) {
case "id":
try {
return makeValueExpression(interpreter.interpretName(ast));
return makeValueExpression(
interpreter.interpretName(ast),
ast.loc,
);
} catch (e) {
if (e instanceof TactConstEvalError) {
if (!e.fatal) {
Expand All @@ -156,7 +182,10 @@ export function partiallyEvalExpression(
}
case "method_call":
// Does not partially evaluate at the moment. Will attempt to fully evaluate
return makeValueExpression(interpreter.interpretMethodCall(ast));
return makeValueExpression(
interpreter.interpretMethodCall(ast),
ast.loc,
);
case "init_of":
throwNonFatalErrorConstEval(
"initOf is not supported at this moment",
Expand All @@ -168,32 +197,56 @@ export function partiallyEvalExpression(
case "boolean":
return ast;
case "number":
return makeValueExpression(interpreter.interpretNumber(ast));
return makeValueExpression(
interpreter.interpretNumber(ast),
ast.loc,
);
case "string":
return makeValueExpression(interpreter.interpretString(ast));
case "op_unary":
return partiallyEvalUnaryOp(ast.op, ast.operand, ast.loc, ctx);
return makeValueExpression(
interpreter.interpretString(ast),
ast.loc,
);
case "op_unary": // The fact that we are passing the interpreter around, probably signals
// that the partial evaluator itself is an instance of an abstract interpreter
return partiallyEvalUnaryOp(
ast.op,
ast.operand,
ast.loc,
ctx,
interpreter,
);
case "op_binary":
return partiallyEvalBinaryOp(
ast.op,
ast.left,
ast.right,
ast.loc,
ctx,
interpreter,
);
case "conditional":
// Does not partially evaluate at the moment. Will attempt to fully evaluate
return makeValueExpression(interpreter.interpretConditional(ast));
return makeValueExpression(
interpreter.interpretConditional(ast),
ast.loc,
);
case "struct_instance":
// Does not partially evaluate at the moment. Will attempt to fully evaluate
return makeValueExpression(
interpreter.interpretStructInstance(ast),
ast.loc,
);
case "field_access":
// Does not partially evaluate at the moment. Will attempt to fully evaluate
return makeValueExpression(interpreter.interpretFieldAccess(ast));
return makeValueExpression(
interpreter.interpretFieldAccess(ast),
ast.loc,
);
case "static_call":
// Does not partially evaluate at the moment. Will attempt to fully evaluate
return makeValueExpression(interpreter.interpretStaticCall(ast));
return makeValueExpression(
interpreter.interpretStaticCall(ast),
ast.loc,
);
}
}
6 changes: 3 additions & 3 deletions src/generator/writers/writeExpression.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct A {
b: Int;
}

fun main() {
fun main(x: Int?) {
let a: Int = 1;
let b: Int = 2;
let c: Int = a + b;
Expand All @@ -42,7 +42,7 @@ fun main() {
let m: Int = -j.b + a;
let n: Int = -j.b + a + (+b);
let o: Int? = null;
let p: Int? = o!! + 1;
let p: Int? = x!! + 1;
let q: Cell = j.toCell();
}
`;
Expand All @@ -63,7 +63,7 @@ const golden: string[] = [
`((- $j'b) + $a)`,
`(((- $j'b) + $a) + (+ $b))`,
"null()",
"(__tact_not_null($o) + 1)",
"(__tact_not_null($x) + 1)",
`$A$_store_cell(($j'a, $j'b))`,
];

Expand Down
8 changes: 1 addition & 7 deletions src/generator/writers/writeExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,7 @@ export function writePathExpression(path: AstId[]): string {
export function writeExpression(f: AstExpression, wCtx: WriterContext): string {
// literals and constant expressions are covered here
try {
// 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, {
maxLoopIterations: 2n ** 12n,
});
const value = evalConstantExpression(f, wCtx.ctx);
return writeValue(value, wCtx);
} catch (error) {
if (!(error instanceof TactConstEvalError) || error.fatal) throw error;
Expand Down
7 changes: 6 additions & 1 deletion src/grammar/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,12 @@ export type AstNull = {
loc: SrcInfo;
};

export type AstValue = AstNumber | AstBoolean | AstNull | AstString;
export type AstValue =
| AstNumber
| AstBoolean
| AstNull
| AstString
| AstStructInstance;

export type AstConstantAttribute =
| { type: "virtual"; loc: SrcInfo }
Expand Down
Loading
Loading