assemblyscript/src/compiler.ts

2194 lines
89 KiB
TypeScript
Raw Normal View History

2017-12-16 02:27:39 +01:00
import { compileCall as compileBuiltinCall, compileGetGlobal as compileBuiltinGetGlobal, initialize } from "./builtins";
import { PATH_DELIMITER } from "./constants";
2017-12-05 01:45:15 +01:00
import { DiagnosticCode, DiagnosticEmitter } from "./diagnostics";
import {
Module,
MemorySegment,
ExpressionRef,
UnaryOp,
BinaryOp,
NativeType,
FunctionTypeRef,
FunctionRef,
ExpressionId
} from "./module";
import {
Program,
ClassPrototype,
Class, Element,
ElementKind,
2017-12-13 04:46:05 +01:00
ElementFlags,
Enum,
FunctionPrototype,
Function,
Global,
Local,
Namespace,
Parameter,
EnumValue
} from "./program";
2017-12-05 01:45:15 +01:00
import { Token } from "./tokenizer";
2017-09-28 13:08:25 +02:00
import {
2017-10-07 14:29:43 +02:00
Node,
2017-09-28 13:08:25 +02:00
NodeKind,
TypeNode,
2017-10-07 14:29:43 +02:00
TypeParameter,
2017-10-02 12:52:15 +02:00
Source,
2017-09-28 13:08:25 +02:00
// statements
BlockStatement,
BreakStatement,
ClassDeclaration,
ContinueStatement,
2017-10-02 12:52:15 +02:00
DeclarationStatement,
2017-09-28 13:08:25 +02:00
DoStatement,
EmptyStatement,
EnumDeclaration,
EnumValueDeclaration,
2017-10-02 12:52:15 +02:00
ExportMember,
ExportStatement,
2017-09-28 13:08:25 +02:00
ExpressionStatement,
2017-10-02 12:52:15 +02:00
FieldDeclaration,
2017-09-28 13:08:25 +02:00
FunctionDeclaration,
ForStatement,
IfStatement,
ImportStatement,
2017-12-18 03:46:36 +01:00
InterfaceDeclaration,
2017-09-28 13:08:25 +02:00
MethodDeclaration,
ModifierKind,
NamespaceDeclaration,
ReturnStatement,
Statement,
SwitchCase,
2017-09-28 13:08:25 +02:00
SwitchStatement,
ThrowStatement,
TryStatement,
VariableLikeDeclarationStatement,
2017-09-28 13:08:25 +02:00
VariableDeclaration,
VariableStatement,
WhileStatement,
// expressions
ArrayLiteralExpression,
AssertionExpression,
BinaryExpression,
CallExpression,
ElementAccessExpression,
Expression,
FloatLiteralExpression,
IdentifierExpression,
IntegerLiteralExpression,
LiteralExpression,
LiteralKind,
NewExpression,
ParenthesizedExpression,
PropertyAccessExpression,
TernaryExpression,
2017-09-28 13:08:25 +02:00
StringLiteralExpression,
UnaryPostfixExpression,
UnaryPrefixExpression,
// utility
2017-12-18 03:46:36 +01:00
hasModifier
2017-09-28 13:08:25 +02:00
} from "./ast";
import {
Type,
2017-10-07 14:29:43 +02:00
TypeKind,
2017-09-28 13:08:25 +02:00
2017-10-02 12:52:15 +02:00
} from "./types";
2017-12-14 11:55:35 +01:00
import { I64, U64 } from "./util/i64";
import { sb } from "./util/sb";
2017-09-28 13:08:25 +02:00
2017-12-14 11:55:35 +01:00
/** Compilation target. */
2017-09-28 13:08:25 +02:00
export enum Target {
/** WebAssembly with 32-bit pointers. */
2017-09-28 13:08:25 +02:00
WASM32,
2017-11-26 04:03:28 +01:00
/** WebAssembly with 64-bit pointers. Experimental and not supported by any runtime yet. */
2017-09-28 13:08:25 +02:00
WASM64
}
2017-12-14 11:55:35 +01:00
/** Compiler options. */
2017-09-28 13:08:25 +02:00
export class Options {
/** WebAssembly target. Defaults to {@link Target.WASM32}. */
2017-09-28 13:08:25 +02:00
target: Target = Target.WASM32;
2017-11-26 04:03:28 +01:00
/** If true, performs compilation as usual but doesn't produce any output (all calls to module become nops). */
2017-10-02 12:52:15 +02:00
noEmit: bool = false;
/** If true, compiles everything instead of just reachable code. */
noTreeShaking: bool = false;
2017-12-04 02:00:48 +01:00
/** If true, replaces assertions with nops. */
2017-12-13 23:24:13 +01:00
noAssert: bool = false;
2017-09-28 13:08:25 +02:00
}
2017-12-14 11:55:35 +01:00
/** Indicates the desired kind of a conversion. */
2017-12-15 17:23:04 +01:00
export const enum ConversionKind {
/** No conversion. */
NONE,
/** Implicit conversion. */
IMPLICIT,
/** Explicit conversion. */
EXPLICIT
}
2017-12-14 11:55:35 +01:00
/** Compiler interface. */
2017-09-28 13:08:25 +02:00
export class Compiler extends DiagnosticEmitter {
2017-11-26 04:03:28 +01:00
/** Program reference. */
2017-09-28 13:08:25 +02:00
program: Program;
2017-11-26 04:03:28 +01:00
/** Provided options. */
2017-09-28 13:08:25 +02:00
options: Options;
2017-11-26 04:03:28 +01:00
/** Module instance being compiled. */
2017-09-28 13:08:25 +02:00
module: Module;
2017-11-26 04:03:28 +01:00
/** Start function being compiled. */
2017-11-17 14:33:51 +01:00
startFunction: Function;
2017-11-26 04:03:28 +01:00
/** Start function expressions. */
startFunctionBody: ExpressionRef[] = new Array();
2017-10-02 12:52:15 +02:00
2017-11-26 04:03:28 +01:00
/** Current type in compilation. */
2017-09-28 13:08:25 +02:00
currentType: Type = Type.void;
2017-11-26 04:03:28 +01:00
/** Current function in compilation. */
2017-11-17 14:33:51 +01:00
currentFunction: Function;
2017-11-26 04:03:28 +01:00
/** 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;
2017-09-28 13:08:25 +02:00
2017-11-26 04:03:28 +01:00
/** Counting memory offset. */
2017-09-28 13:08:25 +02:00
memoryOffset: U64 = new U64(8, 0); // leave space for (any size of) NULL
2017-11-26 04:03:28 +01:00
/** Memory segments being compiled. */
2017-09-28 13:08:25 +02:00
memorySegments: MemorySegment[] = new Array();
2017-11-26 04:03:28 +01:00
/** Already processed file names. */
files: Set<string> = new Set();
2017-11-26 04:03:28 +01:00
/** Compiles a {@link Program} to a {@link Module} using the specified options. */
2017-09-28 13:08:25 +02:00
static compile(program: Program, options: Options | null = null): Module {
const compiler: Compiler = new Compiler(program, options);
return compiler.compile();
}
2017-11-26 04:03:28 +01:00
/** Constructs a new compiler for a {@link Program} using the specified options. */
2017-09-28 13:08:25 +02:00
constructor(program: Program, options: Options | null = null) {
super(program.diagnostics);
this.program = program;
this.options = options ? options : new Options();
2017-10-07 14:29:43 +02:00
this.module = this.options.noEmit ? Module.createStub() : Module.create();
2017-12-15 15:00:19 +01:00
const startFunctionTemplate: FunctionPrototype = new FunctionPrototype(program, "start", "start", null);
2017-11-17 14:33:51 +01:00
const startFunctionInstance: Function = new Function(startFunctionTemplate, startFunctionTemplate.internalName, [], [], Type.void, null);
this.currentFunction = this.startFunction = startFunctionInstance;
this.memoryOffset = new U64(this.options.target == Target.WASM64 ? 8 : 4, 0); // leave space for `null`
2017-09-28 13:08:25 +02:00
}
2017-11-26 04:03:28 +01:00
/** Performs compilation of the underlying {@link Program} to a {@link Module}. */
2017-09-28 13:08:25 +02:00
compile(): Module {
const program: Program = this.program;
2017-12-05 13:35:14 +01:00
// initialize lookup maps, built-ins, imports, exports, etc.
2017-09-28 13:08:25 +02:00
program.initialize(this.options.target);
// compile entry file (exactly one, usually)
const sources: Source[] = program.sources;
let i: i32, k: i32 = sources.length;
for (i = 0; i < k; ++i) {
const source: Source = sources[i];
if (source.isEntry)
this.compileSource(source);
}
// make start function if not empty
if (this.startFunctionBody.length) {
2017-11-26 04:03:28 +01:00
let typeRef: FunctionTypeRef = this.module.getFunctionTypeBySignature(NativeType.None, []);
if (!typeRef)
2017-11-26 04:03:28 +01:00
typeRef = this.module.addFunctionType("v", NativeType.None, []);
this.module.setStart(
2017-12-13 04:46:05 +01:00
this.module.addFunction(this.startFunction.prototype.internalName, typeRef, typesToNativeTypes(this.startFunction.additionalLocals), this.module.createBlock(null, this.startFunctionBody))
);
}
// set up memory
const initial: U64 = this.memoryOffset.clone();
if (this.options.target == Target.WASM64)
2017-12-16 17:54:53 +01:00
this.module.addGlobal("HEAP_BASE", NativeType.I64, false, this.module.createI64(initial.lo, initial.hi));
else
2017-12-16 17:54:53 +01:00
this.module.addGlobal("HEAP_BASE", NativeType.I32, false, this.module.createI32(initial.lo));
// determine initial page size
const initialOverlaps: U64 = initial.clone();
initialOverlaps.and32(0xffff);
if (!initialOverlaps.isZero) {
initial.or32(0xffff);
initial.add32(1);
}
initial.shru32(16); // initial size in 64k pages
this.module.setMemory(initial.toI32(), Module.MAX_MEMORY_WASM32 /* TODO: not WASM64 compatible yet */, this.memorySegments, this.options.target, "memory");
return this.module;
}
// sources
compileSourceByPath(normalizedPath: string, reportNode: Node): void {
for (let i: i32 = 0, k: i32 = this.program.sources.length; i < k; ++i) {
const importedSource: Source = this.program.sources[i];
if (importedSource.normalizedPath == normalizedPath) {
this.compileSource(importedSource);
return;
}
}
this.error(DiagnosticCode.File_0_not_found, reportNode.range, normalizedPath);
}
compileSource(source: Source): void {
if (this.files.has(source.normalizedPath))
return;
this.files.add(source.normalizedPath);
const isEntry: bool = source.isEntry;
const noTreeShaking: bool = this.options.noTreeShaking;
for (let i: i32 = 0, k: i32 = source.statements.length; i < k; ++i) {
const statement: Statement = source.statements[i];
2017-09-28 13:08:25 +02:00
switch (statement.kind) {
case NodeKind.CLASS:
if ((noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (<ClassDeclaration>statement).modifiers)) && !(<ClassDeclaration>statement).typeParameters.length)
this.compileClassDeclaration(<ClassDeclaration>statement, []);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.ENUM:
if (noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (<EnumDeclaration>statement).modifiers))
this.compileEnumDeclaration(<EnumDeclaration>statement);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.FUNCTION:
if ((noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (<FunctionDeclaration>statement).modifiers)) && !(<FunctionDeclaration>statement).typeParameters.length)
this.compileFunctionDeclaration(<FunctionDeclaration>statement, []);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.IMPORT:
this.compileSourceByPath((<ImportStatement>statement).normalizedPath, (<ImportStatement>statement).path);
break;
2017-09-28 13:08:25 +02:00
case NodeKind.NAMESPACE:
if (noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (<NamespaceDeclaration>statement).modifiers))
this.compileNamespaceDeclaration(<NamespaceDeclaration>statement);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.VARIABLE:
if (noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (<VariableStatement>statement).modifiers))
this.compileVariableStatement(<VariableStatement>statement);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.EXPORT:
if ((<ExportStatement>statement).normalizedPath != null)
this.compileSourceByPath(<string>(<ExportStatement>statement).normalizedPath, <StringLiteralExpression>(<ExportStatement>statement).path);
if (noTreeShaking || isEntry)
this.compileExportStatement(<ExportStatement>statement);
break;
// otherwise a top-level statement that is part of the start function's body
2017-10-02 12:52:15 +02:00
default: {
2017-11-17 14:33:51 +01:00
const previousFunction: Function = this.currentFunction;
2017-10-02 12:52:15 +02:00
this.currentFunction = this.startFunction;
this.startFunctionBody.push(this.compileStatement(statement));
this.currentFunction = previousFunction;
2017-09-28 13:08:25 +02:00
break;
2017-10-02 12:52:15 +02:00
}
2017-09-28 13:08:25 +02:00
}
}
}
2017-09-28 13:08:25 +02:00
// globals
2017-09-28 13:08:25 +02:00
compileGlobalDeclaration(declaration: VariableDeclaration, isConst: bool): Global | null {
const element: Element | null = <Element | null>this.program.elements.get(declaration.internalName);
if (!element || element.kind != ElementKind.GLOBAL)
throw new Error("unexpected missing global");
if (!this.compileGlobal(<Global>element))
return null;
2017-12-13 23:24:13 +01:00
if (isModuleExport(element, declaration)) {
if ((<Global>element).hasConstantValue)
2017-12-18 03:46:36 +01:00
this.module.addGlobalExport(element.internalName, declaration.name.name);
else
this.warning(DiagnosticCode.Cannot_export_a_mutable_global, declaration.range);
}
return <Global>element;
2017-09-28 13:08:25 +02:00
}
2017-12-13 04:46:05 +01:00
compileGlobal(global: Global): bool {
if (global.isCompiled)
return true;
2017-12-16 02:27:39 +01:00
if (global.isBuiltIn)
if (compileBuiltinGetGlobal(this, global))
return true;
2017-12-13 04:46:05 +01:00
const declaration: VariableLikeDeclarationStatement | null = global.declaration;
let type: Type | null = global.type;
if (!type) {
if (!declaration)
throw new Error("unexpected missing declaration");
2017-12-14 11:55:35 +01:00
if (!declaration.type) { // TODO: infer type
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Type_expected, declaration.name.range);
2017-12-14 11:55:35 +01:00
return false;
}
type = this.program.resolveType(declaration.type); // reports
if (!type)
return false;
2017-12-13 04:46:05 +01:00
global.type = type;
}
if (this.module.noEmit)
return true;
2017-11-26 04:03:28 +01:00
const nativeType: NativeType = typeToNativeType(<Type>type);
let initializer: ExpressionRef;
let initializeInStart: bool = false;
2017-12-13 04:46:05 +01:00
if (global.hasConstantValue) {
2017-12-14 11:55:35 +01:00
assert(type != null);
if (type.isLongInteger)
2017-12-13 04:46:05 +01:00
initializer = global.constantIntegerValue ? this.module.createI64(global.constantIntegerValue.lo, global.constantIntegerValue.hi) : this.module.createI64(0, 0);
else if (type.kind == TypeKind.F32)
2017-12-13 04:46:05 +01:00
initializer = this.module.createF32(global.constantFloatValue);
else if (type.kind == TypeKind.F64)
2017-12-13 04:46:05 +01:00
initializer = this.module.createF64(global.constantFloatValue);
else if (type.isSmallInteger) {
if (type.isSignedInteger) {
const shift: i32 = type.smallIntegerShift;
2017-12-13 04:46:05 +01:00
initializer = this.module.createI32(global.constantIntegerValue ? global.constantIntegerValue.toI32() << shift >> shift : 0);
} else
2017-12-13 04:46:05 +01:00
initializer = this.module.createI32(global.constantIntegerValue ? global.constantIntegerValue.toI32() & type.smallIntegerMask: 0);
} else
2017-12-13 04:46:05 +01:00
initializer = this.module.createI32(global.constantIntegerValue ? global.constantIntegerValue.toI32() : 0);
} else if (declaration) {
if (declaration.initializer) {
initializer = this.compileExpression(declaration.initializer, type);
if (_BinaryenExpressionGetId(initializer) != ExpressionId.Const) {
2017-12-13 04:46:05 +01:00
if (!global.isMutable) {
initializer = this.precomputeExpressionRef(initializer);
if (_BinaryenExpressionGetId(initializer) != ExpressionId.Const) {
this.warning(DiagnosticCode.Compiling_constant_global_with_non_constant_initializer_as_mutable, declaration.range);
initializeInStart = true;
}
} else
initializeInStart = true;
}
} else
2017-11-26 04:03:28 +01:00
initializer = typeToNativeZero(this.module, type);
} else
throw new Error("unexpected missing declaration or constant value");
2017-12-16 02:27:39 +01:00
2017-12-13 04:46:05 +01:00
const internalName: string = global.internalName;
2017-10-07 14:29:43 +02:00
if (initializeInStart) {
this.module.addGlobal(internalName, nativeType, true, typeToNativeZero(this.module, type));
this.startFunctionBody.push(this.module.createSetGlobal(internalName, initializer));
} else {
2017-12-13 04:46:05 +01:00
this.module.addGlobal(internalName, nativeType, global.isMutable, initializer);
if (!global.isMutable) {
const exprType: NativeType = _BinaryenExpressionGetType(initializer);
switch (exprType) {
case NativeType.I32:
2017-12-13 04:46:05 +01:00
global.constantIntegerValue = new I64(_BinaryenConstGetValueI32(initializer), 0);
break;
case NativeType.I64:
2017-12-13 04:46:05 +01:00
global.constantIntegerValue = new I64(_BinaryenConstGetValueI64Low(initializer), _BinaryenConstGetValueI64High(initializer));
break;
case NativeType.F32:
2017-12-13 04:46:05 +01:00
global.constantFloatValue = _BinaryenConstGetValueF32(initializer);
break;
case NativeType.F64:
2017-12-13 04:46:05 +01:00
global.constantFloatValue = _BinaryenConstGetValueF64(initializer);
break;
default:
throw new Error("unexpected initializer type");
}
2017-12-13 04:46:05 +01:00
global.hasConstantValue = true;
}
}
2017-12-13 04:46:05 +01:00
global.isCompiled = true;
return true;
2017-09-28 13:08:25 +02:00
}
// enums
compileEnumDeclaration(declaration: EnumDeclaration): void {
const element: Element | null = <Element | null>this.program.elements.get(declaration.internalName);
if (!element || element.kind != ElementKind.ENUM)
throw new Error("unexpected missing enum");
this.compileEnum(<Enum>element);
2017-10-07 14:29:43 +02:00
}
compileEnum(element: Enum): void {
2017-12-01 02:08:03 +01:00
if (element.isCompiled)
2017-10-07 14:29:43 +02:00
return;
let previousValue: EnumValue | null = null;
if (element.members)
for (let [key, member] of element.members) {
if (member.kind != ElementKind.ENUMVALUE)
continue;
const val: EnumValue = <EnumValue>member;
if (val.hasConstantValue) {
this.module.addGlobal(val.internalName, NativeType.I32, false, this.module.createI32(val.constantValue));
} else if (val.declaration) {
const declaration: EnumValueDeclaration = val.declaration;
let initializer: ExpressionRef;
let initializeInStart: bool = false;
if (declaration.value) {
initializer = this.compileExpression(<Expression>declaration.value, Type.i32);
if (_BinaryenExpressionGetId(initializer) != ExpressionId.Const) {
initializer = this.precomputeExpressionRef(initializer);
if (_BinaryenExpressionGetId(initializer) != ExpressionId.Const) {
if (element.isConstant)
this.warning(DiagnosticCode.Compiling_constant_global_with_non_constant_initializer_as_mutable, declaration.range);
initializeInStart = true;
}
}
} else if (previousValue == null) {
initializer = this.module.createI32(0);
} else if (previousValue.hasConstantValue) {
initializer = this.module.createI32(previousValue.constantValue + 1);
} else {
// in TypeScript this errors with TS1061, but actually we can do:
initializer = this.module.createBinary(BinaryOp.AddI32,
this.module.createGetGlobal(previousValue.internalName, NativeType.I32),
this.module.createI32(1)
);
if (element.isConstant)
this.warning(DiagnosticCode.Compiling_constant_global_with_non_constant_initializer_as_mutable, declaration.range);
initializeInStart = true;
}
if (initializeInStart) {
this.module.addGlobal(val.internalName, NativeType.I32, true, this.module.createI32(0));
this.startFunctionBody.push(this.module.createSetGlobal(val.internalName, initializer));
} else {
this.module.addGlobal(val.internalName, NativeType.I32, false, initializer);
if (_BinaryenExpressionGetType(initializer) == NativeType.I32) {
val.constantValue = _BinaryenConstGetValueI32(initializer);
val.hasConstantValue = true;
} else
throw new Error("unexpected initializer type");
}
} else
throw new Error("unexpected missing declaration or constant value");
previousValue = <EnumValue>val;
}
element.isCompiled = true;
}
2017-10-07 14:29:43 +02:00
// functions
2017-10-07 14:29:43 +02:00
compileFunctionDeclaration(declaration: FunctionDeclaration, typeArguments: TypeNode[], contextualTypeArguments: Map<string,Type> | null = null, alternativeReportNode: Node | null = null): void {
const internalName: string = declaration.internalName;
const element: Element | null = <Element | null>this.program.elements.get(internalName);
2017-11-17 14:33:51 +01:00
if (!element || element.kind != ElementKind.FUNCTION_PROTOTYPE)
throw new Error("unexpected missing function");
2017-12-02 23:33:01 +01:00
const instance: Function | null = this.compileFunctionUsingTypeArguments(<FunctionPrototype>element, typeArguments, contextualTypeArguments, alternativeReportNode);
2017-12-04 19:26:50 +01:00
if (!instance)
return;
2017-12-13 23:24:13 +01:00
if (isModuleExport(instance, declaration))
2017-12-18 03:46:36 +01:00
this.module.addFunctionExport(instance.internalName, declaration.name.name);
2017-11-20 23:39:50 +01:00
}
2017-12-02 23:33:01 +01:00
compileFunctionUsingTypeArguments(prototype: FunctionPrototype, typeArguments: TypeNode[], contextualTypeArguments: Map<string,Type> | null = null, alternativeReportNode: Node | null = null): Function | null {
2017-11-20 23:39:50 +01:00
const instance: Function | null = prototype.resolveInclTypeArguments(typeArguments, contextualTypeArguments, alternativeReportNode); // reports
if (!instance)
2017-12-02 23:33:01 +01:00
return null;
return this.compileFunction(instance) ? instance : null;
}
2017-10-07 14:29:43 +02:00
2017-12-02 23:33:01 +01:00
compileFunction(instance: Function): bool {
2017-12-01 02:08:03 +01:00
if (instance.isCompiled)
2017-12-02 23:33:01 +01:00
return true;
2017-10-07 14:29:43 +02:00
2017-12-13 04:46:05 +01:00
const declaration: FunctionDeclaration | null = instance.prototype.declaration;
if (!declaration)
throw new Error("unexpected missing declaration");
2017-10-07 14:29:43 +02:00
2017-12-13 04:46:05 +01:00
if (instance.isDeclared) {
2017-12-04 19:26:50 +01:00
if (declaration.statements) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.An_implementation_cannot_be_declared_in_ambient_contexts, declaration.name.range);
2017-12-04 19:26:50 +01:00
return false;
}
} else {
if (!declaration.statements) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration, declaration.name.range);
2017-12-04 19:26:50 +01:00
return false;
}
2017-10-07 14:29:43 +02:00
}
2017-12-01 02:08:03 +01:00
instance.isCompiled = true;
2017-10-07 14:29:43 +02:00
// compile statements
2017-12-04 19:26:50 +01:00
let stmts: ExpressionRef[] | null = null;
2017-12-13 04:46:05 +01:00
if (!instance.isDeclared) {
2017-12-04 19:26:50 +01:00
const previousFunction: Function = this.currentFunction;
this.currentFunction = instance;
stmts = this.compileStatements(<Statement[]>declaration.statements);
this.currentFunction = previousFunction;
}
2017-10-07 14:29:43 +02:00
2017-12-04 19:26:50 +01:00
// create the function type
let k: i32 = instance.parameters.length;
2017-12-14 11:55:35 +01:00
const nativeResultType: NativeType = typeToNativeType(instance.returnType);
const nativeParamTypes: NativeType[] = new Array(k);
const signatureNameParts: string[] = new Array(k + 1);
for (let i: i32 = 0; i < k; ++i) {
2017-12-14 11:55:35 +01:00
nativeParamTypes[i] = typeToNativeType(instance.parameters[i].type);
signatureNameParts[i] = typeToSignatureNamePart(instance.parameters[i].type);
}
signatureNameParts[k] = typeToSignatureNamePart(instance.returnType);
2017-12-14 11:55:35 +01:00
let typeRef: FunctionTypeRef = this.module.getFunctionTypeBySignature(nativeResultType, nativeParamTypes);
2017-11-26 04:03:28 +01:00
if (!typeRef)
2017-12-14 11:55:35 +01:00
typeRef = this.module.addFunctionType(signatureNameParts.join(""), nativeResultType, nativeParamTypes);
2017-12-04 19:26:50 +01:00
// create the function
const internalName: string = instance.internalName;
2017-12-13 23:24:13 +01:00
if (instance.isDeclared) { // TODO: use parent namespace as externalModuleName, if applicable
2017-12-18 03:46:36 +01:00
this.module.addFunctionImport(internalName, "env", declaration.name.name, typeRef);
2017-12-04 19:26:50 +01:00
} else {
this.module.addFunction(internalName, typeRef, typesToNativeTypes(instance.additionalLocals), this.module.createBlock(null, <ExpressionRef[]>stmts, NativeType.None));
}
2017-12-15 15:00:19 +01:00
instance.finalize();
2017-12-02 23:33:01 +01:00
return true;
2017-09-28 13:08:25 +02:00
}
// namespaces
2017-10-02 12:52:15 +02:00
compileNamespaceDeclaration(declaration: NamespaceDeclaration): void {
2017-10-07 14:29:43 +02:00
const members: Statement[] = declaration.members;
const noTreeShaking: bool = this.options.noTreeShaking;
2017-10-07 14:29:43 +02:00
for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) {
const member: Statement = members[i];
switch (member.kind) {
case NodeKind.CLASS:
if ((noTreeShaking || hasModifier(ModifierKind.EXPORT, (<ClassDeclaration>member).modifiers)) && !(<ClassDeclaration>member).typeParameters.length)
this.compileClassDeclaration(<ClassDeclaration>member, []);
2017-10-07 14:29:43 +02:00
break;
2017-12-15 15:00:19 +01:00
case NodeKind.INTERFACE:
if ((noTreeShaking || hasModifier(ModifierKind.EXPORT, (<InterfaceDeclaration>member).modifiers)) && !(<InterfaceDeclaration>member).typeParameters.length)
this.compileInterfaceDeclaration(<InterfaceDeclaration>member, []);
break;
2017-10-07 14:29:43 +02:00
case NodeKind.ENUM:
if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (<EnumDeclaration>member).modifiers))
this.compileEnumDeclaration(<EnumDeclaration>member);
2017-10-07 14:29:43 +02:00
break;
case NodeKind.FUNCTION:
if ((noTreeShaking || hasModifier(ModifierKind.EXPORT, (<FunctionDeclaration>member).modifiers)) && !(<FunctionDeclaration>member).typeParameters.length)
this.compileFunctionDeclaration(<FunctionDeclaration>member, []);
2017-10-07 14:29:43 +02:00
break;
case NodeKind.NAMESPACE:
if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (<NamespaceDeclaration>member).modifiers))
this.compileNamespaceDeclaration(<NamespaceDeclaration>member);
2017-10-07 14:29:43 +02:00
break;
case NodeKind.VARIABLE:
if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (<VariableStatement>member).modifiers))
this.compileVariableStatement(<VariableStatement>member);
2017-10-07 14:29:43 +02:00
break;
default:
throw new Error("unexpected namespace member");
2017-10-07 14:29:43 +02:00
}
}
2017-09-28 13:08:25 +02:00
}
compileNamespace(ns: Namespace): void {
if (!ns.members) return;
const noTreeShaking: bool = this.options.noTreeShaking;
for (let [name, element] of ns.members) {
switch (element.kind) {
2017-10-02 12:52:15 +02:00
2017-11-17 14:33:51 +01:00
case ElementKind.CLASS_PROTOTYPE:
2017-12-13 04:46:05 +01:00
if ((noTreeShaking || (<ClassPrototype>element).isExported) && !(<ClassPrototype>element).isGeneric)
2017-11-20 23:39:50 +01:00
this.compileClassUsingTypeArguments(<ClassPrototype>element, []);
break;
2017-10-02 12:52:15 +02:00
case ElementKind.ENUM:
this.compileEnum(<Enum>element);
2017-10-02 12:52:15 +02:00
break;
2017-11-17 14:33:51 +01:00
case ElementKind.FUNCTION_PROTOTYPE:
2017-12-13 04:46:05 +01:00
if ((noTreeShaking || (<FunctionPrototype>element).isExported) && !(<FunctionPrototype>element).isGeneric)
2017-11-20 23:39:50 +01:00
this.compileFunctionUsingTypeArguments(<FunctionPrototype>element, []);
2017-10-02 12:52:15 +02:00
break;
case ElementKind.GLOBAL:
this.compileGlobal(<Global>element);
2017-10-02 12:52:15 +02:00
break;
case ElementKind.NAMESPACE:
this.compileNamespace(<Namespace>element);
2017-10-02 12:52:15 +02:00
break;
}
}
}
// exports
2017-10-02 12:52:15 +02:00
compileExportStatement(statement: ExportStatement): void {
const members: ExportMember[] = statement.members;
for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) {
const member: ExportMember = members[i];
const internalExportName: string = statement.range.source.internalPath + PATH_DELIMITER + member.externalIdentifier.name;
const element: Element | null = <Element | null>this.program.exports.get(internalExportName);
if (!element) // reported in Program#initialize
continue;
switch (element.kind) {
2017-11-17 14:33:51 +01:00
case ElementKind.CLASS_PROTOTYPE:
if (!(<ClassPrototype>element).isGeneric)
2017-11-20 23:39:50 +01:00
this.compileClassUsingTypeArguments(<ClassPrototype>element, []);
2017-10-02 12:52:15 +02:00
break;
case ElementKind.ENUM:
this.compileEnum(<Enum>element);
break;
2017-11-17 14:33:51 +01:00
case ElementKind.FUNCTION_PROTOTYPE:
2017-12-02 23:33:01 +01:00
if (!(<FunctionPrototype>element).isGeneric) {
const functionInstance: Function | null = this.compileFunctionUsingTypeArguments(<FunctionPrototype>element, []);
if (functionInstance && statement.range.source.isEntry)
2017-12-05 15:28:01 +01:00
this.module.addFunctionExport(functionInstance.internalName, member.externalIdentifier.name);
2017-12-02 23:33:01 +01:00
}
break;
case ElementKind.GLOBAL:
if (this.compileGlobal(<Global>element) && statement.range.source.isEntry) {
if ((<Global>element).hasConstantValue)
this.module.addGlobalExport(element.internalName, member.externalIdentifier.name);
else
this.warning(DiagnosticCode.Cannot_export_a_mutable_global, member.range);
}
break;
case ElementKind.NAMESPACE:
this.compileNamespace(<Namespace>element);
break;
2017-10-02 12:52:15 +02:00
}
}
}
// classes
compileClassDeclaration(declaration: ClassDeclaration, typeArguments: TypeNode[], contextualTypeArguments: Map<string,Type> | null = null, alternativeReportNode: Node | null = null): void {
const internalName: string = declaration.internalName;
const element: Element | null = <Element | null>this.program.elements.get(internalName);
2017-11-17 14:33:51 +01:00
if (!element || element.kind != ElementKind.CLASS_PROTOTYPE)
throw new Error("unexpected missing class");
2017-11-20 23:39:50 +01:00
this.compileClassUsingTypeArguments(<ClassPrototype>element, typeArguments, contextualTypeArguments, alternativeReportNode);
}
compileClassUsingTypeArguments(prototype: ClassPrototype, typeArguments: TypeNode[], contextualTypeArguments: Map<string,Type> | null = null, alternativeReportNode: Node | null = null): void {
const instance: Class | null = prototype.resolveInclTypeArguments(typeArguments, contextualTypeArguments, alternativeReportNode);
if (!instance)
return;
2017-11-20 23:39:50 +01:00
this.compileClass(instance);
}
2017-11-20 23:39:50 +01:00
compileClass(cls: Class) {
throw new Error("not implemented");
2017-10-02 12:52:15 +02:00
}
2017-12-15 15:00:19 +01:00
compileInterfaceDeclaration(declaration: InterfaceDeclaration, typeArguments: TypeNode[], contextualTypeArguments: Map<string,Type> | null = null, alternativeReportNode: Node | null = null): void {
throw new Error("not implemented");
}
2017-09-28 13:08:25 +02:00
// memory
2017-12-14 11:55:35 +01:00
/** Adds a static memory segment with the specified data. */
2017-09-28 13:08:25 +02:00
addMemorySegment(buffer: Uint8Array): MemorySegment {
if (this.memoryOffset.lo & 7) { // align to 8 bytes so any native data type is aligned here
2017-09-28 13:08:25 +02:00
this.memoryOffset.or32(7);
this.memoryOffset.add32(1);
}
const segment: MemorySegment = MemorySegment.create(buffer, this.memoryOffset.clone());
this.memorySegments.push(segment);
this.memoryOffset.add32(buffer.length);
return segment;
}
// types
2017-12-14 11:55:35 +01:00
// TODO: try to get rid of this
determineExpressionType(expression: Expression, contextualType: Type): Type {
2017-10-07 14:29:43 +02:00
const previousType: Type = this.currentType;
const previousNoEmit: bool = this.module.noEmit;
this.module.noEmit = true;
this.compileExpression(expression, contextualType, ConversionKind.NONE); // now performs a dry run
2017-10-07 14:29:43 +02:00
const type: Type = this.currentType;
this.currentType = previousType;
this.module.noEmit = previousNoEmit;
return type;
}
2017-09-28 13:08:25 +02:00
// statements
2017-11-26 04:03:28 +01:00
compileStatement(statement: Statement): ExpressionRef {
2017-09-28 13:08:25 +02:00
switch (statement.kind) {
case NodeKind.BLOCK:
return this.compileBlockStatement(<BlockStatement>statement);
case NodeKind.BREAK:
return this.compileBreakStatement(<BreakStatement>statement);
case NodeKind.CONTINUE:
return this.compileContinueStatement(<ContinueStatement>statement);
case NodeKind.DO:
return this.compileDoStatement(<DoStatement>statement);
case NodeKind.EMPTY:
return this.compileEmptyStatement(<EmptyStatement>statement);
case NodeKind.EXPRESSION:
return this.compileExpressionStatement(<ExpressionStatement>statement);
case NodeKind.FOR:
return this.compileForStatement(<ForStatement>statement);
case NodeKind.IF:
return this.compileIfStatement(<IfStatement>statement);
case NodeKind.RETURN:
return this.compileReturnStatement(<ReturnStatement>statement);
case NodeKind.SWITCH:
return this.compileSwitchStatement(<SwitchStatement>statement);
case NodeKind.THROW:
return this.compileThrowStatement(<ThrowStatement>statement);
case NodeKind.TRY:
return this.compileTryStatement(<TryStatement>statement);
2017-12-20 13:36:39 +01:00
case NodeKind.TYPEDECLARATION:
if (this.currentFunction == this.startFunction)
return this.module.createNop();
break; // must be top-level; function bodies are not initialized
2017-09-28 13:08:25 +02:00
case NodeKind.VARIABLE:
return this.compileVariableStatement(<VariableStatement>statement);
case NodeKind.WHILE:
return this.compileWhileStatement(<WhileStatement>statement);
}
2017-12-20 13:36:39 +01:00
this.error(DiagnosticCode.Operation_not_supported, statement.range);
throw new Error("unexpected statement kind");
}
2017-11-26 04:03:28 +01:00
compileStatements(statements: Statement[]): ExpressionRef[] {
const k: i32 = statements.length;
2017-11-26 04:03:28 +01:00
const stmts: ExpressionRef[] = new Array(k);
for (let i: i32 = 0; i < k; ++i)
stmts[i] = this.compileStatement(statements[i]);
return stmts;
2017-09-28 13:08:25 +02:00
}
2017-11-26 04:03:28 +01:00
compileBlockStatement(statement: BlockStatement): ExpressionRef {
2017-12-05 15:06:44 +01:00
const statements: Statement[] = 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);
2017-09-28 13:08:25 +02:00
}
2017-11-26 04:03:28 +01:00
compileBreakStatement(statement: BreakStatement): ExpressionRef {
if (statement.label)
throw new Error("not implemented");
2017-10-07 14:29:43 +02:00
const context: string | null = this.currentFunction.breakContext;
if (context != null)
return this.module.createBreak("break|" + (<string>context));
2017-10-07 14:29:43 +02:00
this.error(DiagnosticCode.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement, statement.range);
return this.module.createUnreachable();
2017-09-28 13:08:25 +02:00
}
2017-11-26 04:03:28 +01:00
compileContinueStatement(statement: ContinueStatement): ExpressionRef {
if (statement.label)
throw new Error("not implemented");
2017-10-07 14:29:43 +02:00
const context: string | null = this.currentFunction.breakContext;
if (context != null && !this.disallowContinue)
return this.module.createBreak("continue|" + (<string>context));
2017-10-07 14:29:43 +02:00
this.error(DiagnosticCode.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement, statement.range);
return this.module.createUnreachable();
2017-09-28 13:08:25 +02:00
}
2017-11-26 04:03:28 +01:00
compileDoStatement(statement: DoStatement): ExpressionRef {
2017-10-07 14:29:43 +02:00
const label: string = this.currentFunction.enterBreakContext();
2017-11-26 04:03:28 +01:00
const condition: ExpressionRef = this.compileExpression(statement.condition, Type.i32);
const body: ExpressionRef = this.compileStatement(statement.statement);
2017-10-07 14:29:43 +02:00
this.currentFunction.leaveBreakContext();
const breakLabel: string = "break|" + label;
const continueLabel: string = "continue|" + label;
2017-10-07 14:29:43 +02:00
return this.module.createBlock(breakLabel, [
this.module.createLoop(continueLabel,
this.module.createBlock(null, [
body,
this.module.createBreak(continueLabel, condition)
2017-11-26 04:03:28 +01:00
], NativeType.None))
], NativeType.None);
2017-09-28 13:08:25 +02:00
}
2017-11-26 04:03:28 +01:00
compileEmptyStatement(statement: EmptyStatement): ExpressionRef {
2017-09-28 13:08:25 +02:00
return this.module.createNop();
}
2017-11-26 04:03:28 +01:00
compileExpressionStatement(statement: ExpressionStatement): ExpressionRef {
let expr: ExpressionRef = this.compileExpression(statement.expression, Type.void, ConversionKind.NONE);
if (this.currentType != Type.void) {
expr = this.module.createDrop(expr);
this.currentType = Type.void;
}
return expr;
2017-09-28 13:08:25 +02:00
}
2017-11-26 04:03:28 +01:00
compileForStatement(statement: ForStatement): ExpressionRef {
2017-10-07 14:29:43 +02:00
const context: string = this.currentFunction.enterBreakContext();
const variableWasLocal: bool = this.variableIsLocal;
if (this.currentFunction == this.startFunction)
this.variableIsLocal = true;
2017-11-26 04:03:28 +01:00
const initializer: ExpressionRef = statement.initializer ? this.compileStatement(<Statement>statement.initializer) : this.module.createNop();
this.variableIsLocal = variableWasLocal;
2017-11-26 04:03:28 +01:00
const condition: ExpressionRef = statement.condition ? this.compileExpression(<Expression>statement.condition, Type.i32) : this.module.createI32(1);
const incrementor: ExpressionRef = statement.incrementor ? this.compileExpression(<Expression>statement.incrementor, Type.void) : this.module.createNop();
const body: ExpressionRef = this.compileStatement(statement.statement);
2017-10-07 14:29:43 +02:00
this.currentFunction.leaveBreakContext();
const continueLabel: string = "continue|" + context;
const breakLabel: string = "break|" + context;
2017-10-07 14:29:43 +02:00
return this.module.createBlock(breakLabel, [
initializer,
this.module.createLoop(continueLabel, this.module.createBlock(null, [
this.module.createIf(condition, this.module.createBlock(null, [
body,
incrementor,
this.module.createBreak(continueLabel)
2017-11-26 04:03:28 +01:00
], NativeType.None))
], NativeType.None))
], NativeType.None);
2017-09-28 13:08:25 +02:00
}
2017-11-26 04:03:28 +01:00
compileIfStatement(statement: IfStatement): ExpressionRef {
const condition: ExpressionRef = this.compileExpression(statement.condition, Type.i32);
2017-12-18 03:46:36 +01:00
const ifTrue: ExpressionRef = this.compileStatement(statement.ifTrue);
const ifFalse: ExpressionRef = statement.ifFalse ? this.compileStatement(<Statement>statement.ifFalse) : 0;
2017-09-28 13:08:25 +02:00
return this.module.createIf(condition, ifTrue, ifFalse);
}
2017-11-26 04:03:28 +01:00
compileReturnStatement(statement: ReturnStatement): ExpressionRef {
2017-09-28 13:08:25 +02:00
if (this.currentFunction) {
2017-12-18 03:46:36 +01:00
const expression: ExpressionRef = statement.value ? this.compileExpression(<Expression>statement.value, this.currentFunction.returnType) : 0;
2017-09-28 13:08:25 +02:00
return this.module.createReturn(expression);
}
return this.module.createUnreachable();
}
2017-11-26 04:03:28 +01:00
compileSwitchStatement(statement: SwitchStatement): ExpressionRef {
const context: string = this.currentFunction.enterBreakContext();
const previousDisallowContinue: bool = this.disallowContinue;
this.disallowContinue = true;
// introduce a local for evaluating the condition (exactly once)
2017-12-12 09:38:20 +01:00
const tempLocal: Local = this.currentFunction.getTempLocal(Type.i32);
let i: i32, k: i32 = statement.cases.length;
// prepend initializer to inner block
2017-11-26 04:03:28 +01:00
const breaks: ExpressionRef[] = new Array(1 + k);
2017-12-18 03:46:36 +01:00
breaks[0] = this.module.createSetLocal(tempLocal.index, this.compileExpression(statement.condition, Type.i32)); // initializer
2017-12-02 20:58:39 +01:00
// make one br_if per (possibly dynamic) labeled case (binaryen optimizes to br_table where possible)
let breakIndex: i32 = 1;
let defaultIndex: i32 = -1;
for (i = 0; i < k; ++i) {
const case_: SwitchCase = statement.cases[i];
if (case_.label) {
breaks[breakIndex++] = this.module.createBreak("case" + i.toString(10) + "|" + context,
2017-12-02 20:58:39 +01:00
this.module.createBinary(BinaryOp.EqI32,
2017-12-12 09:38:20 +01:00
this.module.createGetLocal(tempLocal.index, NativeType.I32),
2017-12-02 20:58:39 +01:00
this.compileExpression(case_.label, Type.i32)
)
);
} else
defaultIndex = i;
}
2017-12-12 09:38:20 +01:00
this.currentFunction.freeTempLocal(tempLocal);
// otherwise br to default respectively out of the switch if there is no default case
breaks[breakIndex] = this.module.createBreak((defaultIndex >= 0
? "case" + defaultIndex.toString(10)
: "break"
) + "|" + context);
// nest blocks in order
let currentBlock: ExpressionRef = this.module.createBlock("case0|" + context, breaks, NativeType.None);
for (i = 0; i < k; ++i) {
const case_: SwitchCase = statement.cases[i];
const nextLabel: string = i == k - 1
? "break|" + context
: "case" + (i + 1).toString(10) + "|" + context;
const l: i32 = case_.statements.length;
2017-11-26 04:03:28 +01:00
const body: ExpressionRef[] = new Array(1 + l);
body[0] = currentBlock;
for (let j: i32 = 0; j < l; ++j)
body[j + 1] = this.compileStatement(case_.statements[j]);
2017-11-26 04:03:28 +01:00
currentBlock = this.module.createBlock(nextLabel, body, NativeType.None);
}
this.currentFunction.leaveBreakContext();
this.disallowContinue = previousDisallowContinue;
return currentBlock;
2017-09-28 13:08:25 +02:00
}
2017-11-26 04:03:28 +01:00
compileThrowStatement(statement: ThrowStatement): ExpressionRef {
2017-09-28 13:08:25 +02:00
return this.module.createUnreachable(); // TODO: waiting for exception-handling spec
}
2017-11-26 04:03:28 +01:00
compileTryStatement(statement: TryStatement): ExpressionRef {
2017-09-28 13:08:25 +02:00
throw new Error("not implemented");
2017-10-07 14:29:43 +02:00
// can't yet support something like: try { return ... } finally { ... }
// worthwhile to investigate lowering returns to block results (here)?
2017-09-28 13:08:25 +02:00
}
2017-11-26 04:03:28 +01:00
compileVariableStatement(statement: VariableStatement): ExpressionRef {
const declarations: VariableDeclaration[] = statement.declarations;
2017-10-07 14:29:43 +02:00
// top-level variables become globals
if (this.currentFunction == this.startFunction && !this.variableIsLocal) {
const isConst: bool = hasModifier(ModifierKind.CONST, statement.modifiers);
for (let i: i32 = 0, k: i32 = declarations.length; i < k; ++i)
this.compileGlobalDeclaration(declarations[i], isConst);
2017-10-07 14:29:43 +02:00
return this.module.createNop();
}
// other variables become locals
2017-11-26 04:03:28 +01:00
const initializers: ExpressionRef[] = new Array();
2017-10-07 14:29:43 +02:00
for (let i: i32 = 0, k = declarations.length; i < k; ++i) {
const declaration: VariableDeclaration = declarations[i];
if (declaration.type) {
2017-12-18 03:46:36 +01:00
const name: string = declaration.name.name;
const type: Type | null = this.program.resolveType(<TypeNode>declaration.type, this.currentFunction.contextualTypeArguments, true); // reports
2017-10-07 14:29:43 +02:00
if (type) {
if (this.currentFunction.locals.has(name))
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, name); // recoverable
2017-10-07 14:29:43 +02:00
else
this.currentFunction.addLocal(<Type>type, name);
if (declaration.initializer)
2017-12-18 03:46:36 +01:00
initializers.push(this.compileAssignment(declaration.name, <Expression>declaration.initializer, Type.void));
2017-10-07 14:29:43 +02:00
}
2017-12-14 11:55:35 +01:00
} else {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Type_expected, declaration.name.range);
2017-10-07 14:29:43 +02:00
}
}
2017-11-26 04:03:28 +01:00
return initializers.length ? this.module.createBlock(null, initializers, NativeType.None) : this.module.createNop();
2017-09-28 13:08:25 +02:00
}
2017-11-26 04:03:28 +01:00
compileWhileStatement(statement: WhileStatement): ExpressionRef {
2017-10-02 12:52:15 +02:00
const label: string = this.currentFunction.enterBreakContext();
2017-11-26 04:03:28 +01:00
const condition: ExpressionRef = this.compileExpression(statement.condition, Type.i32);
const breakLabel: string = "break|" + label;
const continueLabel: string = "continue|" + label;
2017-11-26 04:03:28 +01:00
const body: ExpressionRef = this.compileStatement(statement.statement);
2017-10-02 12:52:15 +02:00
this.currentFunction.leaveBreakContext();
return this.module.createBlock(breakLabel, [
this.module.createLoop(continueLabel,
this.module.createIf(condition, this.module.createBlock(null, [
body,
this.module.createBreak(continueLabel)
2017-11-26 04:03:28 +01:00
], NativeType.None))
)
2017-11-26 04:03:28 +01:00
], NativeType.None);
2017-09-28 13:08:25 +02:00
}
// expressions
compileExpression(expression: Expression, contextualType: Type, conversionKind: ConversionKind = ConversionKind.IMPLICIT): ExpressionRef {
this.currentType = contextualType;
2017-09-28 13:08:25 +02:00
2017-11-26 04:03:28 +01:00
let expr: ExpressionRef;
2017-09-28 13:08:25 +02:00
switch (expression.kind) {
case NodeKind.ASSERTION:
expr = this.compileAssertionExpression(<AssertionExpression>expression, contextualType);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.BINARY:
expr = this.compileBinaryExpression(<BinaryExpression>expression, contextualType);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.CALL:
expr = this.compileCallExpression(<CallExpression>expression, contextualType);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.ELEMENTACCESS:
expr = this.compileElementAccessExpression(<ElementAccessExpression>expression, contextualType);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.IDENTIFIER:
case NodeKind.FALSE:
case NodeKind.NULL:
case NodeKind.THIS:
case NodeKind.TRUE:
expr = this.compileIdentifierExpression(<IdentifierExpression>expression, contextualType);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.LITERAL:
expr = this.compileLiteralExpression(<LiteralExpression>expression, contextualType);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.NEW:
expr = this.compileNewExpression(<NewExpression>expression, contextualType);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.PARENTHESIZED:
expr = this.compileParenthesizedExpression(<ParenthesizedExpression>expression, contextualType);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.PROPERTYACCESS:
expr = this.compilePropertyAccessExpression(<PropertyAccessExpression>expression, contextualType);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.TERNARY:
expr = this.compileTernaryExpression(<TernaryExpression>expression, contextualType);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.UNARYPOSTFIX:
expr = this.compileUnaryPostfixExpression(<UnaryPostfixExpression>expression, contextualType);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.UNARYPREFIX:
expr = this.compileUnaryPrefixExpression(<UnaryPrefixExpression>expression, contextualType);
2017-09-28 13:08:25 +02:00
break;
default:
throw new Error("unexpected expression kind");
}
2017-12-05 15:06:44 +01:00
if (conversionKind != ConversionKind.NONE && this.currentType != contextualType) {
expr = this.convertExpression(expr, this.currentType, contextualType, conversionKind, expression);
this.currentType = contextualType;
2017-09-28 13:08:25 +02:00
}
return expr;
}
precomputeExpression(expression: Expression, contextualType: Type, conversionKind: ConversionKind = ConversionKind.IMPLICIT): ExpressionRef {
const expr: ExpressionRef = this.compileExpression(expression, contextualType, conversionKind);
return this.precomputeExpressionRef(expr);
}
precomputeExpressionRef(expr: ExpressionRef): ExpressionRef {
const nativeType: NativeType = typeToNativeType(this.currentType);
let typeRef: FunctionTypeRef = this.module.getFunctionTypeBySignature(nativeType, []);
if (!typeRef)
typeRef = this.module.addFunctionType(typeToSignatureNamePart(this.currentType), nativeType, []);
const funcRef: FunctionRef = this.module.addFunction("__precompute", typeRef, [], expr);
this.module.runPasses([ "precompute" ], funcRef);
const ret: ExpressionRef = _BinaryenFunctionGetBody(funcRef);
this.module.removeFunction("__precompute");
// TODO: also remove the function type somehow if no longer used or make the C-API accept
// a `null` typeRef, using an implicit type.
return ret;
}
convertExpression(expr: ExpressionRef, fromType: Type, toType: Type, conversionKind: ConversionKind, reportNode: Node): ExpressionRef {
if (conversionKind == ConversionKind.NONE)
return expr;
2017-09-28 13:08:25 +02:00
2017-12-05 01:45:15 +01:00
if (!fromType) {
_BinaryenExpressionPrint(expr);
throw new Error("WHAT");
}
2017-09-28 13:08:25 +02:00
// void to any
if (fromType.kind == TypeKind.VOID) {
this.error(DiagnosticCode.Operation_not_supported, reportNode.range);
2017-10-07 14:29:43 +02:00
throw new Error("unexpected conversion from void");
}
2017-09-28 13:08:25 +02:00
// any to void
if (toType.kind == TypeKind.VOID)
return this.module.createDrop(expr);
const fromFloat: bool = fromType.isAnyFloat;
const toFloat: bool = toType.isAnyFloat;
const mod: Module = this.module;
let losesInformation: bool = false;
if (fromFloat) {
// float to float
if (toFloat) {
if (fromType.kind == TypeKind.F32) {
// f32 to f64
if (toType.kind == TypeKind.F64)
expr = mod.createUnary(UnaryOp.PromoteF32, expr);
// f64 to f32
} else if (toType.kind == TypeKind.F32) {
losesInformation = true;
expr = mod.createUnary(UnaryOp.DemoteF64, expr);
}
// float to int
} else {
losesInformation = true;
// f32 to int
if (fromType.kind == TypeKind.F32) {
if (toType.isSignedInteger) {
if (toType.isLongInteger)
expr = mod.createUnary(UnaryOp.TruncF32ToI64, expr);
2017-09-28 13:08:25 +02:00
else {
expr = mod.createUnary(UnaryOp.TruncF32ToI32, expr);
2017-09-28 13:08:25 +02:00
if (toType.isSmallInteger) {
expr = mod.createBinary(BinaryOp.ShlI32, expr, mod.createI32(toType.smallIntegerShift));
expr = mod.createBinary(BinaryOp.ShrI32, expr, mod.createI32(toType.smallIntegerShift));
}
}
} else {
if (toType.isLongInteger)
expr = mod.createUnary(UnaryOp.TruncF32ToU64, expr);
2017-09-28 13:08:25 +02:00
else {
expr = mod.createUnary(UnaryOp.TruncF32ToU32, expr);
2017-09-28 13:08:25 +02:00
if (toType.isSmallInteger)
expr = mod.createBinary(BinaryOp.AndI32, expr, mod.createI32(toType.smallIntegerMask));
}
}
// f64 to int
} else {
if (toType.isSignedInteger) {
if (toType.isLongInteger)
expr = mod.createUnary(UnaryOp.TruncF64ToI64, expr);
2017-09-28 13:08:25 +02:00
else {
expr = mod.createUnary(UnaryOp.TruncF64ToI32, expr);
2017-09-28 13:08:25 +02:00
if (toType.isSmallInteger) {
expr = mod.createBinary(BinaryOp.ShlI32, expr, mod.createI32(toType.smallIntegerShift));
expr = mod.createBinary(BinaryOp.ShrI32, expr, mod.createI32(toType.smallIntegerShift));
}
}
} else {
if (toType.isLongInteger)
expr = mod.createUnary(UnaryOp.TruncF64ToU64, expr);
2017-09-28 13:08:25 +02:00
else {
expr = mod.createUnary(UnaryOp.TruncF64ToU32, expr);
2017-09-28 13:08:25 +02:00
if (toType.isSmallInteger)
expr = mod.createBinary(BinaryOp.AndI32, expr, mod.createI32(toType.smallIntegerMask));
}
}
}
}
// int to float
} else if (toFloat) {
// int to f32
if (toType.kind == TypeKind.F32) {
if (fromType.isLongInteger) {
losesInformation = true;
if (fromType.isSignedInteger)
expr = mod.createUnary(UnaryOp.ConvertI64ToF32, expr);
2017-09-28 13:08:25 +02:00
else
expr = mod.createUnary(UnaryOp.ConvertU64ToF32, expr);
2017-12-14 11:55:35 +01:00
} else {
if (!fromType.isSmallInteger)
losesInformation = true;
2017-09-28 13:08:25 +02:00
if (fromType.isSignedInteger)
expr = mod.createUnary(UnaryOp.ConvertI32ToF32, expr);
2017-09-28 13:08:25 +02:00
else
expr = mod.createUnary(UnaryOp.ConvertU32ToF32, expr);
2017-12-14 11:55:35 +01:00
}
2017-09-28 13:08:25 +02:00
// int to f64
} else {
if (fromType.isLongInteger) {
losesInformation = true;
if (fromType.isSignedInteger)
expr = mod.createUnary(UnaryOp.ConvertI64ToF64, expr);
2017-09-28 13:08:25 +02:00
else
expr = mod.createUnary(UnaryOp.ConvertU64ToF64, expr);
2017-09-28 13:08:25 +02:00
} else
if (fromType.isSignedInteger)
expr = mod.createUnary(UnaryOp.ConvertI32ToF64, expr);
2017-09-28 13:08:25 +02:00
else
expr = mod.createUnary(UnaryOp.ConvertU32ToF64, expr);
2017-09-28 13:08:25 +02:00
}
// int to int
} else {
if (fromType.isLongInteger) {
// i64 to i32
if (!toType.isLongInteger) {
losesInformation = true;
2017-12-11 22:04:30 +01:00
expr = mod.createUnary(UnaryOp.WrapI64, expr); // discards upper bits
2017-09-28 13:08:25 +02:00
if (toType.isSmallInteger) {
if (toType.isSignedInteger) {
expr = mod.createBinary(BinaryOp.ShlI32, expr, mod.createI32(toType.smallIntegerShift));
expr = mod.createBinary(BinaryOp.ShrI32, expr, mod.createI32(toType.smallIntegerShift));
} else
expr = mod.createBinary(BinaryOp.AndI32, expr, mod.createI32(toType.smallIntegerMask));
}
}
// i32 to i64
} else if (toType.isLongInteger) {
if (toType.isSignedInteger)
expr = mod.createUnary(UnaryOp.ExtendI32, expr);
else
expr = mod.createUnary(UnaryOp.ExtendU32, expr);
2017-12-14 11:55:35 +01:00
// i32 or smaller to even smaller int
} else if (toType.isSmallInteger && fromType.size > toType.size) {
2017-09-28 13:08:25 +02:00
losesInformation = true;
if (toType.isSignedInteger) {
expr = mod.createBinary(BinaryOp.ShlI32, expr, mod.createI32(toType.smallIntegerShift));
expr = mod.createBinary(BinaryOp.ShrI32, expr, mod.createI32(toType.smallIntegerShift));
} else
expr = mod.createBinary(BinaryOp.AndI32, expr, mod.createI32(toType.smallIntegerMask));
}
}
if (losesInformation && conversionKind == ConversionKind.IMPLICIT)
this.error(DiagnosticCode.Conversion_from_type_0_to_1_requires_an_explicit_cast, reportNode.range, fromType.toString(), toType.toString());
2017-09-28 13:08:25 +02:00
return expr;
}
2017-11-26 04:03:28 +01:00
compileAssertionExpression(expression: AssertionExpression, contextualType: Type): ExpressionRef {
2017-11-17 14:33:51 +01:00
const toType: Type | null = this.program.resolveType(expression.toType, this.currentFunction.contextualTypeArguments); // reports
if (!toType)
return this.module.createUnreachable();
return this.compileExpression(expression.expression, toType, ConversionKind.EXPLICIT);
2017-09-28 13:08:25 +02:00
}
2017-11-26 04:03:28 +01:00
compileBinaryExpression(expression: BinaryExpression, contextualType: Type): ExpressionRef {
let op: BinaryOp;
2017-11-26 04:03:28 +01:00
let left: ExpressionRef;
let right: ExpressionRef;
let compound: Token = 0;
2017-12-04 16:26:34 +01:00
let condition: ExpressionRef;
let tempLocal: Local;
switch (expression.operator) {
case Token.LESSTHAN:
left = this.compileExpression(expression.left, contextualType, ConversionKind.NONE);
right = this.compileExpression(expression.right, this.currentType);
op = this.currentType == Type.f32
? BinaryOp.LtF32
: this.currentType == Type.f64
? BinaryOp.LtF64
: this.currentType.isSignedInteger
? this.currentType.isLongInteger
? BinaryOp.LtI64
: BinaryOp.LtI32
: this.currentType.isLongInteger
? BinaryOp.LtU64
: BinaryOp.LtU32;
this.currentType = Type.bool;
break;
case Token.GREATERTHAN:
left = this.compileExpression(expression.left, contextualType, ConversionKind.NONE);
right = this.compileExpression(expression.right, this.currentType);
op = this.currentType == Type.f32
? BinaryOp.GtF32
: this.currentType == Type.f64
? BinaryOp.GtF64
: this.currentType.isSignedInteger
? this.currentType.isLongInteger
? BinaryOp.GtI64
: BinaryOp.GtI32
: this.currentType.isLongInteger
? BinaryOp.GtU64
: BinaryOp.GtU32;
this.currentType = Type.bool;
break;
case Token.LESSTHAN_EQUALS:
left = this.compileExpression(expression.left, contextualType, ConversionKind.NONE);
right = this.compileExpression(expression.right, this.currentType);
op = this.currentType == Type.f32
? BinaryOp.LeF32
: this.currentType == Type.f64
? BinaryOp.LeF64
: this.currentType.isSignedInteger
? this.currentType.isLongInteger
? BinaryOp.LeI64
: BinaryOp.LeI32
: this.currentType.isLongInteger
? BinaryOp.LeU64
: BinaryOp.LeU32;
this.currentType = Type.bool;
break;
case Token.GREATERTHAN_EQUALS:
left = this.compileExpression(expression.left, contextualType, ConversionKind.NONE);
right = this.compileExpression(expression.right, this.currentType);
op = this.currentType == Type.f32
? BinaryOp.GeF32
: this.currentType == Type.f64
? BinaryOp.GeF64
: this.currentType.isSignedInteger
? this.currentType.isLongInteger
? BinaryOp.GeI64
: BinaryOp.GeI32
: this.currentType.isLongInteger
? BinaryOp.GeU64
: BinaryOp.GeU32;
this.currentType = Type.bool;
break;
case Token.EQUALS_EQUALS:
case Token.EQUALS_EQUALS_EQUALS:
left = this.compileExpression(expression.left, contextualType, ConversionKind.NONE);
right = this.compileExpression(expression.right, this.currentType);
op = this.currentType == Type.f32
? BinaryOp.EqF32
: this.currentType == Type.f64
? BinaryOp.EqF64
: this.currentType.isLongInteger
? BinaryOp.EqI64
: BinaryOp.EqI32;
this.currentType = Type.bool;
break;
case Token.EXCLAMATION_EQUALS:
case Token.EXCLAMATION_EQUALS_EQUALS:
left = this.compileExpression(expression.left, contextualType, ConversionKind.NONE);
right = this.compileExpression(expression.right, this.currentType);
op = this.currentType == Type.f32
? BinaryOp.NeF32
: this.currentType == Type.f64
? BinaryOp.NeF64
: this.currentType.isLongInteger
? BinaryOp.NeI64
: BinaryOp.NeI32;
this.currentType = Type.bool;
break;
2017-10-07 14:29:43 +02:00
case Token.EQUALS:
return this.compileAssignment(expression.left, expression.right, contextualType);
case Token.PLUS_EQUALS:
compound = Token.EQUALS;
case Token.PLUS:
left = this.compileExpression(expression.left, contextualType, ConversionKind.NONE);
right = this.compileExpression(expression.right, this.currentType);
op = this.currentType == Type.f32
? BinaryOp.AddF32
: this.currentType == Type.f64
? BinaryOp.AddF64
: this.currentType.isLongInteger
? BinaryOp.AddI64
: BinaryOp.AddI32;
break;
case Token.MINUS_EQUALS:
compound = Token.EQUALS;
case Token.MINUS:
left = this.compileExpression(expression.left, contextualType, ConversionKind.NONE);
right = this.compileExpression(expression.right, this.currentType);
op = this.currentType == Type.f32
? BinaryOp.SubF32
: this.currentType == Type.f64
? BinaryOp.SubF64
: this.currentType.isLongInteger
? BinaryOp.SubI64
: BinaryOp.SubI32;
break;
case Token.ASTERISK_EQUALS:
compound = Token.EQUALS;
case Token.ASTERISK:
left = this.compileExpression(expression.left, contextualType, ConversionKind.NONE);
right = this.compileExpression(expression.right, this.currentType);
op = this.currentType == Type.f32
? BinaryOp.MulF32
: this.currentType == Type.f64
? BinaryOp.MulF64
: this.currentType.isLongInteger
? BinaryOp.MulI64
: BinaryOp.MulI32;
break;
case Token.SLASH_EQUALS:
compound = Token.EQUALS;
case Token.SLASH:
left = this.compileExpression(expression.left, contextualType, ConversionKind.NONE);
right = this.compileExpression(expression.right, this.currentType);
op = this.currentType == Type.f32
? BinaryOp.DivF32
: this.currentType == Type.f64
? BinaryOp.DivF64
: this.currentType.isSignedInteger
? this.currentType.isLongInteger
? BinaryOp.DivI64
: BinaryOp.DivI32
: this.currentType.isLongInteger
? BinaryOp.DivU64
: BinaryOp.DivU32;
break;
case Token.PERCENT_EQUALS:
compound = Token.EQUALS;
case Token.PERCENT:
left = this.compileExpression(expression.left, contextualType, ConversionKind.NONE);
right = this.compileExpression(expression.right, this.currentType);
if (this.currentType.isAnyFloat)
2017-12-01 02:08:03 +01:00
throw new Error("not implemented"); // TODO: internal fmod, possibly simply imported from JS
op = this.currentType.isSignedInteger
? this.currentType.isLongInteger
? BinaryOp.RemI64
: BinaryOp.RemI32
: this.currentType.isLongInteger
? BinaryOp.RemU64
: BinaryOp.RemU32;
break;
case Token.LESSTHAN_LESSTHAN_EQUALS:
compound = Token.EQUALS;
case Token.LESSTHAN_LESSTHAN:
left = this.compileExpression(expression.left, contextualType.isAnyFloat ? Type.i64 : contextualType, ConversionKind.NONE);
right = this.compileExpression(expression.right, this.currentType);
op = this.currentType.isLongInteger
? BinaryOp.ShlI64
: BinaryOp.ShlI32;
break;
case Token.GREATERTHAN_GREATERTHAN_EQUALS:
compound = Token.EQUALS;
case Token.GREATERTHAN_GREATERTHAN:
left = this.compileExpression(expression.left, contextualType.isAnyFloat ? Type.i64 : contextualType, ConversionKind.NONE);
right = this.compileExpression(expression.right, this.currentType);
op = this.currentType.isSignedInteger
? this.currentType.isLongInteger
? BinaryOp.ShrI64
: BinaryOp.ShrI32
: this.currentType.isLongInteger
? BinaryOp.ShrU64
: BinaryOp.ShrU32;
break;
case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN_EQUALS:
compound = Token.EQUALS;
case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN:
left = this.compileExpression(expression.left, contextualType.isAnyFloat ? Type.u64 : contextualType, ConversionKind.NONE);
right = this.compileExpression(expression.right, this.currentType);
op = this.currentType.isLongInteger
? BinaryOp.ShrU64
: BinaryOp.ShrU32;
break;
case Token.AMPERSAND_EQUALS:
compound = Token.EQUALS;
case Token.AMPERSAND:
left = this.compileExpression(expression.left, contextualType.isAnyFloat ? Type.i64 : contextualType, ConversionKind.NONE);
right = this.compileExpression(expression.right, this.currentType);
op = this.currentType.isLongInteger
? BinaryOp.AndI64
: BinaryOp.AndI32;
break;
case Token.BAR_EQUALS:
compound = Token.EQUALS;
case Token.BAR:
left = this.compileExpression(expression.left, contextualType.isAnyFloat ? Type.i64 : contextualType, ConversionKind.NONE);
right = this.compileExpression(expression.right, this.currentType);
op = this.currentType.isLongInteger
? BinaryOp.OrI64
: BinaryOp.OrI32;
break;
case Token.CARET_EQUALS:
compound = Token.EQUALS;
case Token.CARET:
left = this.compileExpression(expression.left, contextualType.isAnyFloat ? Type.i64 : contextualType, ConversionKind.NONE);
right = this.compileExpression(expression.right, this.currentType);
op = this.currentType.isLongInteger
? BinaryOp.XorI64
: BinaryOp.XorI32;
break;
2017-12-04 16:26:34 +01:00
case Token.AMPERSAND_AMPERSAND: // left && right
left = this.compileExpression(expression.left, contextualType, ConversionKind.NONE);
2017-12-04 16:26:34 +01:00
right = this.compileExpression(expression.right, this.currentType);
// simplify if left is free of side effects while tolerating two levels of nesting, e.g., i32.load(i32.load(i32.const))
// if (condition = this.module.cloneExpression(left, true, 2))
// return this.module.createIf(
// this.currentType.isLongInteger
// ? this.module.createBinary(BinaryOp.NeI64, condition, this.module.createI64(0, 0))
// : this.currentType == Type.f64
// ? this.module.createBinary(BinaryOp.NeF64, condition, this.module.createF64(0))
// : this.currentType == Type.f32
// ? this.module.createBinary(BinaryOp.NeF32, condition, this.module.createF32(0))
// : condition, // usual case: saves one EQZ when not using EQZ above
// right,
// left
// );
// otherwise use a temporary local for the intermediate value
2017-12-12 09:38:20 +01:00
tempLocal = this.currentFunction.getAndFreeTempLocal(this.currentType);
condition = this.module.createTeeLocal(tempLocal.index, left);
2017-12-04 16:26:34 +01:00
return this.module.createIf(
this.currentType.isLongInteger
? this.module.createBinary(BinaryOp.NeI64, condition, this.module.createI64(0, 0))
2017-12-04 16:26:34 +01:00
: this.currentType == Type.f64
? this.module.createBinary(BinaryOp.NeF64, condition, this.module.createF64(0))
2017-12-04 16:26:34 +01:00
: this.currentType == Type.f32
? this.module.createBinary(BinaryOp.NeF32, condition, this.module.createF32(0))
2017-12-04 16:26:34 +01:00
: this.module.createTeeLocal(tempLocal.index, left),
right,
this.module.createGetLocal(tempLocal.index, typeToNativeType(tempLocal.type))
);
case Token.BAR_BAR: // left || right
left = this.compileExpression(expression.left, contextualType, ConversionKind.NONE);
2017-12-04 16:26:34 +01:00
right = this.compileExpression(expression.right, this.currentType);
// simplify if left is free of side effects while tolerating two levels of nesting
// if (condition = this.module.cloneExpression(left, true, 2))
// return this.module.createIf(
// this.currentType.isLongInteger
// ? this.module.createBinary(BinaryOp.NeI64, condition, this.module.createI64(0, 0))
// : this.currentType == Type.f64
// ? this.module.createBinary(BinaryOp.NeF64, condition, this.module.createF64(0))
// : this.currentType == Type.f32
// ? this.module.createBinary(BinaryOp.NeF32, condition, this.module.createF32(0))
// : condition, // usual case: saves one EQZ when not using EQZ above
// left,
// right
// );
// otherwise use a temporary local for the intermediate value
2017-12-12 09:38:20 +01:00
tempLocal = this.currentFunction.getAndFreeTempLocal(this.currentType);
condition = this.module.createTeeLocal(tempLocal.index, left);
2017-12-04 16:26:34 +01:00
return this.module.createIf(
this.currentType.isLongInteger
? this.module.createBinary(BinaryOp.NeI64, condition, this.module.createI64(0, 0))
2017-12-04 16:26:34 +01:00
: this.currentType == Type.f64
? this.module.createBinary(BinaryOp.NeF64, condition, this.module.createF64(0))
2017-12-04 16:26:34 +01:00
: this.currentType == Type.f32
? this.module.createBinary(BinaryOp.NeF32, condition, this.module.createF32(0))
2017-12-04 16:26:34 +01:00
: this.module.createTeeLocal(tempLocal.index, left),
this.module.createGetLocal(tempLocal.index, typeToNativeType(tempLocal.type)),
right
);
default:
throw new Error("not implemented");
}
2017-10-07 14:29:43 +02:00
if (compound) {
right = this.module.createBinary(op, left, right);
return this.compileAssignmentWithValue(expression.left, right, contextualType != Type.void);
}
return this.module.createBinary(op, left, right);
}
2017-11-26 04:03:28 +01:00
compileAssignment(expression: Expression, valueExpression: Expression, contextualType: Type): ExpressionRef {
this.currentType = this.determineExpressionType(expression, contextualType);
2017-12-03 01:18:35 +01:00
return this.compileAssignmentWithValue(expression, this.compileExpression(valueExpression, this.currentType, ConversionKind.IMPLICIT), contextualType != Type.void);
}
2017-11-26 04:03:28 +01:00
compileAssignmentWithValue(expression: Expression, valueWithCorrectType: ExpressionRef, tee: bool = false): ExpressionRef {
const element: Element | null = this.program.resolveElement(expression, this.currentFunction);
if (!element)
return this.module.createUnreachable();
if (element.kind == ElementKind.LOCAL) {
if (tee) {
this.currentType = (<Local>element).type;
return this.module.createTeeLocal((<Local>element).index, valueWithCorrectType);
2017-10-07 14:29:43 +02:00
}
this.currentType = Type.void;
return this.module.createSetLocal((<Local>element).index, valueWithCorrectType);
}
if (element.kind == ElementKind.GLOBAL) {
2017-12-16 02:27:39 +01:00
if (!this.compileGlobal(<Global>element))
return this.module.createUnreachable();
this.currentType = <Type>(<Global>element).type;
if (!(<Global>element).isMutable) {
this.error(DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, expression.range, element.internalName);
2017-12-16 02:27:39 +01:00
return this.module.createUnreachable();
}
if (tee) {
2017-11-26 04:03:28 +01:00
const globalNativeType: NativeType = typeToNativeType(<Type>(<Global>element).type);
return this.module.createBlock(null, [ // teeGlobal
this.module.createSetGlobal((<Global>element).internalName, valueWithCorrectType),
2017-11-26 04:03:28 +01:00
this.module.createGetGlobal((<Global>element).internalName, globalNativeType)
], globalNativeType);
2017-10-07 14:29:43 +02:00
}
this.currentType = Type.void;
return this.module.createSetGlobal((<Global>element).internalName, valueWithCorrectType);
2017-10-07 14:29:43 +02:00
}
// TODO: fields, setters
2017-09-28 13:08:25 +02:00
throw new Error("not implemented");
}
2017-11-26 04:03:28 +01:00
compileCallExpression(expression: CallExpression, contextualType: Type): ExpressionRef {
2017-11-17 14:33:51 +01:00
const element: Element | null = this.program.resolveElement(expression.expression, this.currentFunction); // reports
if (!element)
2017-10-07 14:29:43 +02:00
return this.module.createUnreachable();
2017-12-05 01:45:15 +01:00
2017-11-20 23:39:50 +01:00
if (element.kind == ElementKind.FUNCTION_PROTOTYPE) {
2017-12-01 02:08:03 +01:00
const functionPrototype: FunctionPrototype = <FunctionPrototype>element;
let functionInstance: Function | null = null;
2017-12-05 13:35:14 +01:00
if (functionPrototype.isBuiltIn) {
2017-12-04 02:00:48 +01:00
const k: i32 = expression.typeArguments.length;
const resolvedTypeArguments: Type[] = new Array(k);
2017-12-01 02:08:03 +01:00
sb.length = 0;
2017-12-04 02:00:48 +01:00
for (let i: i32 = 0; i < k; ++i) {
let resolvedType: Type | null = this.program.resolveType(expression.typeArguments[i], this.currentFunction.contextualTypeArguments, true); // reports
if (!resolvedType)
2017-12-01 02:08:03 +01:00
return this.module.createUnreachable();
2017-12-04 02:00:48 +01:00
resolvedTypeArguments[i] = resolvedType;
sb.push(resolvedType.toString());
2017-12-01 02:08:03 +01:00
}
2017-12-04 02:00:48 +01:00
2017-12-01 02:08:03 +01:00
functionInstance = <Function | null>functionPrototype.instances.get(sb.join(","));
if (!functionInstance) {
2017-12-05 01:45:15 +01:00
this.currentType = contextualType;
2017-12-12 01:35:48 +01:00
let expr: ExpressionRef = compileBuiltinCall(this, functionPrototype, resolvedTypeArguments, expression.arguments, expression);
2017-12-05 01:45:15 +01:00
if (!expr) {
this.error(DiagnosticCode.Operation_not_supported, expression.range);
return this.module.createUnreachable();
2017-12-04 02:00:48 +01:00
}
2017-12-05 01:45:15 +01:00
return expr;
}
2017-12-01 02:08:03 +01:00
} else {
// TODO: infer type arguments from parameter types if omitted
2017-12-01 02:08:03 +01:00
functionInstance = (<FunctionPrototype>element).resolveInclTypeArguments(expression.typeArguments, this.currentFunction.contextualTypeArguments, expression); // reports
}
2017-11-20 23:39:50 +01:00
if (!functionInstance)
return this.module.createUnreachable();
return this.compileCall(functionInstance, expression.arguments, expression);
}
this.error(DiagnosticCode.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures, expression.range, element.internalName);
return this.module.createUnreachable();
}
2017-11-26 04:03:28 +01:00
/** Compiles a call to a function. If an instance method, `this` is the first element in `argumentExpressions`. */
compileCall(functionInstance: Function, argumentExpressions: Expression[], reportNode: Node): ExpressionRef {
const previousType: Type = this.currentType;
2017-11-26 04:03:28 +01:00
// validate and compile arguments
2017-11-20 23:39:50 +01:00
const parameters: Parameter[] = functionInstance.parameters;
2017-11-26 04:03:28 +01:00
const parameterCount: i32 = parameters.length;
const argumentCount: i32 = argumentExpressions.length;
if (argumentExpressions.length > parameterCount) { // too many arguments
this.error(DiagnosticCode.Expected_0_arguments_but_got_1, reportNode.range,
(functionInstance.isInstance ? parameterCount - 1 : parameterCount).toString(10),
(functionInstance.isInstance ? argumentCount - 1 : argumentCount).toString(10)
);
2017-11-17 14:33:51 +01:00
return this.module.createUnreachable();
}
2017-11-26 04:03:28 +01:00
const operands: ExpressionRef[] = new Array(parameterCount);
for (let i: i32 = 0; i < parameterCount; ++i) {
2017-11-20 23:39:50 +01:00
if (argumentExpressions.length > i) {
operands[i] = this.compileExpression(argumentExpressions[i], parameters[i].type);
2017-11-20 23:39:50 +01:00
} else {
const initializer: Expression | null = parameters[i].initializer;
2017-11-26 04:03:28 +01:00
if (initializer) { // omitted, uses initializer
// FIXME: here, the initializer is compiled in the caller's scope.
2017-12-01 02:08:03 +01:00
// a solution could be to use a stub for each possible overload, calling the
2017-11-26 04:03:28 +01:00
// full function with optional arguments being part of the stub's body.
2017-11-20 23:39:50 +01:00
operands[i] = this.compileExpression(initializer, parameters[i].type);
2017-11-26 04:03:28 +01:00
} else { // too few arguments
this.error(DiagnosticCode.Expected_at_least_0_arguments_but_got_1, reportNode.range,
(functionInstance.isInstance ? i : i + 1).toString(10),
(functionInstance.isInstance ? argumentCount - 1 : argumentCount).toString(10)
);
2017-11-20 23:39:50 +01:00
return this.module.createUnreachable();
}
}
}
2017-11-26 04:03:28 +01:00
this.currentType = functionInstance.returnType;
2017-12-01 02:08:03 +01:00
if (!functionInstance.isCompiled)
this.compileFunction(functionInstance);
// imported function
2017-12-13 04:46:05 +01:00
if (functionInstance.isDeclared)
2017-12-01 02:08:03 +01:00
return this.module.createCallImport(functionInstance.internalName, operands, typeToNativeType(functionInstance.returnType));
// internal function
2017-11-26 04:03:28 +01:00
return this.module.createCall(functionInstance.internalName, operands, typeToNativeType(functionInstance.returnType));
2017-09-28 13:08:25 +02:00
}
2017-11-26 04:03:28 +01:00
compileElementAccessExpression(expression: ElementAccessExpression, contextualType: Type): ExpressionRef {
2017-11-20 23:39:50 +01:00
const element: Element | null = this.program.resolveElement(expression.expression, this.currentFunction); // reports
if (!element)
return this.module.createUnreachable();
2017-09-28 13:08:25 +02:00
throw new Error("not implemented");
}
2017-11-26 04:03:28 +01:00
compileIdentifierExpression(expression: IdentifierExpression, contextualType: Type): ExpressionRef {
switch (expression.kind) {
case NodeKind.NULL:
if (this.options.target == Target.WASM64) {
if (!contextualType.classType) {
assert(contextualType.kind == TypeKind.USIZE);
this.currentType = Type.usize64;
}
return this.module.createI64(0, 0);
}
if (!contextualType.classType) {
assert(contextualType.kind == TypeKind.USIZE);
this.currentType = Type.usize32;
}
return this.module.createI32(0);
case NodeKind.TRUE:
this.currentType = Type.bool;
return this.module.createI32(1);
case NodeKind.FALSE:
this.currentType = Type.bool;
return this.module.createI32(0);
case NodeKind.THIS:
if (this.currentFunction.instanceMethodOf) {
this.currentType = this.currentFunction.instanceMethodOf.type;
return this.module.createGetLocal(0, this.options.target == Target.WASM64 ? NativeType.I64 : NativeType.I32);
}
this.error(DiagnosticCode._this_cannot_be_referenced_in_current_location, expression.range);
this.currentType = this.options.target == Target.WASM64 ? Type.u64 : Type.u32;
return this.module.createUnreachable();
2017-10-07 14:29:43 +02:00
}
const element: Element | null = this.program.resolveElement(expression, this.currentFunction); // reports
if (!element)
return this.module.createUnreachable();
// local
if (element.kind == ElementKind.LOCAL) {
this.currentType = (<Local>element).type;
return this.module.createGetLocal((<Local>element).index, typeToNativeType(this.currentType));
}
// global
if (element.kind == ElementKind.GLOBAL) {
2017-12-16 02:27:39 +01:00
if (element.isBuiltIn)
return compileBuiltinGetGlobal(this, <Global>element);
const global: Global = <Global>element;
if (!this.compileGlobal(global)) // reports
return this.module.createUnreachable();
this.currentType = <Type>global.type;
if (global.hasConstantValue) {
if (global.type == Type.f32)
return this.module.createF32((<Global>element).constantFloatValue);
else if (global.type == Type.f64)
return this.module.createF64((<Global>element).constantFloatValue);
else if ((<Type>global.type).isLongInteger)
return this.module.createI64((<I64>global.constantIntegerValue).lo, (<I64>global.constantIntegerValue).hi);
else if ((<Type>global.type).isAnyInteger)
return this.module.createI32((<I64>global.constantIntegerValue).lo);
else
throw new Error("unexpected global type");
} else
return this.module.createGetGlobal((<Global>element).internalName, typeToNativeType(this.currentType));
}
// field
// if (element.kind == ElementKind.FIELD)
// throw new Error("not implemented");
// getter
2017-11-17 14:33:51 +01:00
if (element.kind == ElementKind.FUNCTION_PROTOTYPE && (<FunctionPrototype>element).isGetter)
throw new Error("not implemented");
this.error(DiagnosticCode.Operation_not_supported, expression.range);
2017-10-07 14:29:43 +02:00
return this.module.createUnreachable();
2017-09-28 13:08:25 +02:00
}
2017-11-26 04:03:28 +01:00
compileLiteralExpression(expression: LiteralExpression, contextualType: Type): ExpressionRef {
2017-09-28 13:08:25 +02:00
switch (expression.literalKind) {
// case LiteralKind.ARRAY:
case LiteralKind.FLOAT: {
const floatValue: f64 = (<FloatLiteralExpression>expression).value;
2017-12-01 02:08:03 +01:00
if (contextualType == Type.f32)
return this.module.createF32(<f32>floatValue);
2017-09-28 13:08:25 +02:00
this.currentType = Type.f64;
return this.module.createF64(floatValue);
}
2017-09-28 13:08:25 +02:00
case LiteralKind.INTEGER: {
const intValue: I64 = (<IntegerLiteralExpression>expression).value;
if (contextualType == Type.bool && (intValue.isZero || intValue.isOne))
return this.module.createI32(intValue.isZero ? 0 : 1);
if (contextualType == Type.f64)
return this.module.createF64((<f64>intValue.lo) + (<f64>intValue.hi) * 0xffffffff);
if (contextualType == Type.f32)
return this.module.createF32((<f32>intValue.lo) + (<f32>intValue.hi) * 0xffffffff);
2017-09-28 13:08:25 +02:00
if (contextualType.isLongInteger)
return this.module.createI64(intValue.lo, intValue.hi);
if (contextualType.isSmallInteger)
return this.module.createI32(intValue.toI32());
2017-12-14 11:55:35 +01:00
this.currentType = contextualType.isSignedInteger ? Type.i32 : Type.u32;
return this.module.createI32(intValue.toI32());
}
2017-09-28 13:08:25 +02:00
// case LiteralKind.OBJECT:
// case LiteralKind.REGEXP:
// case LiteralKind.STRING:
}
throw new Error("not implemented");
}
2017-11-26 04:03:28 +01:00
compileNewExpression(expression: NewExpression, contextualType: Type): ExpressionRef {
2017-09-28 13:08:25 +02:00
throw new Error("not implemented");
}
2017-11-26 04:03:28 +01:00
compileParenthesizedExpression(expression: ParenthesizedExpression, contextualType: Type): ExpressionRef {
// does not change types, just order
return this.compileExpression(expression.expression, contextualType, ConversionKind.NONE);
2017-09-28 13:08:25 +02:00
}
2017-12-13 23:24:13 +01:00
compilePropertyAccessExpression(propertyAccess: PropertyAccessExpression, contextualType: Type): ExpressionRef {
const expression: Expression = propertyAccess.expression;
const propertyName: string = propertyAccess.property.name;
// the lhs expression is either 'this', 'super', an identifier or another property access
let target: Element | null;
switch (expression.kind) {
case NodeKind.THIS:
if (!this.currentFunction.instanceMethodOf) {
this.error(DiagnosticCode._this_cannot_be_referenced_in_current_location, expression.range);
return this.module.createUnreachable();
}
target = this.currentFunction.instanceMethodOf;
break;
case NodeKind.SUPER:
if (!(this.currentFunction.instanceMethodOf && this.currentFunction.instanceMethodOf.base)) {
this.error(DiagnosticCode._super_can_only_be_referenced_in_a_derived_class, expression.range);
return this.module.createUnreachable();
}
target = this.currentFunction.instanceMethodOf.base;
break;
case NodeKind.IDENTIFIER:
target = this.program.resolveIdentifier(<IdentifierExpression>expression, this.currentFunction); // reports
break;
case NodeKind.PROPERTYACCESS:
target = this.program.resolvePropertyAccess(<PropertyAccessExpression>expression, this.currentFunction); // reports
break;
default:
throw new Error("unexpected expression kind");
2017-12-13 23:24:13 +01:00
}
if (!target)
return this.module.createUnreachable();
// look up the property within the target to obtain the actual element
let element: Element | null;
let expr: ExpressionRef;
switch (target.kind) {
case ElementKind.LOCAL:
element = (<Local>target).type.classType;
if (!element) {
this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName, (<Local>target).type.toString());
return this.module.createUnreachable();
}
target = element;
break;
case ElementKind.GLOBAL:
if (!this.compileGlobal(<Global>target))
return this.module.createUnreachable();
element = (<Type>(<Global>target).type).classType;
if (!element) {
this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName, (<Local>target).type.toString());
return this.module.createUnreachable();
}
target = element;
break;
default:
2017-12-15 15:00:19 +01:00
if (target.members) {
element = target.members.get(propertyName);
if (!element) {
this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName);
return this.module.createUnreachable();
}
// handle enum values right away
if (element.kind == ElementKind.ENUMVALUE) {
this.currentType = Type.i32;
return (<EnumValue>element).hasConstantValue
? this.module.createI32((<EnumValue>element).constantValue)
: this.module.createGetGlobal((<EnumValue>element).internalName, NativeType.I32);
}
} else {
this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName);
return this.module.createUnreachable();
}
break;
2017-12-13 23:24:13 +01:00
}
// handle the element
switch (element.kind) {
case ElementKind.LOCAL:
return this.module.createGetLocal((<Local>element).index, typeToNativeType(this.currentType = (<Local>element).type));
case ElementKind.GLOBAL:
2017-12-15 15:00:19 +01:00
if (!this.compileGlobal(<Global>element))
return this.module.createUnreachable();
this.currentType = <Type>(<Global>element).type;
if ((<Global>element).hasConstantValue)
return this.currentType== Type.f32
? this.module.createF32((<Global>element).constantFloatValue)
: this.currentType == Type.f64
? this.module.createF64((<Global>element).constantFloatValue)
: this.currentType.isLongInteger
? this.module.createI64((<I64>(<Global>element).constantIntegerValue).lo, (<I64>(<Global>element).constantIntegerValue).hi)
: this.module.createI32((<I64>(<Global>element).constantIntegerValue).lo);
return this.module.createGetGlobal((<Global>element).internalName, typeToNativeType(this.currentType));
2017-12-13 23:24:13 +01:00
case ElementKind.FUNCTION: // getter
if (!(<Function>element).prototype.isGetter) {
this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName, element.internalName);
return this.module.createUnreachable();
}
return this.compileCall(<Function>element, [], propertyAccess);
}
this.error(DiagnosticCode.Operation_not_supported, propertyAccess.range);
2017-09-28 13:08:25 +02:00
throw new Error("not implemented");
}
compileTernaryExpression(expression: TernaryExpression, contextualType: Type): ExpressionRef {
2017-11-26 04:03:28 +01:00
const condition: ExpressionRef = this.compileExpression(expression.condition, Type.i32);
const ifThen: ExpressionRef = this.compileExpression(expression.ifThen, contextualType);
const ifElse: ExpressionRef = this.compileExpression(expression.ifElse, contextualType);
return this.module.createIf(condition, ifThen, ifElse);
2017-09-28 13:08:25 +02:00
}
2017-11-26 04:03:28 +01:00
compileUnaryPostfixExpression(expression: UnaryPostfixExpression, contextualType: Type): ExpressionRef {
2017-10-02 12:52:15 +02:00
const operator: Token = expression.operator;
2017-12-03 01:18:35 +01:00
// make a getter for the expression (also obtains the type)
2017-12-18 03:46:36 +01:00
const getValue: ExpressionRef = this.compileExpression(expression.operand, contextualType, contextualType == Type.void ? ConversionKind.NONE : ConversionKind.IMPLICIT);
2017-12-03 01:18:35 +01:00
// use a temp local for the intermediate value
2017-12-12 09:38:20 +01:00
const tempLocal: Local = this.currentFunction.getTempLocal(this.currentType);
2017-12-03 01:18:35 +01:00
2017-10-02 12:52:15 +02:00
let op: BinaryOp;
2017-11-26 04:03:28 +01:00
let nativeType: NativeType;
let nativeOne: ExpressionRef;
2017-10-02 12:52:15 +02:00
2017-12-03 01:18:35 +01:00
if (tempLocal.type == Type.f32) {
2017-10-02 12:52:15 +02:00
op = operator == Token.PLUS_PLUS ? BinaryOp.AddF32 : BinaryOp.SubF32;
2017-11-26 04:03:28 +01:00
nativeType = NativeType.F32;
nativeOne = this.module.createF32(1);
2017-12-03 01:18:35 +01:00
} else if (tempLocal.type == Type.f64) {
2017-10-02 12:52:15 +02:00
op = operator == Token.PLUS_PLUS ? BinaryOp.AddF64 : BinaryOp.SubF64;
2017-11-26 04:03:28 +01:00
nativeType = NativeType.F64;
nativeOne = this.module.createF64(1);
2017-12-03 01:18:35 +01:00
} else if (tempLocal.type.isLongInteger) {
2017-10-02 12:52:15 +02:00
op = operator == Token.PLUS_PLUS ? BinaryOp.AddI64 : BinaryOp.SubI64;
2017-11-26 04:03:28 +01:00
nativeType = NativeType.I64;
nativeOne = this.module.createI64(1, 0);
2017-12-03 01:18:35 +01:00
2017-10-02 12:52:15 +02:00
} else {
op = operator == Token.PLUS_PLUS ? BinaryOp.AddI32 : BinaryOp.SubI32;
2017-11-26 04:03:28 +01:00
nativeType = NativeType.I32;
nativeOne = this.module.createI32(1);
2017-10-02 12:52:15 +02:00
}
2017-12-03 01:18:35 +01:00
// make a setter that sets the new value (temp value +/- 1)
2017-12-18 03:46:36 +01:00
const setValue: ExpressionRef = this.compileAssignmentWithValue(expression.operand,
2017-12-03 01:18:35 +01:00
this.module.createBinary(op,
this.module.createGetLocal(tempLocal.index, nativeType),
nativeOne
), false
);
// NOTE: can't preemptively tee_local the return value on the stack because binaryen expects
// this to be well-formed. becomes a tee_local when optimizing, though.
this.currentType = tempLocal.type;
2017-12-12 09:38:20 +01:00
this.currentFunction.freeTempLocal(tempLocal);
2017-10-02 12:52:15 +02:00
return this.module.createBlock(null, [
2017-12-03 01:18:35 +01:00
this.module.createSetLocal(tempLocal.index, getValue), // +++ this.module.createTeeLocal(tempLocal.index, getValue),
setValue,
this.module.createGetLocal(tempLocal.index, nativeType) // ---
2017-11-26 04:03:28 +01:00
], nativeType);
2017-09-28 13:08:25 +02:00
}
2017-11-26 04:03:28 +01:00
compileUnaryPrefixExpression(expression: UnaryPrefixExpression, contextualType: Type): ExpressionRef {
2017-12-18 03:46:36 +01:00
const operandExpression: Expression = expression.operand;
2017-10-07 14:29:43 +02:00
2017-11-26 04:03:28 +01:00
let operand: ExpressionRef;
let op: UnaryOp;
switch (expression.operator) {
case Token.PLUS:
return this.compileExpression(operandExpression, contextualType, contextualType == Type.void ? ConversionKind.NONE : ConversionKind.IMPLICIT);
case Token.MINUS:
operand = this.compileExpression(operandExpression, contextualType, contextualType == Type.void ? ConversionKind.NONE : ConversionKind.IMPLICIT);
if (this.currentType == Type.f32)
op = UnaryOp.NegF32;
else if (this.currentType == Type.f64)
op = UnaryOp.NegF64;
else
return this.currentType.isLongInteger
2017-10-02 12:52:15 +02:00
? this.module.createBinary(BinaryOp.SubI64, this.module.createI64(0, 0), operand)
: this.module.createBinary(BinaryOp.SubI32, this.module.createI32(0), operand);
break;
2017-10-02 12:52:15 +02:00
case Token.PLUS_PLUS:
operand = this.compileExpression(operandExpression, contextualType, contextualType == Type.void ? ConversionKind.NONE : ConversionKind.IMPLICIT);
2017-10-02 12:52:15 +02:00
return this.currentType == Type.f32
2017-10-07 14:29:43 +02:00
? this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.AddF32, operand, this.module.createF32(1)), contextualType != Type.void)
2017-10-02 12:52:15 +02:00
: this.currentType == Type.f64
2017-10-07 14:29:43 +02:00
? this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.AddF64, operand, this.module.createF64(1)), contextualType != Type.void)
2017-10-02 12:52:15 +02:00
: this.currentType.isLongInteger
2017-10-07 14:29:43 +02:00
? this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.AddI64, operand, this.module.createI64(1, 0)), contextualType != Type.void)
: this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.AddI32, operand, this.module.createI32(1)), contextualType != Type.void);
2017-10-02 12:52:15 +02:00
case Token.MINUS_MINUS:
operand = this.compileExpression(operandExpression, contextualType, contextualType == Type.void ? ConversionKind.NONE : ConversionKind.IMPLICIT);
2017-10-02 12:52:15 +02:00
return this.currentType == Type.f32
2017-10-07 14:29:43 +02:00
? this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.SubF32, operand, this.module.createF32(1)), contextualType != Type.void)
2017-10-02 12:52:15 +02:00
: this.currentType == Type.f64
2017-10-07 14:29:43 +02:00
? this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.SubF64, operand, this.module.createF64(1)), contextualType != Type.void)
2017-10-02 12:52:15 +02:00
: this.currentType.isLongInteger
2017-10-07 14:29:43 +02:00
? this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.SubI64, operand, this.module.createI64(1, 0)), contextualType != Type.void)
: this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.SubI32, operand, this.module.createI32(1)), contextualType != Type.void);
case Token.EXCLAMATION:
operand = this.compileExpression(operandExpression, Type.bool, ConversionKind.NONE);
if (this.currentType == Type.f32) {
this.currentType = Type.bool;
return this.module.createBinary(BinaryOp.EqF32, operand, this.module.createF32(0));
}
if (this.currentType == Type.f64) {
this.currentType = Type.bool;
return this.module.createBinary(BinaryOp.EqF64, operand, this.module.createF64(0));
}
op = this.currentType.isLongInteger
? UnaryOp.EqzI64 // TODO: does this yield i64 0/1?
: UnaryOp.EqzI32;
this.currentType = Type.bool;
break;
case Token.TILDE:
operand = this.compileExpression(operandExpression, contextualType.isAnyFloat ? Type.i64 : contextualType, contextualType == Type.void ? ConversionKind.NONE : ConversionKind.IMPLICIT);
return this.currentType.isLongInteger
2017-10-02 12:52:15 +02:00
? this.module.createBinary(BinaryOp.XorI64, operand, this.module.createI64(-1, -1))
: this.module.createBinary(BinaryOp.XorI32, operand, this.module.createI32(-1));
default:
throw new Error("not implemented");
}
return this.module.createUnary(op, operand);
2017-09-28 13:08:25 +02:00
}
}
2017-10-02 12:52:15 +02:00
// helpers
2017-12-05 01:45:15 +01:00
export function typeToNativeType(type: Type): NativeType {
2017-10-02 12:52:15 +02:00
return type.kind == TypeKind.F32
2017-11-26 04:03:28 +01:00
? NativeType.F32
2017-10-02 12:52:15 +02:00
: type.kind == TypeKind.F64
2017-11-26 04:03:28 +01:00
? NativeType.F64
2017-10-02 12:52:15 +02:00
: type.isLongInteger
2017-11-26 04:03:28 +01:00
? NativeType.I64
2017-10-02 12:52:15 +02:00
: type.isAnyInteger || type.kind == TypeKind.BOOL
2017-11-26 04:03:28 +01:00
? NativeType.I32
: NativeType.None;
2017-10-02 12:52:15 +02:00
}
2017-12-05 01:45:15 +01:00
export function typesToNativeTypes(types: Type[]): NativeType[] {
2017-10-07 14:29:43 +02:00
const k: i32 = types.length;
2017-11-26 04:03:28 +01:00
const ret: NativeType[] = new Array(k);
2017-10-07 14:29:43 +02:00
for (let i: i32 = 0; i < k; ++i)
2017-11-26 04:03:28 +01:00
ret[i] = typeToNativeType(types[i]);
2017-10-07 14:29:43 +02:00
return ret;
}
2017-12-05 01:45:15 +01:00
export function typeToNativeZero(module: Module, type: Type): ExpressionRef {
2017-10-02 12:52:15 +02:00
return type.kind == TypeKind.F32
? module.createF32(0)
: type.kind == TypeKind.F64
? module.createF64(0)
: type.isLongInteger
? module.createI64(0, 0)
: module.createI32(0);
}
2017-12-05 01:45:15 +01:00
export function typeToNativeOne(module: Module, type: Type): ExpressionRef {
2017-10-02 12:52:15 +02:00
return type.kind == TypeKind.F32
? module.createF32(1)
: type.kind == TypeKind.F64
? module.createF64(1)
: type.isLongInteger
? module.createI64(1, 0)
: module.createI32(1);
}
function typeToSignatureNamePart(type: Type): string {
return type.kind == TypeKind.VOID
? "v"
: type.kind == TypeKind.F32
? "f"
: type.kind == TypeKind.F64
? "F"
: type.isLongInteger
? "I"
: "i";
}
function typesToSignatureName(paramTypes: Type[], returnType: Type): string {
sb.length = 0;
2017-10-07 14:29:43 +02:00
for (let i: i32 = 0, k: i32 = paramTypes.length; i < k; ++i)
2017-10-02 12:52:15 +02:00
sb.push(typeToSignatureNamePart(paramTypes[i]));
sb.push(typeToSignatureNamePart(returnType));
return sb.join("");
}
2017-12-13 23:24:13 +01:00
function isModuleExport(element: Element, declaration: DeclarationStatement): bool {
if (!element.isExported)
return false;
if (declaration.range.source.isEntry)
return true;
let parentNode: Node | null = declaration.parent;
if (!parentNode)
return false;
if (parentNode.kind == NodeKind.VARIABLE)
if (!(parentNode = parentNode.parent))
return false;
if (parentNode.kind != NodeKind.NAMESPACE && parentNode.kind != NodeKind.CLASS)
return false;
let parent: Element | null = element.program.elements.get((<DeclarationStatement>parentNode).internalName);
if (!parent)
return false;
return isModuleExport(parent, <DeclarationStatement>parentNode);
}