mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-07-03 16:41:49 +00:00
Control flow evaluator; Support for block-level let/const variables
This commit is contained in:
390
src/compiler.ts
390
src/compiler.ts
@ -42,7 +42,10 @@ import {
|
||||
Parameter,
|
||||
EnumValue,
|
||||
Property,
|
||||
VariableLikeElement
|
||||
VariableLikeElement,
|
||||
Flow,
|
||||
FlowFlags,
|
||||
ElementFlags
|
||||
} from "./program";
|
||||
|
||||
import {
|
||||
@ -112,6 +115,7 @@ import {
|
||||
Type,
|
||||
TypeKind,
|
||||
TypeFlags,
|
||||
|
||||
typesToNativeTypes
|
||||
} from "./types";
|
||||
|
||||
@ -166,17 +170,13 @@ export class Compiler extends DiagnosticEmitter {
|
||||
|
||||
/** Start function being compiled. */
|
||||
startFunction: Function;
|
||||
/** Start function expressions. */
|
||||
/** Start function statements. */
|
||||
startFunctionBody: ExpressionRef[] = new Array();
|
||||
|
||||
/** Current type in compilation. */
|
||||
currentType: Type = Type.void;
|
||||
/** Current function in compilation. */
|
||||
currentFunction: Function;
|
||||
/** Marker indicating whether continue statements are allowed in the current break context. */
|
||||
disallowContinue: bool = true;
|
||||
/** Marker indicating that a new variable, if present, is always a local. Used to distinguish locals from globals in the start function. */
|
||||
variableIsLocal: bool = false;
|
||||
/** Current type in compilation. */
|
||||
currentType: Type = Type.void;
|
||||
|
||||
/** Counting memory offset. */
|
||||
memoryOffset: U64 = new U64(8, 0); // leave space for (any size of) NULL
|
||||
@ -203,7 +203,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
var startFunctionTemplate = new FunctionPrototype(program, "start", "start", null);
|
||||
var startFunctionInstance = new Function(startFunctionTemplate, startFunctionTemplate.internalName, [], [], Type.void, null);
|
||||
this.currentFunction = this.startFunction = startFunctionInstance;
|
||||
}
|
||||
}
|
||||
|
||||
/** Performs compilation of the underlying {@link Program} to a {@link Module}. */
|
||||
compile(): Module {
|
||||
@ -297,8 +297,10 @@ export class Compiler extends DiagnosticEmitter {
|
||||
this.compileNamespaceDeclaration(<NamespaceDeclaration>statement);
|
||||
break;
|
||||
|
||||
case NodeKind.VARIABLE: // global
|
||||
this.compileVariableStatement(<VariableStatement>statement); // always because initializers might have side effects
|
||||
case NodeKind.VARIABLE: // global, always compiled because initializers might have side effects
|
||||
var variableInit = this.compileVariableStatement(<VariableStatement>statement);
|
||||
if (variableInit)
|
||||
this.startFunctionBody.push(variableInit);
|
||||
break;
|
||||
|
||||
case NodeKind.EXPORT:
|
||||
@ -322,7 +324,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
|
||||
// globals
|
||||
|
||||
compileGlobalDeclaration(declaration: VariableDeclaration, isConst: bool): Global | null {
|
||||
compileGlobalDeclaration(declaration: VariableDeclaration): Global | null {
|
||||
var element = this.program.elements.get(declaration.fileLevelInternalName);
|
||||
if (!element || element.kind != ElementKind.GLOBAL)
|
||||
throw new Error("global expected");
|
||||
@ -332,7 +334,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
}
|
||||
|
||||
compileGlobal(global: Global): bool {
|
||||
if (global.isCompiled || global.isBuiltIn)
|
||||
if (global.is(ElementFlags.COMPILED) || global.is(ElementFlags.BUILTIN))
|
||||
return true;
|
||||
|
||||
var declaration = global.declaration;
|
||||
@ -366,10 +368,10 @@ export class Compiler extends DiagnosticEmitter {
|
||||
|
||||
var nativeType = global.type.toNativeType();
|
||||
|
||||
if (global.isDeclared) {
|
||||
if (global.isConstant) {
|
||||
if (global.is(ElementFlags.DECLARED)) {
|
||||
if (global.is(ElementFlags.CONSTANT)) {
|
||||
this.module.addGlobalImport(global.internalName, global.namespace ? global.namespace.simpleName : "env", global.simpleName, nativeType);
|
||||
global.isCompiled = true;
|
||||
global.set(ElementFlags.COMPILED);
|
||||
return true;
|
||||
} else if (declaration) {
|
||||
this.error(DiagnosticCode.Operation_not_supported, declaration.range);
|
||||
@ -379,14 +381,14 @@ export class Compiler extends DiagnosticEmitter {
|
||||
|
||||
var initializeInStart = false;
|
||||
|
||||
if (global.hasConstantValue) {
|
||||
if (global.is(ElementFlags.INLINED)) {
|
||||
initExpr = makeInlineConstant(global, this.module);
|
||||
} else if (declaration) {
|
||||
if (declaration.initializer) {
|
||||
if (!initExpr)
|
||||
initExpr = this.compileExpression(declaration.initializer, global.type);
|
||||
if (_BinaryenExpressionGetId(initExpr) != ExpressionId.Const) {
|
||||
if (!global.isMutable) {
|
||||
if (global.is(ElementFlags.CONSTANT)) {
|
||||
initExpr = this.precomputeExpressionRef(initExpr);
|
||||
if (_BinaryenExpressionGetId(initExpr) != ExpressionId.Const) {
|
||||
this.warning(DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable, declaration.range);
|
||||
@ -406,7 +408,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
var setExpr = this.module.createSetGlobal(internalName, initExpr);
|
||||
this.startFunctionBody.push(setExpr);
|
||||
} else {
|
||||
if (!global.isMutable) {
|
||||
if (global.is(ElementFlags.CONSTANT)) {
|
||||
var exprType = _BinaryenExpressionGetType(initExpr);
|
||||
switch (exprType) {
|
||||
|
||||
@ -429,16 +431,16 @@ export class Compiler extends DiagnosticEmitter {
|
||||
default:
|
||||
throw new Error("concrete type expected");
|
||||
}
|
||||
global.hasConstantValue = true;
|
||||
global.set(ElementFlags.INLINED);
|
||||
if (!declaration || declaration.isTopLevel) { // might be re-exported
|
||||
this.module.addGlobal(internalName, nativeType, global.isMutable, initExpr);
|
||||
this.module.addGlobal(internalName, nativeType, !global.is(ElementFlags.CONSTANT), initExpr);
|
||||
}
|
||||
if (declaration && declaration.range.source.isEntry && declaration.isTopLevelExport)
|
||||
this.module.addGlobalExport(global.internalName, declaration.programLevelInternalName);
|
||||
} else
|
||||
this.module.addGlobal(internalName, nativeType, global.isMutable, initExpr);
|
||||
this.module.addGlobal(internalName, nativeType, !global.is(ElementFlags.CONSTANT), initExpr);
|
||||
}
|
||||
global.isCompiled = true;
|
||||
global.set(ElementFlags.COMPILED);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -452,10 +454,12 @@ export class Compiler extends DiagnosticEmitter {
|
||||
}
|
||||
|
||||
compileEnum(element: Enum): bool {
|
||||
if (element.isCompiled)
|
||||
if (element.is(ElementFlags.COMPILED))
|
||||
return true;
|
||||
|
||||
element.isCompiled = true; // members might reference each other, triggering another compile
|
||||
// members might reference each other, triggering another compile
|
||||
element.set(ElementFlags.COMPILED);
|
||||
|
||||
var previousValue: EnumValue | null = null;
|
||||
if (element.members)
|
||||
for (var member of element.members.values()) {
|
||||
@ -464,7 +468,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
var initInStart = false;
|
||||
var val = <EnumValue>member;
|
||||
var valueDeclaration = val.declaration;
|
||||
if (val.hasConstantValue) {
|
||||
if (val.is(ElementFlags.INLINED)) {
|
||||
if (!element.declaration || element.declaration.isTopLevelExport)
|
||||
this.module.addGlobal(val.internalName, NativeType.I32, false, this.module.createI32(val.constantValue));
|
||||
} else if (valueDeclaration) {
|
||||
@ -474,14 +478,14 @@ export class Compiler extends DiagnosticEmitter {
|
||||
if (_BinaryenExpressionGetId(initExpr) != ExpressionId.Const) {
|
||||
initExpr = this.precomputeExpressionRef(initExpr);
|
||||
if (_BinaryenExpressionGetId(initExpr) != ExpressionId.Const) {
|
||||
if (element.isConstant)
|
||||
if (element.is(ElementFlags.CONSTANT))
|
||||
this.warning(DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable, valueDeclaration.range);
|
||||
initInStart = true;
|
||||
}
|
||||
}
|
||||
} else if (previousValue == null) {
|
||||
initExpr = this.module.createI32(0);
|
||||
} else if (previousValue.hasConstantValue) {
|
||||
} else if (previousValue.is(ElementFlags.INLINED)) {
|
||||
initExpr = this.module.createI32(previousValue.constantValue + 1);
|
||||
} else {
|
||||
// in TypeScript this errors with TS1061, but actually we can do:
|
||||
@ -489,7 +493,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
this.module.createGetGlobal(previousValue.internalName, NativeType.I32),
|
||||
this.module.createI32(1)
|
||||
);
|
||||
if (element.isConstant)
|
||||
if (element.is(ElementFlags.CONSTANT))
|
||||
this.warning(DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable, valueDeclaration.range);
|
||||
initInStart = true;
|
||||
}
|
||||
@ -501,7 +505,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
this.module.addGlobal(val.internalName, NativeType.I32, false, initExpr);
|
||||
if (_BinaryenExpressionGetType(initExpr) == NativeType.I32) {
|
||||
val.constantValue = _BinaryenConstGetValueI32(initExpr);
|
||||
val.hasConstantValue = true;
|
||||
val.set(ElementFlags.INLINED);
|
||||
} else
|
||||
throw new Error("i32 expected");
|
||||
}
|
||||
@ -511,7 +515,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
|
||||
// export values if the enum is exported
|
||||
if (element.declaration && element.declaration.range.source.isEntry && element.declaration.isTopLevelExport) {
|
||||
if (member.hasConstantValue)
|
||||
if (member.is(ElementFlags.INLINED))
|
||||
this.module.addGlobalExport(member.internalName, member.internalName);
|
||||
else if (valueDeclaration)
|
||||
this.warning(DiagnosticCode.Cannot_export_a_mutable_global, valueDeclaration.range);
|
||||
@ -537,14 +541,14 @@ export class Compiler extends DiagnosticEmitter {
|
||||
}
|
||||
|
||||
compileFunction(instance: Function): bool {
|
||||
if (instance.isCompiled)
|
||||
if (instance.is(ElementFlags.COMPILED))
|
||||
return true;
|
||||
|
||||
var declaration = instance.prototype.declaration;
|
||||
if (!declaration)
|
||||
throw new Error("declaration expected"); // built-ins are not compiled here
|
||||
|
||||
if (instance.isDeclared) {
|
||||
if (instance.is(ElementFlags.DECLARED)) {
|
||||
if (declaration.statements) {
|
||||
this.error(DiagnosticCode.An_implementation_cannot_be_declared_in_ambient_contexts, declaration.name.range);
|
||||
return false;
|
||||
@ -555,11 +559,13 @@ export class Compiler extends DiagnosticEmitter {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
instance.isCompiled = true;
|
||||
|
||||
// might trigger compilation of other functions referring to this one
|
||||
instance.set(ElementFlags.COMPILED);
|
||||
|
||||
// compile statements
|
||||
var stmts: ExpressionRef[] | null = null;
|
||||
if (!instance.isDeclared) {
|
||||
if (!instance.is(ElementFlags.DECLARED)) {
|
||||
var previousFunction = this.currentFunction;
|
||||
this.currentFunction = instance;
|
||||
stmts = this.compileStatements(<Statement[]>declaration.statements);
|
||||
@ -589,7 +595,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
typeRef = this.module.addFunctionType(signatureNameParts.join(""), nativeResultType, nativeParamTypes);
|
||||
|
||||
// create the function
|
||||
if (instance.isDeclared) {
|
||||
if (instance.is(ElementFlags.DECLARED)) {
|
||||
this.module.addFunctionImport(instance.internalName, instance.prototype.namespace ? instance.prototype.namespace.simpleName : "env", declaration.name.name, typeRef);
|
||||
} else {
|
||||
this.module.addFunction(instance.internalName, typeRef, typesToNativeTypes(instance.additionalLocals), this.module.createBlock(null, <ExpressionRef[]>stmts, NativeType.None));
|
||||
@ -636,8 +642,11 @@ export class Compiler extends DiagnosticEmitter {
|
||||
break;
|
||||
|
||||
case NodeKind.VARIABLE:
|
||||
if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (<VariableStatement>member).modifiers))
|
||||
this.compileVariableStatement(<VariableStatement>member);
|
||||
if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (<VariableStatement>member).modifiers)) {
|
||||
var variableInit = this.compileVariableStatement(<VariableStatement>member, true);
|
||||
if (variableInit)
|
||||
this.startFunctionBody.push(variableInit);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -655,7 +664,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
switch (element.kind) {
|
||||
|
||||
case ElementKind.CLASS_PROTOTYPE:
|
||||
if ((noTreeShaking || (<ClassPrototype>element).isExported) && !(<ClassPrototype>element).isGeneric)
|
||||
if ((noTreeShaking || (<ClassPrototype>element).is(ElementFlags.EXPORTED)) && !(<ClassPrototype>element).is(ElementFlags.GENERIC))
|
||||
this.compileClassUsingTypeArguments(<ClassPrototype>element, []);
|
||||
break;
|
||||
|
||||
@ -664,7 +673,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
break;
|
||||
|
||||
case ElementKind.FUNCTION_PROTOTYPE:
|
||||
if ((noTreeShaking || (<FunctionPrototype>element).isExported) && !(<FunctionPrototype>element).isGeneric)
|
||||
if ((noTreeShaking || (<FunctionPrototype>element).is(ElementFlags.EXPORTED)) && !(<FunctionPrototype>element).is(ElementFlags.GENERIC))
|
||||
this.compileFunctionUsingTypeArguments(<FunctionPrototype>element, []);
|
||||
break;
|
||||
|
||||
@ -692,7 +701,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
switch (element.kind) {
|
||||
|
||||
case ElementKind.CLASS_PROTOTYPE:
|
||||
if (!(<ClassPrototype>element).isGeneric)
|
||||
if (!(<ClassPrototype>element).is(ElementFlags.GENERIC))
|
||||
this.compileClassUsingTypeArguments(<ClassPrototype>element, []);
|
||||
break;
|
||||
|
||||
@ -701,7 +710,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
break;
|
||||
|
||||
case ElementKind.FUNCTION_PROTOTYPE:
|
||||
if (!(<FunctionPrototype>element).isGeneric && statement.range.source.isEntry) {
|
||||
if (!(<FunctionPrototype>element).is(ElementFlags.GENERIC) && statement.range.source.isEntry) {
|
||||
var functionInstance = this.compileFunctionUsingTypeArguments(<FunctionPrototype>element, []);
|
||||
if (functionInstance) {
|
||||
var functionDeclaration = functionInstance.prototype.declaration;
|
||||
@ -715,7 +724,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
if (this.compileGlobal(<Global>element) && statement.range.source.isEntry) {
|
||||
var globalDeclaration = (<Global>element).declaration;
|
||||
if (globalDeclaration && globalDeclaration.needsExplicitExport(member)) {
|
||||
if ((<Global>element).hasConstantValue)
|
||||
if ((<Global>element).is(ElementFlags.INLINED))
|
||||
this.module.addGlobalExport(element.internalName, member.externalIdentifier.name);
|
||||
else
|
||||
this.warning(DiagnosticCode.Cannot_export_a_mutable_global, member.range);
|
||||
@ -747,10 +756,10 @@ export class Compiler extends DiagnosticEmitter {
|
||||
}
|
||||
|
||||
compileClass(instance: Class): bool {
|
||||
if (instance.isCompiled)
|
||||
if (instance.is(ElementFlags.COMPILED))
|
||||
return true;
|
||||
|
||||
return instance.isCompiled = true;
|
||||
instance.set(ElementFlags.COMPILED);
|
||||
return true;
|
||||
}
|
||||
|
||||
compileInterfaceDeclaration(declaration: InterfaceDeclaration, typeArguments: TypeNode[], contextualTypeArguments: Map<string,Type> | null = null, alternativeReportNode: Node | null = null): void {
|
||||
@ -813,7 +822,8 @@ export class Compiler extends DiagnosticEmitter {
|
||||
return this.compileTryStatement(<TryStatement>statement);
|
||||
|
||||
case NodeKind.VARIABLE:
|
||||
return this.compileVariableStatement(<VariableStatement>statement);
|
||||
var variableInit = this.compileVariableStatement(<VariableStatement>statement);
|
||||
return variableInit ? variableInit : this.module.createNop();
|
||||
|
||||
case NodeKind.WHILE:
|
||||
return this.compileWhileStatement(<WhileStatement>statement);
|
||||
@ -838,11 +848,20 @@ export class Compiler extends DiagnosticEmitter {
|
||||
|
||||
compileBlockStatement(statement: BlockStatement): ExpressionRef {
|
||||
var statements = statement.statements;
|
||||
if (statements.length == 0)
|
||||
return this.module.createNop();
|
||||
if (statements.length == 1)
|
||||
return this.compileStatement(statements[0]);
|
||||
return this.module.createBlock(null, this.compileStatements(statements), NativeType.None);
|
||||
|
||||
// NOTE that we could optimize this to a NOP if empty or unwrap a single
|
||||
// statement, but that's not what the source told us to do and left to the
|
||||
// optimizer.
|
||||
|
||||
// Not actually a branch, but can contain its own scoped variables.
|
||||
this.currentFunction.flow = this.currentFunction.flow.enterBranch();
|
||||
|
||||
var stmt = this.module.createBlock(null, this.compileStatements(statements), NativeType.None);
|
||||
|
||||
// Switch back to the parent flow
|
||||
this.currentFunction.flow = this.currentFunction.flow.leaveBranch();
|
||||
|
||||
return stmt;
|
||||
}
|
||||
|
||||
compileBreakStatement(statement: BreakStatement): ExpressionRef {
|
||||
@ -850,12 +869,12 @@ export class Compiler extends DiagnosticEmitter {
|
||||
this.error(DiagnosticCode.Operation_not_supported, statement.label.range);
|
||||
return this.module.createUnreachable();
|
||||
}
|
||||
var context = this.currentFunction.breakContext;
|
||||
if (context != null)
|
||||
return this.module.createBreak("break|" + context);
|
||||
|
||||
this.error(DiagnosticCode.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement, statement.range);
|
||||
return this.module.createUnreachable();
|
||||
var breakLabel = this.currentFunction.flow.breakLabel;
|
||||
if (breakLabel == null) {
|
||||
this.error(DiagnosticCode.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement, statement.range);
|
||||
return this.module.createUnreachable();
|
||||
}
|
||||
return this.module.createBreak(breakLabel);
|
||||
}
|
||||
|
||||
compileContinueStatement(statement: ContinueStatement): ExpressionRef {
|
||||
@ -863,21 +882,35 @@ export class Compiler extends DiagnosticEmitter {
|
||||
this.error(DiagnosticCode.Operation_not_supported, statement.label.range);
|
||||
return this.module.createUnreachable();
|
||||
}
|
||||
var context = this.currentFunction.breakContext;
|
||||
if (context && !this.disallowContinue)
|
||||
return this.module.createBreak("continue|" + context);
|
||||
|
||||
this.error(DiagnosticCode.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement, statement.range);
|
||||
return this.module.createUnreachable();
|
||||
// Check if 'continue' is allowed here
|
||||
var continueLabel = this.currentFunction.flow.continueLabel;
|
||||
if (continueLabel == null) {
|
||||
this.error(DiagnosticCode.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement, statement.range);
|
||||
return this.module.createUnreachable();
|
||||
}
|
||||
return this.module.createBreak(continueLabel);
|
||||
}
|
||||
|
||||
compileDoStatement(statement: DoStatement): ExpressionRef {
|
||||
|
||||
// A do statement does not initiate a new branch because it is executed at
|
||||
// least once, but has its own break and continue labels.
|
||||
var label = this.currentFunction.enterBreakContext();
|
||||
var previousBreakLabel = this.currentFunction.flow.breakLabel;
|
||||
var previousContinueLabel = this.currentFunction.flow.continueLabel;
|
||||
var breakLabel = this.currentFunction.flow.breakLabel = "break|" + label;
|
||||
var continueLabel = this.currentFunction.flow.continueLabel = "continue|" + label;
|
||||
|
||||
var body = this.compileStatement(statement.statement);
|
||||
|
||||
// Reset to the previous break and continue labels, if any.
|
||||
this.currentFunction.flow.breakLabel = previousBreakLabel;
|
||||
this.currentFunction.flow.continueLabel = previousContinueLabel;
|
||||
|
||||
var condition = this.compileExpression(statement.condition, Type.i32);
|
||||
|
||||
this.currentFunction.leaveBreakContext();
|
||||
var breakLabel = "break|" + label;
|
||||
var continueLabel = "continue|" + label;
|
||||
|
||||
return this.module.createBlock(breakLabel, [
|
||||
this.module.createLoop(continueLabel,
|
||||
this.module.createBlock(null, [
|
||||
@ -901,18 +934,24 @@ export class Compiler extends DiagnosticEmitter {
|
||||
}
|
||||
|
||||
compileForStatement(statement: ForStatement): ExpressionRef {
|
||||
|
||||
// A for statement initiates a new branch with its own scoped variables
|
||||
// possibly declared in its initializer, and break context.
|
||||
var context = this.currentFunction.enterBreakContext();
|
||||
var variableWasLocal = this.variableIsLocal;
|
||||
if (this.currentFunction == this.startFunction)
|
||||
this.variableIsLocal = true;
|
||||
this.currentFunction.flow = this.currentFunction.flow.enterBranch();
|
||||
var breakLabel = this.currentFunction.flow.breakLabel = "break|" + context;
|
||||
var continueLabel = this.currentFunction.flow.continueLabel = "continue|" + context;
|
||||
|
||||
// Compile in correct order
|
||||
var initializer = statement.initializer ? this.compileStatement(<Statement>statement.initializer) : this.module.createNop();
|
||||
this.variableIsLocal = variableWasLocal;
|
||||
var condition = statement.condition ? this.compileExpression(<Expression>statement.condition, Type.i32) : this.module.createI32(1);
|
||||
var incrementor = statement.incrementor ? this.compileExpression(<Expression>statement.incrementor, Type.void) : this.module.createNop();
|
||||
var body = this.compileStatement(statement.statement);
|
||||
|
||||
// Switch back to the parent flow
|
||||
this.currentFunction.flow = this.currentFunction.flow.leaveBranch();
|
||||
this.currentFunction.leaveBreakContext();
|
||||
var continueLabel = "continue|" + context;
|
||||
var breakLabel = "break|" + context;
|
||||
|
||||
return this.module.createBlock(breakLabel, [
|
||||
initializer,
|
||||
this.module.createLoop(continueLabel, this.module.createBlock(null, [
|
||||
@ -926,30 +965,47 @@ export class Compiler extends DiagnosticEmitter {
|
||||
}
|
||||
|
||||
compileIfStatement(statement: IfStatement): ExpressionRef {
|
||||
|
||||
// The condition doesn't initiate a branch yet
|
||||
var condition = this.compileExpression(statement.condition, Type.i32);
|
||||
|
||||
// Each arm initiates a branch
|
||||
this.currentFunction.flow = this.currentFunction.flow.enterBranch();
|
||||
var ifTrue = this.compileStatement(statement.ifTrue);
|
||||
var ifFalse = statement.ifFalse ? this.compileStatement(<Statement>statement.ifFalse) : <ExpressionRef>0;
|
||||
this.currentFunction.flow = this.currentFunction.flow.leaveBranch();
|
||||
|
||||
var ifFalse: ExpressionRef = 0;
|
||||
if (statement.ifFalse) {
|
||||
this.currentFunction.flow = this.currentFunction.flow.enterBranch();
|
||||
ifFalse = this.compileStatement(statement.ifFalse);
|
||||
this.currentFunction.flow = this.currentFunction.flow.leaveBranch();
|
||||
}
|
||||
return this.module.createIf(condition, ifTrue, ifFalse);
|
||||
}
|
||||
|
||||
compileReturnStatement(statement: ReturnStatement): ExpressionRef {
|
||||
if (this.currentFunction) {
|
||||
var expression = statement.value ? this.compileExpression(<Expression>statement.value, this.currentFunction.returnType) : <ExpressionRef>0;
|
||||
return this.module.createReturn(expression);
|
||||
}
|
||||
return this.module.createUnreachable();
|
||||
assert(this.currentFunction);
|
||||
|
||||
var expression: ExpressionRef = 0;
|
||||
if (statement.value)
|
||||
expression = this.compileExpression(<Expression>statement.value, this.currentFunction.returnType);
|
||||
|
||||
// Remember that this flow returns
|
||||
this.currentFunction.flow.set(FlowFlags.RETURNS);
|
||||
|
||||
return this.module.createReturn(expression);
|
||||
}
|
||||
|
||||
compileSwitchStatement(statement: SwitchStatement): ExpressionRef {
|
||||
|
||||
// Everything within a switch uses the same break context
|
||||
var context = this.currentFunction.enterBreakContext();
|
||||
var previousDisallowContinue = this.disallowContinue;
|
||||
this.disallowContinue = true;
|
||||
|
||||
// introduce a local for evaluating the condition (exactly once)
|
||||
var tempLocal = this.currentFunction.getTempLocal(Type.i32);
|
||||
var k = statement.cases.length;
|
||||
|
||||
// prepend initializer to inner block
|
||||
// Prepend initializer to inner block. Does not initiate a new branch, yet.
|
||||
var breaks = new Array<ExpressionRef>(1 + k);
|
||||
breaks[0] = this.module.createSetLocal(tempLocal.index, this.compileExpression(statement.condition, Type.i32)); // initializer
|
||||
|
||||
@ -981,22 +1037,34 @@ export class Compiler extends DiagnosticEmitter {
|
||||
var currentBlock = this.module.createBlock("case0|" + context, breaks, NativeType.None);
|
||||
for (i = 0; i < k; ++i) {
|
||||
case_ = statement.cases[i];
|
||||
var nextLabel = i == k - 1 ? "break|" + context : "case" + (i + 1).toString(10) + "|" + context;
|
||||
var l = case_.statements.length;
|
||||
var body = new Array<ExpressionRef>(1 + l);
|
||||
body[0] = currentBlock;
|
||||
|
||||
// Each switch case initiates a new branch
|
||||
this.currentFunction.flow = this.currentFunction.flow.enterBranch();
|
||||
var breakLabel = this.currentFunction.flow.breakLabel = "break|" + context;
|
||||
|
||||
var nextLabel = i == k - 1 ? breakLabel : "case" + (i + 1).toString(10) + "|" + context;
|
||||
for (var j = 0; j < l; ++j)
|
||||
body[j + 1] = this.compileStatement(case_.statements[j]);
|
||||
|
||||
// Switch back to the parent flow
|
||||
this.currentFunction.flow = this.currentFunction.flow.leaveBranch();
|
||||
|
||||
currentBlock = this.module.createBlock(nextLabel, body, NativeType.None);
|
||||
}
|
||||
this.currentFunction.leaveBreakContext();
|
||||
this.disallowContinue = previousDisallowContinue;
|
||||
|
||||
return currentBlock;
|
||||
}
|
||||
|
||||
compileThrowStatement(statement: ThrowStatement): ExpressionRef {
|
||||
return this.module.createUnreachable(); // TODO: waiting for exception-handling spec
|
||||
|
||||
// Remember that this branch possibly throws
|
||||
this.currentFunction.flow.set(FlowFlags.THROWS);
|
||||
|
||||
// TODO: requires exception-handling spec.
|
||||
return this.module.createUnreachable();
|
||||
}
|
||||
|
||||
compileTryStatement(statement: TryStatement): ExpressionRef {
|
||||
@ -1005,16 +1073,28 @@ export class Compiler extends DiagnosticEmitter {
|
||||
// worthwhile to investigate lowering returns to block results (here)?
|
||||
}
|
||||
|
||||
compileVariableStatement(statement: VariableStatement): ExpressionRef {
|
||||
/**
|
||||
* Compiles a variable statement. Returns `0` if an initializer is not
|
||||
* necessary.
|
||||
*/
|
||||
compileVariableStatement(statement: VariableStatement, isKnownGlobal: bool = false): ExpressionRef {
|
||||
var declarations = statement.declarations;
|
||||
|
||||
// top-level variables become globals
|
||||
if (this.currentFunction == this.startFunction && !this.variableIsLocal) {
|
||||
var isConst = hasModifier(ModifierKind.CONST, statement.modifiers);
|
||||
// top-level variables and constants become globals
|
||||
if (isKnownGlobal || (
|
||||
this.currentFunction == this.startFunction &&
|
||||
statement.parent && statement.parent.kind == NodeKind.SOURCE
|
||||
)) {
|
||||
// NOTE that the above condition also covers top-level variables declared with 'let', even
|
||||
// though such variables could also become start function locals if, and only if, not used
|
||||
// within any function declared in the same source, which is unknown at this point. the only
|
||||
// efficient way to deal with this would be to keep track of all occasions it is used and
|
||||
// replace these instructions afterwards, dynamically. (TOOD: what about a Binaryen pass?)
|
||||
for (var i = 0, k = declarations.length; i < k; ++i)
|
||||
this.compileGlobalDeclaration(declarations[i], isConst);
|
||||
return this.module.createNop();
|
||||
this.compileGlobalDeclaration(declarations[i]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// other variables become locals
|
||||
var initializers = new Array<ExpressionRef>();
|
||||
for (i = 0, k = declarations.length; i < k; ++i) {
|
||||
@ -1039,53 +1119,73 @@ export class Compiler extends DiagnosticEmitter {
|
||||
this.error(DiagnosticCode.Type_expected, declaration.name.range.atEnd);
|
||||
continue;
|
||||
}
|
||||
if (this.currentFunction.locals.has(name))
|
||||
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, name); // recoverable
|
||||
else {
|
||||
if (hasModifier(ModifierKind.CONST, declaration.modifiers)) {
|
||||
if (init) {
|
||||
init = this.precomputeExpressionRef(init);
|
||||
if (_BinaryenExpressionGetId(init) == ExpressionId.Const) {
|
||||
var local = new Local(this.program, name, -1, type);
|
||||
switch (_BinaryenExpressionGetType(init)) {
|
||||
case NativeType.I32:
|
||||
local = local.withConstantIntegerValue(_BinaryenConstGetValueI32(init), 0);
|
||||
break;
|
||||
case NativeType.I64:
|
||||
local = local.withConstantIntegerValue(_BinaryenConstGetValueI64Low(init), _BinaryenConstGetValueI64High(init));
|
||||
break;
|
||||
case NativeType.F32:
|
||||
local = local.withConstantFloatValue(<f64>_BinaryenConstGetValueF32(init));
|
||||
break;
|
||||
case NativeType.F64:
|
||||
local = local.withConstantFloatValue(_BinaryenConstGetValueF64(init));
|
||||
break;
|
||||
default:
|
||||
throw new Error("concrete type expected");
|
||||
}
|
||||
this.currentFunction.locals.set(name, local);
|
||||
continue;
|
||||
} else
|
||||
this.warning(DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable, declaration.range);
|
||||
} else {
|
||||
this.error(DiagnosticCode._const_declarations_must_be_initialized, declaration.range);
|
||||
}
|
||||
}
|
||||
this.currentFunction.addLocal(type, name);
|
||||
if (init)
|
||||
initializers.push(this.compileAssignmentWithValue(declaration.name, init));
|
||||
if (hasModifier(ModifierKind.CONST, declaration.modifiers)) {
|
||||
if (init) {
|
||||
init = this.precomputeExpressionRef(init);
|
||||
if (_BinaryenExpressionGetId(init) == ExpressionId.Const) {
|
||||
var local = new Local(this.program, name, -1, type);
|
||||
switch (_BinaryenExpressionGetType(init)) {
|
||||
case NativeType.I32:
|
||||
local = local.withConstantIntegerValue(_BinaryenConstGetValueI32(init), 0);
|
||||
break;
|
||||
case NativeType.I64:
|
||||
local = local.withConstantIntegerValue(_BinaryenConstGetValueI64Low(init), _BinaryenConstGetValueI64High(init));
|
||||
break;
|
||||
case NativeType.F32:
|
||||
local = local.withConstantFloatValue(<f64>_BinaryenConstGetValueF32(init));
|
||||
break;
|
||||
case NativeType.F64:
|
||||
local = local.withConstantFloatValue(_BinaryenConstGetValueF64(init));
|
||||
break;
|
||||
default:
|
||||
throw new Error("concrete type expected");
|
||||
}
|
||||
// Create a virtual local that doesn't actually exist in WebAssembly
|
||||
var scopedLocals = this.currentFunction.flow.scopedLocals;
|
||||
if (!scopedLocals)
|
||||
scopedLocals = this.currentFunction.flow.scopedLocals = new Map();
|
||||
else if (scopedLocals.has(name)) {
|
||||
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, name);
|
||||
return 0;
|
||||
}
|
||||
scopedLocals.set(name, local);
|
||||
return 0;
|
||||
} else
|
||||
this.warning(DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable, declaration.range);
|
||||
} else
|
||||
this.error(DiagnosticCode._const_declarations_must_be_initialized, declaration.range);
|
||||
}
|
||||
if (hasModifier(ModifierKind.LET, declaration.modifiers)) // here: not top-level
|
||||
this.currentFunction.flow.addScopedLocal(name, type, declaration.name); // reports
|
||||
else
|
||||
this.currentFunction.addLocal(type, name); // reports
|
||||
if (init)
|
||||
initializers.push(this.compileAssignmentWithValue(declaration.name, init));
|
||||
}
|
||||
return initializers.length ? this.module.createBlock(null, initializers, NativeType.None) : this.module.createNop();
|
||||
return initializers.length // we can unwrap these here because the
|
||||
? initializers.length == 1 // source didn't tell us exactly what to do
|
||||
? initializers[0]
|
||||
: this.module.createBlock(null, initializers, NativeType.None)
|
||||
: 0;
|
||||
}
|
||||
|
||||
compileWhileStatement(statement: WhileStatement): ExpressionRef {
|
||||
var label = this.currentFunction.enterBreakContext();
|
||||
|
||||
// The condition does not yet initialize a branch
|
||||
var condition = this.compileExpression(statement.condition, Type.i32);
|
||||
var breakLabel = "break|" + label;
|
||||
var continueLabel = "continue|" + label;
|
||||
|
||||
// Statements initiate a new branch with its own break context
|
||||
var label = this.currentFunction.enterBreakContext();
|
||||
this.currentFunction.flow = this.currentFunction.flow.enterBranch();
|
||||
var breakLabel = this.currentFunction.flow.breakLabel = "break|" + label;
|
||||
var continueLabel = this.currentFunction.flow.continueLabel = "continue|" + label;
|
||||
|
||||
var body = this.compileStatement(statement.statement);
|
||||
|
||||
// Switch back to the parent flow
|
||||
this.currentFunction.flow = this.currentFunction.flow.leaveBranch();
|
||||
this.currentFunction.leaveBreakContext();
|
||||
|
||||
return this.module.createBlock(breakLabel, [
|
||||
this.module.createLoop(continueLabel,
|
||||
this.module.createIf(condition, this.module.createBlock(null, [
|
||||
@ -2256,7 +2356,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
|
||||
case ElementKind.LOCAL:
|
||||
this.currentType = tee ? (<Local>element).type : Type.void;
|
||||
if ((<Local>element).isConstant) {
|
||||
if ((<Local>element).is(ElementFlags.CONSTANT)) {
|
||||
this.error(DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, expression.range, (<Local>element).internalName);
|
||||
return this.module.createUnreachable();
|
||||
}
|
||||
@ -2269,7 +2369,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
return this.module.createUnreachable();
|
||||
assert((<Global>element).type != Type.void);
|
||||
this.currentType = tee ? (<Global>element).type : Type.void;
|
||||
if ((<Local>element).isConstant) {
|
||||
if ((<Local>element).is(ElementFlags.CONSTANT)) {
|
||||
this.error(DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, expression.range, (<Local>element).internalName);
|
||||
return this.module.createUnreachable();
|
||||
}
|
||||
@ -2307,7 +2407,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
if (setterInstance) {
|
||||
assert(setterInstance.parameters.length == 1);
|
||||
if (!tee) {
|
||||
if (setterInstance.isInstance) {
|
||||
if (setterInstance.is(ElementFlags.INSTANCE)) {
|
||||
assert(resolved.targetExpression != null);
|
||||
targetExpr = this.compileExpression(<Expression>resolved.targetExpression, this.options.target == Target.WASM64 ? Type.usize64 : Type.usize32, ConversionKind.NONE);
|
||||
assert(this.currentType.classType);
|
||||
@ -2323,7 +2423,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
var getterInstance = (<FunctionPrototype>getterPrototype).resolve(); // reports
|
||||
if (getterInstance) {
|
||||
assert(getterInstance.parameters.length == 0);
|
||||
if (setterInstance.isInstance) {
|
||||
if (setterInstance.is(ElementFlags.INSTANCE)) {
|
||||
assert(resolved.targetExpression != null);
|
||||
targetExpr = this.compileExpression(<Expression>resolved.targetExpression, this.options.target == Target.WASM64 ? Type.usize64 : Type.usize32, ConversionKind.NONE);
|
||||
assert(this.currentType.classType);
|
||||
@ -2388,7 +2488,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
if (element.kind == ElementKind.FUNCTION_PROTOTYPE) {
|
||||
var functionPrototype = <FunctionPrototype>element;
|
||||
var functionInstance: Function | null = null;
|
||||
if (functionPrototype.isBuiltIn) {
|
||||
if (functionPrototype.is(ElementFlags.BUILTIN)) {
|
||||
var resolvedTypeArguments: Type[] | null = null;
|
||||
if (expression.typeArguments) {
|
||||
var k = expression.typeArguments.length;
|
||||
@ -2489,11 +2589,11 @@ export class Compiler extends DiagnosticEmitter {
|
||||
}
|
||||
|
||||
private makeCall(functionInstance: Function, operands: ExpressionRef[] | null = null): ExpressionRef {
|
||||
if (!(functionInstance.isCompiled || this.compileFunction(functionInstance)))
|
||||
if (!(functionInstance.is(ElementFlags.COMPILED) || this.compileFunction(functionInstance)))
|
||||
return this.module.createUnreachable();
|
||||
|
||||
// imported function
|
||||
if (functionInstance.isDeclared)
|
||||
if (functionInstance.is(ElementFlags.DECLARED))
|
||||
return this.module.createCallImport(functionInstance.internalName, operands, functionInstance.returnType.toNativeType());
|
||||
|
||||
// internal function
|
||||
@ -2576,19 +2676,19 @@ export class Compiler extends DiagnosticEmitter {
|
||||
|
||||
case ElementKind.LOCAL:
|
||||
this.currentType = (<Local>element).type;
|
||||
if ((<Local>element).hasConstantValue)
|
||||
if ((<Local>element).is(ElementFlags.INLINED))
|
||||
return makeInlineConstant(<Local>element, this.module);
|
||||
assert((<Local>element).index >= 0);
|
||||
return this.module.createGetLocal((<Local>element).index, this.currentType.toNativeType());
|
||||
|
||||
case ElementKind.GLOBAL:
|
||||
if (element.isBuiltIn)
|
||||
if (element.is(ElementFlags.BUILTIN))
|
||||
return compileBuiltinGetConstant(this, <Global>element, expression);
|
||||
if (!this.compileGlobal(<Global>element)) // reports; not yet compiled if a static field compiled as a global
|
||||
return this.module.createUnreachable();
|
||||
assert((<Global>element).type != Type.void);
|
||||
this.currentType = (<Global>element).type;
|
||||
if ((<Global>element).hasConstantValue)
|
||||
if ((<Global>element).is(ElementFlags.INLINED))
|
||||
return makeInlineConstant(<Global>element, this.module);
|
||||
return this.module.createGetGlobal((<Global>element).internalName, this.currentType.toNativeType());
|
||||
}
|
||||
@ -2660,13 +2760,13 @@ export class Compiler extends DiagnosticEmitter {
|
||||
switch (element.kind) {
|
||||
|
||||
case ElementKind.GLOBAL: // static property
|
||||
if (element.isBuiltIn)
|
||||
if (element.is(ElementFlags.BUILTIN))
|
||||
return compileBuiltinGetConstant(this, <Global>element, propertyAccess);
|
||||
if (!this.compileGlobal(<Global>element)) // reports; not yet compiled if a static field compiled as a global
|
||||
return this.module.createUnreachable();
|
||||
assert((<Global>element).type != Type.void);
|
||||
this.currentType = (<Global>element).type;
|
||||
if ((<Global>element).hasConstantValue)
|
||||
if ((<Global>element).is(ElementFlags.INLINED))
|
||||
return makeInlineConstant(<Global>element, this.module);
|
||||
return this.module.createGetGlobal((<Global>element).internalName, this.currentType.toNativeType());
|
||||
|
||||
@ -2674,7 +2774,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
if (!this.compileEnum((<EnumValue>element).enum))
|
||||
return this.module.createUnreachable();
|
||||
this.currentType = Type.i32;
|
||||
if ((<EnumValue>element).hasConstantValue)
|
||||
if ((<EnumValue>element).is(ElementFlags.INLINED))
|
||||
return this.module.createI32((<EnumValue>element).constantValue);
|
||||
return this.module.createGetGlobal((<EnumValue>element).internalName, NativeType.I32);
|
||||
|
||||
@ -2698,7 +2798,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
return this.module.createUnreachable();
|
||||
assert(getterInstance.parameters.length == 0);
|
||||
this.currentType = getterInstance.returnType;
|
||||
if (getterInstance.isInstance) {
|
||||
if (getterInstance.is(ElementFlags.INSTANCE)) {
|
||||
var targetExpr = this.compileExpression(<Expression>resolved.targetExpression, this.options.target == Target.WASM64 ? Type.usize64 : Type.usize32)
|
||||
this.currentType = getterInstance.returnType;
|
||||
return this.makeCall(getterInstance, [ targetExpr ]);
|
||||
@ -3062,7 +3162,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
|
||||
/** Creates an inlined expression of a constant variable-like element. */
|
||||
function makeInlineConstant(element: VariableLikeElement, module: Module): ExpressionRef {
|
||||
assert(element.hasConstantValue);
|
||||
assert(element.is(ElementFlags.INLINED));
|
||||
assert(element.type != null);
|
||||
switch ((<Type>element.type).kind) {
|
||||
|
||||
|
Reference in New Issue
Block a user