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