mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-05-02 10:22:15 +00:00
Flow improvements
Makes the internal API for working with flows more explicit in an attempt to avoid further issues. Also tackles remaining issues with 'continue' statements in 'for' loops.
This commit is contained in:
parent
2e5a42d233
commit
0d64c9661a
2
dist/assemblyscript.js
vendored
2
dist/assemblyscript.js
vendored
File diff suppressed because one or more lines are too long
2
dist/assemblyscript.js.map
vendored
2
dist/assemblyscript.js.map
vendored
File diff suppressed because one or more lines are too long
182
src/compiler.ts
182
src/compiler.ts
@ -1474,7 +1474,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
let stmt = this.compileStatement(statements[i]);
|
||||
if (getExpressionId(stmt) != ExpressionId.Nop) {
|
||||
stmts[count++] = stmt;
|
||||
if (flow.isAny(FlowFlags.TERMINATED)) break;
|
||||
if (flow.isAny(FlowFlags.ANY_TERMINATING)) break;
|
||||
}
|
||||
}
|
||||
stmts.length = count;
|
||||
@ -1483,10 +1483,9 @@ export class Compiler extends DiagnosticEmitter {
|
||||
|
||||
compileBlockStatement(statement: BlockStatement): ExpressionRef {
|
||||
var statements = statement.statements;
|
||||
|
||||
// Not actually a branch, but can contain its own scoped variables.
|
||||
var blockFlow = this.currentFunction.flow.enterBranchOrScope();
|
||||
this.currentFunction.flow = blockFlow;
|
||||
var parentFlow = this.currentFunction.flow;
|
||||
var flow = parentFlow.fork();
|
||||
this.currentFunction.flow = flow;
|
||||
|
||||
var stmts = this.compileStatements(statements);
|
||||
var stmt = stmts.length == 0
|
||||
@ -1495,10 +1494,8 @@ export class Compiler extends DiagnosticEmitter {
|
||||
? stmts[0]
|
||||
: this.module.createBlock(null, stmts,getExpressionType(stmts[stmts.length - 1]));
|
||||
|
||||
// Switch back to the parent flow
|
||||
var parentFlow = blockFlow.leaveBranchOrScope();
|
||||
this.currentFunction.flow = parentFlow;
|
||||
parentFlow.inherit(blockFlow);
|
||||
this.currentFunction.flow = flow.free();
|
||||
parentFlow.inherit(flow);
|
||||
return stmt;
|
||||
}
|
||||
|
||||
@ -1553,7 +1550,8 @@ export class Compiler extends DiagnosticEmitter {
|
||||
var module = this.module;
|
||||
|
||||
var label = currentFunction.enterBreakContext();
|
||||
var flow = currentFunction.flow.enterBranchOrScope();
|
||||
var parentFlow = currentFunction.flow;
|
||||
var flow = parentFlow.fork();
|
||||
currentFunction.flow = flow;
|
||||
var breakLabel = "break|" + label;
|
||||
flow.breakLabel = breakLabel;
|
||||
@ -1565,22 +1563,30 @@ export class Compiler extends DiagnosticEmitter {
|
||||
this.compileExpression(statement.condition, Type.i32, ConversionKind.NONE, WrapMode.NONE),
|
||||
this.currentType
|
||||
);
|
||||
// TODO: check if condition is always false and if so, omit it?
|
||||
// TODO: check if condition is always false and if so, omit it (just a block)
|
||||
|
||||
// Switch back to the parent flow
|
||||
currentFunction.flow = flow.leaveBranchOrScope();
|
||||
currentFunction.flow = flow.free();
|
||||
currentFunction.leaveBreakContext();
|
||||
var terminated = flow.isAny(FlowFlags.ANY_TERMINATING);
|
||||
flow.unset(
|
||||
FlowFlags.BREAKS |
|
||||
FlowFlags.CONDITIONALLY_BREAKS |
|
||||
FlowFlags.CONTINUES |
|
||||
FlowFlags.CONDITIONALLY_CONTINUES
|
||||
);
|
||||
parentFlow.inherit(flow);
|
||||
|
||||
return module.createBlock(breakLabel, [
|
||||
module.createLoop(continueLabel,
|
||||
flow.isAny(FlowFlags.BREAKS | FlowFlags.CONTINUES | FlowFlags.RETURNS)
|
||||
terminated
|
||||
? body // skip trailing continue if unnecessary
|
||||
: module.createBlock(null, [
|
||||
body,
|
||||
module.createBreak(continueLabel, condExpr)
|
||||
], NativeType.None)
|
||||
)
|
||||
], NativeType.None);
|
||||
], terminated ? NativeType.Unreachable : NativeType.None);
|
||||
}
|
||||
|
||||
compileEmptyStatement(statement: EmptyStatement): ExpressionRef {
|
||||
@ -1601,7 +1607,8 @@ export class Compiler extends DiagnosticEmitter {
|
||||
// possibly declared in its initializer, and break context.
|
||||
var currentFunction = this.currentFunction;
|
||||
var label = currentFunction.enterBreakContext();
|
||||
var flow = currentFunction.flow.enterBranchOrScope();
|
||||
var parentFlow = currentFunction.flow;
|
||||
var flow = parentFlow.fork();
|
||||
currentFunction.flow = flow;
|
||||
var breakLabel = flow.breakLabel = "break|" + label;
|
||||
flow.breakLabel = breakLabel;
|
||||
@ -1615,7 +1622,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
? this.compileStatement(<Statement>statement.initializer)
|
||||
: 0;
|
||||
var condExpr: ExpressionRef = 0;
|
||||
var alwaysTrue = true;
|
||||
var alwaysTrue = false;
|
||||
if (statement.condition) {
|
||||
condExpr = this.makeIsTrueish(
|
||||
this.compileExpressionRetainType(<Expression>statement.condition, Type.bool, WrapMode.NONE),
|
||||
@ -1645,16 +1652,24 @@ export class Compiler extends DiagnosticEmitter {
|
||||
var bodyExpr = this.compileStatement(statement.statement);
|
||||
|
||||
// Switch back to the parent flow
|
||||
var parentFlow = flow.leaveBranchOrScope();
|
||||
if (alwaysTrue) parentFlow.inherit(flow);
|
||||
currentFunction.flow = parentFlow;
|
||||
currentFunction.flow = flow.free();
|
||||
currentFunction.leaveBreakContext();
|
||||
var usesContinue = flow.isAny(FlowFlags.CONTINUES | FlowFlags.CONDITIONALLY_CONTINUES);
|
||||
flow.unset(
|
||||
FlowFlags.BREAKS |
|
||||
FlowFlags.CONDITIONALLY_BREAKS |
|
||||
FlowFlags.CONTINUES |
|
||||
FlowFlags.CONDITIONALLY_CONTINUES
|
||||
);
|
||||
var terminated = alwaysTrue && flow.isAny(FlowFlags.ANY_TERMINATING);
|
||||
if (alwaysTrue) parentFlow.inherit(flow);
|
||||
else parentFlow.inheritConditional(flow);
|
||||
|
||||
var breakBlock = new Array<ExpressionRef>(); // outer 'break' block
|
||||
if (initExpr) breakBlock.push(initExpr);
|
||||
|
||||
var repeatBlock = new Array<ExpressionRef>(); // block repeating the loop
|
||||
if (parentFlow.isAny(FlowFlags.CONTINUES | FlowFlags.CONDITIONALLY_CONTINUES)) {
|
||||
if (usesContinue) {
|
||||
repeatBlock.push(
|
||||
module.createBlock(continueLabel, [ // inner 'continue' block
|
||||
module.createBreak(breakLabel, module.createUnary(UnaryOp.EqzI32, condExpr)),
|
||||
@ -1678,16 +1693,13 @@ export class Compiler extends DiagnosticEmitter {
|
||||
)
|
||||
);
|
||||
|
||||
var expr = module.createBlock(breakLabel, breakBlock, NativeType.None);
|
||||
|
||||
// If the loop is guaranteed to run and return, append a hint for Binaryen
|
||||
if (flow.isAny(FlowFlags.RETURNS | FlowFlags.THROWS)) {
|
||||
expr = module.createBlock(null, [
|
||||
expr,
|
||||
module.createUnreachable()
|
||||
]);
|
||||
}
|
||||
return expr;
|
||||
return module.createBlock(
|
||||
breakLabel,
|
||||
breakBlock,
|
||||
terminated
|
||||
? NativeType.Unreachable
|
||||
: NativeType.None
|
||||
);
|
||||
}
|
||||
|
||||
compileIfStatement(statement: IfStatement): ExpressionRef {
|
||||
@ -1728,20 +1740,21 @@ export class Compiler extends DiagnosticEmitter {
|
||||
}
|
||||
|
||||
// Each arm initiates a branch
|
||||
var ifTrueFlow = currentFunction.flow.enterBranchOrScope();
|
||||
var parentFlow = currentFunction.flow;
|
||||
var ifTrueFlow = parentFlow.fork();
|
||||
currentFunction.flow = ifTrueFlow;
|
||||
var ifTrueExpr = this.compileStatement(ifTrue);
|
||||
currentFunction.flow = ifTrueFlow.leaveBranchOrScope();
|
||||
currentFunction.flow = ifTrueFlow.free();
|
||||
|
||||
var ifFalseFlow: Flow | null;
|
||||
var ifFalseExpr: ExpressionRef = 0;
|
||||
if (ifFalse) {
|
||||
ifFalseFlow = currentFunction.flow.enterBranchOrScope();
|
||||
let ifFalseFlow = parentFlow.fork();
|
||||
currentFunction.flow = ifFalseFlow;
|
||||
ifFalseExpr = this.compileStatement(ifFalse);
|
||||
let parentFlow = ifFalseFlow.leaveBranchOrScope();
|
||||
currentFunction.flow = parentFlow;
|
||||
currentFunction.flow = ifFalseFlow.free();
|
||||
parentFlow.inheritMutual(ifTrueFlow, ifFalseFlow);
|
||||
} else {
|
||||
parentFlow.inheritConditional(ifTrueFlow);
|
||||
}
|
||||
return module.createIf(condExpr, ifTrueExpr, ifFalseExpr);
|
||||
}
|
||||
@ -1800,6 +1813,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
|
||||
// Everything within a switch uses the same break context
|
||||
var context = currentFunction.enterBreakContext();
|
||||
var parentFlow = currentFunction.flow;
|
||||
|
||||
// introduce a local for evaluating the condition (exactly once)
|
||||
var tempLocal = currentFunction.getTempLocal(Type.u32, false);
|
||||
@ -1850,7 +1864,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
let numStatements = statements.length;
|
||||
|
||||
// Each switch case initiates a new branch
|
||||
let flow = currentFunction.flow.enterBranchOrScope();
|
||||
let flow = parentFlow.fork();
|
||||
currentFunction.flow = flow;
|
||||
let breakLabel = "break|" + context;
|
||||
flow.breakLabel = breakLabel;
|
||||
@ -1865,7 +1879,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
let stmt = this.compileStatement(statements[j]);
|
||||
if (getExpressionId(stmt) != ExpressionId.Nop) {
|
||||
stmts[count++] = stmt;
|
||||
if (flow.isAny(FlowFlags.TERMINATED)) {
|
||||
if (flow.isAny(FlowFlags.ANY_TERMINATING)) {
|
||||
terminated = true;
|
||||
break;
|
||||
}
|
||||
@ -1880,18 +1894,21 @@ export class Compiler extends DiagnosticEmitter {
|
||||
}
|
||||
|
||||
// Switch back to the parent flow
|
||||
currentFunction.flow = flow.leaveBranchOrScope(false);
|
||||
flow.unset(
|
||||
FlowFlags.BREAKS |
|
||||
FlowFlags.CONDITIONALLY_BREAKS
|
||||
);
|
||||
currentFunction.flow = flow.free();
|
||||
currentBlock = module.createBlock(nextLabel, stmts, NativeType.None); // must be a labeled block
|
||||
}
|
||||
currentFunction.leaveBreakContext();
|
||||
|
||||
// If the switch has a default (guaranteed to handle any value), propagate common flags
|
||||
if (defaultIndex >= 0) {
|
||||
let flow = currentFunction.flow;
|
||||
if (alwaysReturns) flow.set(FlowFlags.RETURNS);
|
||||
if (alwaysReturnsWrapped) flow.set(FlowFlags.RETURNS_WRAPPED);
|
||||
if (alwaysThrows) flow.set(FlowFlags.THROWS);
|
||||
if (alwaysAllocates) flow.set(FlowFlags.ALLOCATES);
|
||||
if (alwaysReturns) parentFlow.set(FlowFlags.RETURNS);
|
||||
if (alwaysReturnsWrapped) parentFlow.set(FlowFlags.RETURNS_WRAPPED);
|
||||
if (alwaysThrows) parentFlow.set(FlowFlags.THROWS);
|
||||
if (alwaysAllocates) parentFlow.set(FlowFlags.ALLOCATES);
|
||||
}
|
||||
return currentBlock;
|
||||
}
|
||||
@ -2109,7 +2126,8 @@ export class Compiler extends DiagnosticEmitter {
|
||||
// Statements initiate a new branch with its own break context
|
||||
var currentFunction = this.currentFunction;
|
||||
var label = currentFunction.enterBreakContext();
|
||||
var flow = currentFunction.flow.enterBranchOrScope();
|
||||
var parentFlow = currentFunction.flow;
|
||||
var flow = parentFlow.fork();
|
||||
currentFunction.flow = flow;
|
||||
var breakLabel = "break|" + label;
|
||||
flow.breakLabel = breakLabel;
|
||||
@ -2117,17 +2135,26 @@ export class Compiler extends DiagnosticEmitter {
|
||||
flow.continueLabel = continueLabel;
|
||||
|
||||
var body = this.compileStatement(statement.statement);
|
||||
var alwaysReturns = false; // CONDITION_IS_ALWAYS_TRUE && flow.is(FlowFlags.RETURNS);
|
||||
// TODO: evaluate if condition is always true
|
||||
var alwaysTrue = false; // TODO
|
||||
var alwaysReturns = alwaysTrue && flow.is(FlowFlags.RETURNS);
|
||||
var terminated = flow.isAny(FlowFlags.ANY_TERMINATING);
|
||||
|
||||
// Switch back to the parent flow
|
||||
currentFunction.flow = flow.leaveBranchOrScope();
|
||||
currentFunction.flow = flow.free();
|
||||
currentFunction.leaveBreakContext();
|
||||
flow.unset(
|
||||
FlowFlags.BREAKS |
|
||||
FlowFlags.CONDITIONALLY_BREAKS |
|
||||
FlowFlags.CONTINUES |
|
||||
FlowFlags.CONDITIONALLY_CONTINUES
|
||||
);
|
||||
if (alwaysTrue) parentFlow.inherit(flow);
|
||||
else parentFlow.inheritConditional(flow);
|
||||
|
||||
var expr = module.createBlock(breakLabel, [
|
||||
module.createLoop(continueLabel,
|
||||
module.createIf(condExpr,
|
||||
flow.isAny(FlowFlags.CONTINUES | FlowFlags.BREAKS | FlowFlags.RETURNS)
|
||||
terminated
|
||||
? body // skip trailing continue if unnecessary
|
||||
: module.createBlock(null, [
|
||||
body,
|
||||
@ -2135,15 +2162,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
], NativeType.None)
|
||||
)
|
||||
)
|
||||
], NativeType.None);
|
||||
|
||||
// If the loop is guaranteed to run and return, propagate that and append a hint
|
||||
if (alwaysReturns) {
|
||||
expr = module.createBlock(null, [
|
||||
expr,
|
||||
module.createUnreachable()
|
||||
]);
|
||||
}
|
||||
], alwaysReturns ? NativeType.Unreachable : NativeType.None);
|
||||
return expr;
|
||||
}
|
||||
|
||||
@ -5282,7 +5301,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
let stmt = this.compileStatement(statements[i]);
|
||||
if (getExpressionId(stmt) != ExpressionId.Nop) {
|
||||
body.push(stmt);
|
||||
if (flow.isAny(FlowFlags.TERMINATED)) break;
|
||||
if (flow.isAny(FlowFlags.ANY_TERMINATING)) break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -5304,7 +5323,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
this.currentType = returnType;
|
||||
|
||||
// Check that all branches are terminated
|
||||
if (returnType != Type.void && !flow.isAny(FlowFlags.TERMINATED)) {
|
||||
if (returnType != Type.void && !flow.isAny(FlowFlags.ANY_TERMINATING)) {
|
||||
this.error(
|
||||
DiagnosticCode.A_function_whose_declared_type_is_not_void_must_return_a_value,
|
||||
declaration.signature.returnType.range
|
||||
@ -6482,6 +6501,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
var ifThen = expression.ifThen;
|
||||
var ifElse = expression.ifElse;
|
||||
var currentFunction = this.currentFunction;
|
||||
var parentFlow = currentFunction.flow;
|
||||
|
||||
var condExpr = this.makeIsTrueish(
|
||||
this.compileExpressionRetainType(expression.condition, Type.bool, WrapMode.NONE),
|
||||
@ -6511,40 +6531,20 @@ export class Compiler extends DiagnosticEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
var ifThenExpr: ExpressionRef;
|
||||
var ifElseExpr: ExpressionRef;
|
||||
var ifThenType: Type;
|
||||
var ifElseType: Type;
|
||||
var ifThenFlow = parentFlow.fork();
|
||||
currentFunction.flow = ifThenFlow;
|
||||
var ifThenExpr = this.compileExpressionRetainType(ifThen, contextualType, WrapMode.NONE);
|
||||
var ifThenType = this.currentType;
|
||||
ifThenFlow.free();
|
||||
|
||||
// if part of a constructor, keep track of memory allocations
|
||||
if (currentFunction.is(CommonFlags.CONSTRUCTOR)) {
|
||||
let flow = currentFunction.flow;
|
||||
var ifElseFlow = parentFlow.fork();
|
||||
currentFunction.flow = ifElseFlow;
|
||||
var ifElseExpr = this.compileExpressionRetainType(ifElse, contextualType, WrapMode.NONE);
|
||||
var ifElseType = this.currentType;
|
||||
currentFunction.flow = ifElseFlow.free();
|
||||
|
||||
flow = flow.enterBranchOrScope();
|
||||
currentFunction.flow = flow;
|
||||
ifThenExpr = this.compileExpressionRetainType(ifThen, contextualType, WrapMode.NONE);
|
||||
ifThenType = this.currentType;
|
||||
let ifThenAllocates = flow.is(FlowFlags.ALLOCATES);
|
||||
flow = flow.leaveBranchOrScope();
|
||||
currentFunction.flow = flow;
|
||||
parentFlow.inheritMutual(ifThenFlow, ifElseFlow);
|
||||
|
||||
flow = flow.enterBranchOrScope();
|
||||
currentFunction.flow = flow;
|
||||
ifElseExpr = this.compileExpressionRetainType(ifElse, contextualType, WrapMode.NONE);
|
||||
ifElseType = this.currentType;
|
||||
let ifElseAllocates = flow.is(FlowFlags.ALLOCATES);
|
||||
flow = flow.leaveBranchOrScope();
|
||||
currentFunction.flow = flow;
|
||||
|
||||
if (ifThenAllocates && ifElseAllocates) flow.set(FlowFlags.ALLOCATES);
|
||||
|
||||
// otherwise simplify
|
||||
} else {
|
||||
ifThenExpr = this.compileExpressionRetainType(ifThen, contextualType, WrapMode.NONE);
|
||||
ifThenType = this.currentType;
|
||||
ifElseExpr = this.compileExpressionRetainType(ifElse, contextualType, WrapMode.NONE);
|
||||
ifElseType = this.currentType;
|
||||
}
|
||||
var commonType = Type.commonCompatible(ifThenType, ifElseType, false);
|
||||
if (!commonType) {
|
||||
this.error(
|
||||
|
140
src/program.ts
140
src/program.ts
@ -3715,37 +3715,63 @@ export const enum FlowFlags {
|
||||
/** No specific conditions. */
|
||||
NONE = 0,
|
||||
|
||||
// categorical
|
||||
|
||||
/** This branch always returns. */
|
||||
RETURNS = 1 << 0,
|
||||
/** This branch always returns a wrapped value. */
|
||||
RETURNS_WRAPPED = 1 << 1,
|
||||
/** This branch always throws. */
|
||||
THROWS = 1 << 1,
|
||||
THROWS = 1 << 2,
|
||||
/** This branch always breaks. */
|
||||
BREAKS = 1 << 2,
|
||||
BREAKS = 1 << 3,
|
||||
/** This branch always continues. */
|
||||
CONTINUES = 1 << 3,
|
||||
CONTINUES = 1 << 4,
|
||||
/** This branch always allocates. Constructors only. */
|
||||
ALLOCATES = 1 << 4,
|
||||
ALLOCATES = 1 << 5,
|
||||
|
||||
// conditional
|
||||
|
||||
/** This branch conditionally returns in a child branch. */
|
||||
CONDITIONALLY_RETURNS = 1 << 5,
|
||||
CONDITIONALLY_RETURNS = 1 << 6,
|
||||
/** This branch conditionally throws in a child branch. */
|
||||
CONDITIONALLY_THROWS = 1 << 6,
|
||||
CONDITIONALLY_THROWS = 1 << 7,
|
||||
/** This branch conditionally breaks in a child branch. */
|
||||
CONDITIONALLY_BREAKS = 1 << 7,
|
||||
CONDITIONALLY_BREAKS = 1 << 8,
|
||||
/** This branch conditionally continues in a child branch. */
|
||||
CONDITIONALLY_CONTINUES = 1 << 8,
|
||||
CONDITIONALLY_CONTINUES = 1 << 9,
|
||||
/** This branch conditionally allocates in a child branch. Constructors only. */
|
||||
CONDITIONALLY_ALLOCATES = 1 << 9,
|
||||
CONDITIONALLY_ALLOCATES = 1 << 10,
|
||||
|
||||
// special
|
||||
|
||||
/** This branch is part of inlining a function. */
|
||||
INLINE_CONTEXT = 1 << 10,
|
||||
INLINE_CONTEXT = 1 << 11,
|
||||
/** This branch explicitly requests no bounds checking. */
|
||||
UNCHECKED_CONTEXT = 1 << 11,
|
||||
/** This branch returns a properly wrapped value. */
|
||||
RETURNS_WRAPPED = 1 << 12,
|
||||
UNCHECKED_CONTEXT = 1 << 12,
|
||||
|
||||
/** This branch is terminated if any of these flags is set. */
|
||||
TERMINATED = FlowFlags.RETURNS | FlowFlags.THROWS | FlowFlags.BREAKS | FlowFlags.CONTINUES
|
||||
// masks
|
||||
|
||||
/** Any terminating flag. */
|
||||
ANY_TERMINATING = FlowFlags.RETURNS
|
||||
| FlowFlags.THROWS
|
||||
| FlowFlags.BREAKS
|
||||
| FlowFlags.CONTINUES,
|
||||
|
||||
/** Any categorical flag. */
|
||||
ANY_CATEGORICAL = FlowFlags.RETURNS
|
||||
| FlowFlags.RETURNS_WRAPPED
|
||||
| FlowFlags.THROWS
|
||||
| FlowFlags.BREAKS
|
||||
| FlowFlags.CONTINUES
|
||||
| FlowFlags.ALLOCATES,
|
||||
|
||||
/** Any conditional flag. */
|
||||
ANY_CONDITIONAL = FlowFlags.CONDITIONALLY_RETURNS
|
||||
| FlowFlags.CONDITIONALLY_THROWS
|
||||
| FlowFlags.CONDITIONALLY_BREAKS
|
||||
| FlowFlags.CONDITIONALLY_CONTINUES
|
||||
| FlowFlags.CONDITIONALLY_ALLOCATES
|
||||
}
|
||||
|
||||
/** A control flow evaluator. */
|
||||
@ -3801,8 +3827,8 @@ export class Flow {
|
||||
/** Unsets the specified flag or flags. */
|
||||
unset(flag: FlowFlags): void { this.flags &= ~flag; }
|
||||
|
||||
/** Enters a new branch or scope and returns the new flow. */
|
||||
enterBranchOrScope(): Flow {
|
||||
/** Forks this flow to a child flow. */
|
||||
fork(): Flow {
|
||||
var branch = new Flow();
|
||||
branch.parent = this;
|
||||
branch.flags = this.flags;
|
||||
@ -3817,12 +3843,10 @@ export class Flow {
|
||||
return branch;
|
||||
}
|
||||
|
||||
/** Leaves the current branch or scope and returns the parent flow. */
|
||||
leaveBranchOrScope(propagate: bool = true): Flow {
|
||||
/** Frees this flow's scoped variables. */
|
||||
free(): Flow {
|
||||
var parent = assert(this.parent);
|
||||
|
||||
// Free block-scoped locals
|
||||
if (this.scopedLocals) {
|
||||
if (this.scopedLocals) { // free block-scoped locals
|
||||
for (let scopedLocal of this.scopedLocals.values()) {
|
||||
if (scopedLocal.is(CommonFlags.SCOPED)) { // otherwise an alias
|
||||
this.currentFunction.freeTempLocal(scopedLocal);
|
||||
@ -3830,25 +3854,6 @@ export class Flow {
|
||||
}
|
||||
this.scopedLocals = null;
|
||||
}
|
||||
|
||||
// Propagate conditionaal flags to parent
|
||||
if (propagate) {
|
||||
if (this.is(FlowFlags.RETURNS)) {
|
||||
parent.set(FlowFlags.CONDITIONALLY_RETURNS);
|
||||
}
|
||||
if (this.is(FlowFlags.THROWS)) {
|
||||
parent.set(FlowFlags.CONDITIONALLY_THROWS);
|
||||
}
|
||||
if (this.is(FlowFlags.BREAKS) && parent.breakLabel == this.breakLabel) {
|
||||
parent.set(FlowFlags.CONDITIONALLY_BREAKS);
|
||||
}
|
||||
if (this.is(FlowFlags.CONTINUES) && parent.continueLabel == this.continueLabel) {
|
||||
parent.set(FlowFlags.CONDITIONALLY_CONTINUES);
|
||||
}
|
||||
if (this.is(FlowFlags.ALLOCATES)) {
|
||||
parent.set(FlowFlags.CONDITIONALLY_ALLOCATES);
|
||||
}
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
@ -3982,36 +3987,43 @@ export class Flow {
|
||||
else this.wrappedLocals = map;
|
||||
}
|
||||
|
||||
/** Inherits flags and local wrap states from the specified flow (e.g. on inner block). */
|
||||
/** Inherits flags and local wrap states from the specified flow (e.g. blocks). */
|
||||
inherit(other: Flow): void {
|
||||
this.flags |= other.flags & (
|
||||
FlowFlags.RETURNS |
|
||||
FlowFlags.RETURNS_WRAPPED |
|
||||
FlowFlags.THROWS |
|
||||
FlowFlags.BREAKS |
|
||||
FlowFlags.CONTINUES |
|
||||
FlowFlags.ALLOCATES
|
||||
);
|
||||
this.flags |= other.flags & (FlowFlags.ANY_CATEGORICAL | FlowFlags.ANY_CONDITIONAL);
|
||||
this.wrappedLocals = other.wrappedLocals;
|
||||
this.wrappedLocalsExt = other.wrappedLocalsExt; // no need to slice because other flow is finished
|
||||
}
|
||||
|
||||
/** Inherits mutual flags and local wrap states from the specified flows (e.g. on then/else branches). */
|
||||
/** Inherits categorical flags as conditional flags from the specified flow (e.g. then without else). */
|
||||
inheritConditional(other: Flow): void {
|
||||
if (other.is(FlowFlags.RETURNS)) {
|
||||
this.set(FlowFlags.CONDITIONALLY_RETURNS);
|
||||
}
|
||||
if (other.is(FlowFlags.THROWS)) {
|
||||
this.set(FlowFlags.CONDITIONALLY_THROWS);
|
||||
}
|
||||
if (other.is(FlowFlags.BREAKS) && other.breakLabel == this.breakLabel) {
|
||||
this.set(FlowFlags.CONDITIONALLY_BREAKS);
|
||||
}
|
||||
if (other.is(FlowFlags.CONTINUES) && other.continueLabel == this.continueLabel) {
|
||||
this.set(FlowFlags.CONDITIONALLY_CONTINUES);
|
||||
}
|
||||
if (other.is(FlowFlags.ALLOCATES)) {
|
||||
this.set(FlowFlags.CONDITIONALLY_ALLOCATES);
|
||||
}
|
||||
}
|
||||
|
||||
/** Inherits mutual flags and local wrap states from the specified flows (e.g. then with else). */
|
||||
inheritMutual(left: Flow, right: Flow): void {
|
||||
// flags set in both arms
|
||||
this.flags |= left.flags & right.flags & (
|
||||
FlowFlags.RETURNS |
|
||||
FlowFlags.RETURNS_WRAPPED |
|
||||
FlowFlags.THROWS |
|
||||
FlowFlags.BREAKS |
|
||||
FlowFlags.CONTINUES |
|
||||
FlowFlags.ALLOCATES
|
||||
);
|
||||
// categorical flags set in both arms
|
||||
this.flags |= left.flags & right.flags & FlowFlags.ANY_CATEGORICAL;
|
||||
|
||||
// conditional flags set in at least one arm
|
||||
this.flags |= left.flags & FlowFlags.ANY_CONDITIONAL;
|
||||
this.flags |= right.flags & FlowFlags.ANY_CONDITIONAL;
|
||||
|
||||
// locals wrapped in both arms
|
||||
this.wrappedLocals = i64_and(
|
||||
left.wrappedLocals,
|
||||
right.wrappedLocals
|
||||
);
|
||||
this.wrappedLocals = i64_and(left.wrappedLocals, right.wrappedLocals);
|
||||
var leftExt = left.wrappedLocalsExt;
|
||||
var rightExt = right.wrappedLocalsExt;
|
||||
if (leftExt != null && rightExt != null) {
|
||||
|
@ -9,6 +9,7 @@
|
||||
(start $start)
|
||||
(func $start (; 1 ;) (type $v)
|
||||
(local $0 i32)
|
||||
(local $1 i32)
|
||||
(block $break|0
|
||||
(set_global $for/i
|
||||
(i32.const 0)
|
||||
@ -153,5 +154,53 @@
|
||||
(unreachable)
|
||||
)
|
||||
)
|
||||
(block $break|6
|
||||
(set_local $0
|
||||
(i32.const 0)
|
||||
)
|
||||
(loop $repeat|6
|
||||
(br_if $break|6
|
||||
(i32.ge_s
|
||||
(get_local $0)
|
||||
(i32.const 10)
|
||||
)
|
||||
)
|
||||
(block $break|7
|
||||
(set_local $1
|
||||
(i32.const 0)
|
||||
)
|
||||
(loop $repeat|7
|
||||
(block $continue|7
|
||||
(br_if $break|7
|
||||
(i32.ge_s
|
||||
(get_local $1)
|
||||
(i32.const 10)
|
||||
)
|
||||
)
|
||||
(br_if $continue|7
|
||||
(i32.eq
|
||||
(get_local $0)
|
||||
(get_local $1)
|
||||
)
|
||||
)
|
||||
)
|
||||
(set_local $1
|
||||
(i32.add
|
||||
(get_local $1)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
(br $repeat|7)
|
||||
)
|
||||
)
|
||||
(set_local $0
|
||||
(i32.add
|
||||
(get_local $0)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
(br $repeat|6)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -17,3 +17,9 @@ for (;;) if (--i == 0) break;
|
||||
|
||||
for (var k: i32 = 0; k < 10; ++k) continue;
|
||||
assert(k == 10);
|
||||
|
||||
for (let i = 0; i < 10; i++) { // without continue block
|
||||
for (let j = 0; j < 10; j++) { // with continue block
|
||||
if (i === j) continue;
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@
|
||||
(func $start (; 1 ;) (type $v)
|
||||
(local $0 i32)
|
||||
(local $1 i32)
|
||||
(local $2 i32)
|
||||
(local $3 i32)
|
||||
(block $break|0
|
||||
(set_global $for/i
|
||||
(i32.const 0)
|
||||
@ -201,5 +203,58 @@
|
||||
(unreachable)
|
||||
)
|
||||
)
|
||||
(block $break|6
|
||||
(set_local $2
|
||||
(i32.const 0)
|
||||
)
|
||||
(loop $repeat|6
|
||||
(br_if $break|6
|
||||
(i32.eqz
|
||||
(i32.lt_s
|
||||
(get_local $2)
|
||||
(i32.const 10)
|
||||
)
|
||||
)
|
||||
)
|
||||
(block $break|7
|
||||
(set_local $3
|
||||
(i32.const 0)
|
||||
)
|
||||
(loop $repeat|7
|
||||
(block $continue|7
|
||||
(br_if $break|7
|
||||
(i32.eqz
|
||||
(i32.lt_s
|
||||
(get_local $3)
|
||||
(i32.const 10)
|
||||
)
|
||||
)
|
||||
)
|
||||
(if
|
||||
(i32.eq
|
||||
(get_local $2)
|
||||
(get_local $3)
|
||||
)
|
||||
(br $continue|7)
|
||||
)
|
||||
)
|
||||
(set_local $3
|
||||
(i32.add
|
||||
(get_local $3)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
(br $repeat|7)
|
||||
)
|
||||
)
|
||||
(set_local $2
|
||||
(i32.add
|
||||
(get_local $2)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
(br $repeat|6)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user