assemblyscript/src/compiler.ts

2042 lines
84 KiB
TypeScript

import { PATH_DELIMITER } from "./constants";
import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics";
import { Module, MemorySegment, ExpressionRef, UnaryOp, BinaryOp, HostOp, NativeType, FunctionTypeRef, getExpressionId, ExpressionId } from "./module";
import { Program, ClassPrototype, Class, Element, ElementKind, Enum, FunctionPrototype, Function, Global, Local, Namespace, Parameter } from "./program";
import { CharCode, I64, U64, normalizePath, sb } from "./util";
import { Token, Range } from "./tokenizer";
import {
Node,
NodeKind,
TypeNode,
TypeParameter,
Source,
// statements
BlockStatement,
BreakStatement,
ClassDeclaration,
ContinueStatement,
DeclarationStatement,
DoStatement,
EmptyStatement,
EnumDeclaration,
EnumValueDeclaration,
ExportMember,
ExportStatement,
ExpressionStatement,
FieldDeclaration,
FunctionDeclaration,
ForStatement,
IfStatement,
ImportStatement,
MethodDeclaration,
ModifierKind,
NamespaceDeclaration,
ReturnStatement,
Statement,
SwitchCase,
SwitchStatement,
ThrowStatement,
TryStatement,
VariableLikeDeclarationStatement,
VariableDeclaration,
VariableStatement,
WhileStatement,
// expressions
ArrayLiteralExpression,
AssertionExpression,
BinaryExpression,
CallExpression,
ElementAccessExpression,
Expression,
FloatLiteralExpression,
IdentifierExpression,
IntegerLiteralExpression,
LiteralExpression,
LiteralKind,
NewExpression,
ParenthesizedExpression,
PropertyAccessExpression,
TernaryExpression,
StringLiteralExpression,
UnaryPostfixExpression,
UnaryPrefixExpression,
hasModifier
} from "./ast";
import {
Type,
TypeKind,
} from "./types";
export enum Target {
/** WebAssembly with 32-bit pointers. */
WASM32,
/** WebAssembly with 64-bit pointers. Experimental and not supported by any runtime yet. */
WASM64
}
export class Options {
/** WebAssembly target. Defaults to {@link Target.WASM32}. */
target: Target = Target.WASM32;
/** If true, performs compilation as usual but doesn't produce any output (all calls to module become nops). */
noEmit: bool = false;
/** If true, compiles everything instead of just reachable code. */
noTreeShaking: bool = false;
/** If true, replaces assertions with nops. */
noDebug: bool = false;
}
const enum ConversionKind {
/** No conversion. */
NONE,
/** Implicit conversion. */
IMPLICIT,
/** Explicit conversion. */
EXPLICIT
}
export class Compiler extends DiagnosticEmitter {
/** Program reference. */
program: Program;
/** Provided options. */
options: Options;
/** Module instance being compiled. */
module: Module;
/** Start function being compiled. */
startFunction: Function;
/** Start function expressions. */
startFunctionBody: ExpressionRef[] = new Array();
/** Current type in compilation. */
currentType: Type = Type.void;
/** Current function in compilation. */
currentFunction: Function;
/** Marker indicating whether continue statements are allowed in the current break context. */
disallowContinue: bool = true;
/** Counting memory offset. */
memoryOffset: U64 = new U64(8, 0); // leave space for (any size of) NULL
/** Memory segments being compiled. */
memorySegments: MemorySegment[] = new Array();
/** Already processed file names. */
files: Set<string> = new Set();
/** Compiles a {@link Program} to a {@link Module} using the specified options. */
static compile(program: Program, options: Options | null = null): Module {
const compiler: Compiler = new Compiler(program, options);
return compiler.compile();
}
/** Constructs a new compiler for a {@link Program} using the specified options. */
constructor(program: Program, options: Options | null = null) {
super(program.diagnostics);
this.program = program;
this.options = options ? options : new Options();
this.module = this.options.noEmit ? Module.createStub() : Module.create();
const startFunctionTemplate: FunctionPrototype = new FunctionPrototype(program, "start", null);
const startFunctionInstance: Function = new Function(startFunctionTemplate, startFunctionTemplate.internalName, [], [], Type.void, null);
this.currentFunction = this.startFunction = startFunctionInstance;
this.memoryOffset = new U64(2 * (this.options.target == Target.WASM64 ? 8 : 4), 0); // leave space for `null` and heapStart (both of usize type)
}
/** Performs compilation of the underlying {@link Program} to a {@link Module}. */
compile(): Module {
const program: Program = this.program;
// initialize lookup maps, builtins, imports, exports, etc.
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) {
let typeRef: FunctionTypeRef = this.module.getFunctionTypeBySignature(NativeType.None, []);
if (!typeRef)
typeRef = this.module.addFunctionType("v", NativeType.None, []);
this.module.setStart(
this.module.addFunction(this.startFunction.template.internalName, typeRef, typesToNativeTypes(this.startFunction.additionalLocals), this.module.createBlock(null, this.startFunctionBody))
);
}
// set up memory
// store heapStart at `sizeof<usize>()` (that is right after `null`) as an usize
const initial: U64 = this.memoryOffset.clone();
let heapStartBuffer: Uint8Array;
let heapStartOffset: i32;
if (this.options.target == Target.WASM64) {
heapStartBuffer = new Uint8Array(8);
heapStartOffset = 8;
heapStartBuffer[0] = (initial.lo ) as u8;
heapStartBuffer[1] = (initial.lo >>> 8) as u8;
heapStartBuffer[2] = (initial.lo >>> 16) as u8;
heapStartBuffer[3] = (initial.lo >>> 24) as u8;
heapStartBuffer[4] = (initial.hi ) as u8;
heapStartBuffer[5] = (initial.hi >>> 8) as u8;
heapStartBuffer[6] = (initial.hi >>> 16) as u8;
heapStartBuffer[7] = (initial.hi >>> 24) as u8;
} else {
if (!initial.fitsInU32)
throw new Error("static memory size overflows 32 bits");
heapStartBuffer = new Uint8Array(4);
heapStartOffset = 4;
heapStartBuffer[0] = (initial.lo ) as u8;
heapStartBuffer[1] = (initial.lo >>> 8) as u8;
heapStartBuffer[2] = (initial.lo >>> 16) as u8;
heapStartBuffer[3] = (initial.lo >>> 24) as u8;
}
this.memorySegments.push(MemorySegment.create(heapStartBuffer, new U64(heapStartOffset, 0)));
// 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];
switch (statement.kind) {
case NodeKind.CLASS:
if ((noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (<ClassDeclaration>statement).modifiers)) && !(<ClassDeclaration>statement).typeParameters.length)
this.compileClassDeclaration(<ClassDeclaration>statement, []);
break;
case NodeKind.ENUM:
if (noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (<EnumDeclaration>statement).modifiers))
this.compileEnumDeclaration(<EnumDeclaration>statement);
break;
case NodeKind.FUNCTION:
if ((noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (<FunctionDeclaration>statement).modifiers)) && !(<FunctionDeclaration>statement).typeParameters.length)
this.compileFunctionDeclaration(<FunctionDeclaration>statement, []);
break;
case NodeKind.IMPORT:
this.compileSourceByPath((<ImportStatement>statement).normalizedPath, (<ImportStatement>statement).path);
break;
case NodeKind.NAMESPACE:
if (noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (<NamespaceDeclaration>statement).modifiers))
this.compileNamespaceDeclaration(<NamespaceDeclaration>statement);
break;
case NodeKind.VARIABLE:
if (noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (<VariableStatement>statement).modifiers))
this.compileVariableStatement(<VariableStatement>statement);
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
default: {
const previousFunction: Function = this.currentFunction;
this.currentFunction = this.startFunction;
this.startFunctionBody.push(this.compileStatement(statement));
this.currentFunction = previousFunction;
break;
}
}
}
}
// globals
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");
return this.compileGlobal(<Global>element)
? <Global>element
: null;
}
compileGlobal(element: Global): bool {
if (element.isCompiled)
return true;
const declaration: VariableLikeDeclarationStatement | null = element.declaration;
let type: Type | null = element.type;
if (!type) {
if (!declaration)
throw new Error("unexpected missing declaration");
if (!declaration.type)
return false; // TODO: infer type? currently reported by parser
type = this.program.resolveType(declaration.type); // reports
if (!type)
return false;
element.type = type;
}
if (this.module.noEmit)
return true;
const nativeType: NativeType = typeToNativeType(<Type>type);
let initializer: ExpressionRef;
let initializeInStart: bool;
if (element.hasConstantValue) {
if (type.isLongInteger)
initializer = element.constantIntegerValue ? this.module.createI64(element.constantIntegerValue.lo, element.constantIntegerValue.hi) : this.module.createI64(0, 0);
else if (type.kind == TypeKind.F32)
initializer = this.module.createF32(element.constantFloatValue);
else if (type.kind == TypeKind.F64)
initializer = this.module.createF64(element.constantFloatValue);
else if (type.isSmallInteger) {
if (type.isSignedInteger) {
const shift: i32 = type.smallIntegerShift;
initializer = this.module.createI32(element.constantIntegerValue ? element.constantIntegerValue.toI32() << shift >> shift : 0);
} else
initializer = this.module.createI32(element.constantIntegerValue ? element.constantIntegerValue.toI32() & type.smallIntegerMask: 0);
} else
initializer = this.module.createI32(element.constantIntegerValue ? element.constantIntegerValue.toI32() : 0);
initializeInStart = false;
this.module.addGlobal(element.internalName, nativeType, element.isMutable, initializer);
} else if (declaration) {
if (declaration.initializer) {
initializer = this.compileExpression(declaration.initializer, type);
initializeInStart = getExpressionId(initializer) != ExpressionId.Const; // MVP doesn't support complex initializers
} else {
initializer = typeToNativeZero(this.module, type);
initializeInStart = false;
}
} else
throw new Error("unexpected missing declaration or constant value");
const internalName: string = element.internalName;
if (initializeInStart) {
this.module.addGlobal(internalName, nativeType, true, typeToNativeZero(this.module, type));
this.startFunctionBody.push(this.module.createSetGlobal(internalName, initializer));
} else {
this.module.addGlobal(internalName, nativeType, element.isMutable, initializer);
if (!element.isMutable) {
// TODO: check export, requires updated binaryen.js with Module#addGlobalExport
}
}
return element.isCompiled = true;
}
// 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);
}
compileEnum(element: Enum): void {
if (element.isCompiled)
return;
let previousInternalName: string | null = null;
for (let [key, val] of element.members) {
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);
initializeInStart = getExpressionId(initializer) != ExpressionId.Const; // MVP doesn't support complex initializers
} else if (previousInternalName == null) {
initializer = this.module.createI32(0);
initializeInStart = false;
} else {
initializer = this.module.createBinary(BinaryOp.AddI32,
this.module.createGetGlobal(previousInternalName, NativeType.I32),
this.module.createI32(1)
);
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);
// TODO: check export, requires updated binaryen.js with Module#addGlobalExport
}
} else
throw new Error("unexpected missing declaration or constant value");
previousInternalName = val.internalName;
}
element.isCompiled = true;
}
// functions
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);
if (!element || element.kind != ElementKind.FUNCTION_PROTOTYPE)
throw new Error("unexpected missing function");
const instance: Function | null = this.compileFunctionUsingTypeArguments(<FunctionPrototype>element, typeArguments, contextualTypeArguments, alternativeReportNode);
if (instance && declaration.range.source.isEntry && declaration.parent == declaration.range.source && hasModifier(ModifierKind.EXPORT, declaration.modifiers))
this.module.addExport(instance.internalName, declaration.identifier.name);
}
compileFunctionUsingTypeArguments(prototype: FunctionPrototype, typeArguments: TypeNode[], contextualTypeArguments: Map<string,Type> | null = null, alternativeReportNode: Node | null = null): Function | null {
const instance: Function | null = prototype.resolveInclTypeArguments(typeArguments, contextualTypeArguments, alternativeReportNode); // reports
if (!instance)
return null;
return this.compileFunction(instance) ? instance : null;
}
compileFunction(instance: Function): bool {
if (instance.isCompiled)
return true;
const declaration: FunctionDeclaration | null = instance.template.declaration;
if (!declaration)
throw new Error("unexpected missing declaration");
if (!declaration.statements) {
this.error(DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration, declaration.identifier.range);
return false;
}
instance.isCompiled = true;
// compile statements
const previousFunction: Function = this.currentFunction;
this.currentFunction = instance;
const stmts: ExpressionRef[] = this.compileStatements(<Statement[]>declaration.statements);
this.currentFunction = previousFunction;
// create the function
let k: i32 = instance.parameters.length;
const binaryenResultType: NativeType = typeToNativeType(instance.returnType);
const binaryenParamTypes: NativeType[] = new Array(k);
const signatureNameParts: string[] = new Array(k + 1);
for (let i: i32 = 0; i < k; ++i) {
binaryenParamTypes[i] = typeToNativeType(instance.parameters[i].type);
signatureNameParts[i] = typeToSignatureNamePart(instance.parameters[i].type);
}
signatureNameParts[k] = typeToSignatureNamePart(instance.returnType);
let typeRef: FunctionTypeRef = this.module.getFunctionTypeBySignature(binaryenResultType, binaryenParamTypes);
if (!typeRef)
typeRef = this.module.addFunctionType(signatureNameParts.join(""), binaryenResultType, binaryenParamTypes);
const internalName: string = instance.internalName;
this.module.addFunction(internalName, typeRef, typesToNativeTypes(instance.additionalLocals), this.module.createBlock(null, stmts, NativeType.None));
return true;
}
// namespaces
compileNamespaceDeclaration(declaration: NamespaceDeclaration): void {
const members: Statement[] = declaration.members;
const noTreeShaking: bool = this.options.noTreeShaking;
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, []);
break;
case NodeKind.ENUM:
if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (<EnumDeclaration>member).modifiers))
this.compileEnumDeclaration(<EnumDeclaration>member);
break;
case NodeKind.FUNCTION:
if ((noTreeShaking || hasModifier(ModifierKind.EXPORT, (<FunctionDeclaration>member).modifiers)) && !(<FunctionDeclaration>member).typeParameters.length)
this.compileFunctionDeclaration(<FunctionDeclaration>member, []);
break;
case NodeKind.NAMESPACE:
if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (<NamespaceDeclaration>member).modifiers))
this.compileNamespaceDeclaration(<NamespaceDeclaration>member);
break;
case NodeKind.VARIABLE:
if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (<VariableStatement>member).modifiers))
this.compileVariableStatement(<VariableStatement>member);
break;
default:
throw new Error("unexpected namespace member");
}
}
throw new Error("not implemented");
}
compileNamespace(ns: Namespace): void {
const noTreeShaking: bool = this.options.noTreeShaking;
for (let [name, element] of ns.members) {
switch (element.kind) {
case ElementKind.CLASS_PROTOTYPE:
if ((noTreeShaking || (<ClassPrototype>element).isExport) && !(<ClassPrototype>element).isGeneric)
this.compileClassUsingTypeArguments(<ClassPrototype>element, []);
break;
case ElementKind.ENUM:
this.compileEnum(<Enum>element);
break;
case ElementKind.FUNCTION_PROTOTYPE:
if ((noTreeShaking || (<FunctionPrototype>element).isExport) && !(<FunctionPrototype>element).isGeneric)
this.compileFunctionUsingTypeArguments(<FunctionPrototype>element, []);
break;
case ElementKind.GLOBAL:
this.compileGlobal(<Global>element);
break;
case ElementKind.NAMESPACE:
this.compileNamespace(<Namespace>element);
break;
}
}
}
// exports
compileExportStatement(statement: ExportStatement): void {
const members: ExportMember[] = statement.members;
const internalPath: string | null = statement.path ? statement.internalPath : statement.range.source.internalPath;
for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) {
const member: ExportMember = members[i];
const internalExportName: string = 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) {
case ElementKind.CLASS_PROTOTYPE:
if (!(<ClassPrototype>element).isGeneric)
this.compileClassUsingTypeArguments(<ClassPrototype>element, []);
break;
case ElementKind.ENUM:
this.compileEnum(<Enum>element);
break;
case ElementKind.FUNCTION_PROTOTYPE:
if (!(<FunctionPrototype>element).isGeneric) {
const functionInstance: Function | null = this.compileFunctionUsingTypeArguments(<FunctionPrototype>element, []);
if (functionInstance && statement.range.source.isEntry)
this.module.addExport(functionInstance.internalName, member.externalIdentifier.name);
}
break;
case ElementKind.GLOBAL:
this.compileGlobal(<Global>element);
break;
case ElementKind.NAMESPACE:
this.compileNamespace(<Namespace>element);
break;
}
}
}
// 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);
if (!element || element.kind != ElementKind.CLASS_PROTOTYPE)
throw new Error("unexpected missing class");
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;
this.compileClass(instance);
}
compileClass(cls: Class) {
throw new Error("not implemented");
}
// memory
addMemorySegment(buffer: Uint8Array): MemorySegment {
if (this.memoryOffset.lo & 7) { // align to 8 bytes so any possible data type is aligned here
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
determineExpressionType(expression: Expression, contextualType: Type): Type {
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
const type: Type = this.currentType;
this.currentType = previousType;
this.module.noEmit = previousNoEmit;
return type;
}
// statements
compileStatement(statement: Statement): ExpressionRef {
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);
case NodeKind.VARIABLE:
return this.compileVariableStatement(<VariableStatement>statement);
case NodeKind.WHILE:
return this.compileWhileStatement(<WhileStatement>statement);
}
throw new Error("unexpected statement kind");
}
compileStatements(statements: Statement[]): ExpressionRef[] {
const k: i32 = statements.length;
const stmts: ExpressionRef[] = new Array(k);
for (let i: i32 = 0; i < k; ++i)
stmts[i] = this.compileStatement(statements[i]);
return stmts;
}
compileBlockStatement(statement: BlockStatement): ExpressionRef {
return this.module.createBlock(null, this.compileStatements(statement.statements), NativeType.None);
}
compileBreakStatement(statement: BreakStatement): ExpressionRef {
if (statement.label)
throw new Error("not implemented");
const context: string | null = this.currentFunction.breakContext;
if (context != null)
return this.module.createBreak("break$" + (<string>context));
this.error(DiagnosticCode.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement, statement.range);
return this.module.createUnreachable();
}
compileContinueStatement(statement: ContinueStatement): ExpressionRef {
if (statement.label)
throw new Error("not implemented");
const context: string | null = this.currentFunction.breakContext;
if (context != null && !this.disallowContinue)
return this.module.createBreak("continue$" + (<string>context));
this.error(DiagnosticCode.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement, statement.range);
return this.module.createUnreachable();
}
compileDoStatement(statement: DoStatement): ExpressionRef {
const label: string = this.currentFunction.enterBreakContext();
const condition: ExpressionRef = this.compileExpression(statement.condition, Type.i32);
const body: ExpressionRef = this.compileStatement(statement.statement);
this.currentFunction.leaveBreakContext();
const breakLabel: string = "break$" + label;
const continueLabel: string = "continue$" + label;
return this.module.createBlock(breakLabel, [
this.module.createLoop(continueLabel,
this.module.createBlock(null, [
body,
this.module.createBreak(continueLabel, condition)
], NativeType.None))
], NativeType.None);
}
compileEmptyStatement(statement: EmptyStatement): ExpressionRef {
return this.module.createNop();
}
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;
}
compileForStatement(statement: ForStatement): ExpressionRef {
const context: string = this.currentFunction.enterBreakContext();
const initializer: ExpressionRef = statement.initializer ? this.compileStatement(<Statement>statement.initializer) : this.module.createNop();
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);
this.currentFunction.leaveBreakContext();
const continueLabel: string = "continue$" + context;
const breakLabel: string = "break$" + context;
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)
], NativeType.None))
], NativeType.None))
], NativeType.None);
}
compileIfStatement(statement: IfStatement): ExpressionRef {
const condition: ExpressionRef = this.compileExpression(statement.condition, Type.i32);
const ifTrue: ExpressionRef = this.compileStatement(statement.statement);
const ifFalse: ExpressionRef = statement.elseStatement ? this.compileStatement(<Statement>statement.elseStatement) : 0;
return this.module.createIf(condition, ifTrue, ifFalse);
}
compileReturnStatement(statement: ReturnStatement): ExpressionRef {
if (this.currentFunction) {
const expression: ExpressionRef = statement.expression ? this.compileExpression(<Expression>statement.expression, this.currentFunction.returnType) : 0;
return this.module.createReturn(expression);
}
return this.module.createUnreachable();
}
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)
const local: Local = this.currentFunction.addLocal(Type.i32);
let i: i32, k: i32 = statement.cases.length;
// prepend initializer to inner block
const breaks: ExpressionRef[] = new Array(1 + k);
breaks[0] = this.module.createSetLocal(local.index, this.compileExpression(statement.expression, Type.i32)); // initializer
// 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,
this.module.createBinary(BinaryOp.EqI32,
this.module.createGetLocal(local.index, NativeType.I32),
this.compileExpression(case_.label, Type.i32)
)
);
} else
defaultIndex = i;
}
// 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;
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]);
currentBlock = this.module.createBlock(nextLabel, body, NativeType.None);
}
this.currentFunction.leaveBreakContext();
this.disallowContinue = previousDisallowContinue;
return currentBlock;
}
compileThrowStatement(statement: ThrowStatement): ExpressionRef {
return this.module.createUnreachable(); // TODO: waiting for exception-handling spec
}
compileTryStatement(statement: TryStatement): ExpressionRef {
throw new Error("not implemented");
// can't yet support something like: try { return ... } finally { ... }
// worthwhile to investigate lowering returns to block results (here)?
}
compileVariableStatement(statement: VariableStatement): ExpressionRef {
const declarations: VariableDeclaration[] = statement.declarations;
// top-level variables become globals
if (this.currentFunction == this.startFunction) {
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);
return this.module.createNop();
}
// other variables become locals
const initializers: ExpressionRef[] = new Array();
for (let i: i32 = 0, k = declarations.length; i < k; ++i) {
const declaration: VariableDeclaration = declarations[i];
if (declaration.type) {
const name: string = declaration.identifier.name;
const type: Type | null = this.program.resolveType(<TypeNode>declaration.type, this.currentFunction.contextualTypeArguments, true); // reports
if (type) {
if (this.currentFunction.locals.has(name))
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, name); // recoverable
else
this.currentFunction.addLocal(<Type>type, name);
if (declaration.initializer)
initializers.push(this.compileAssignment(declaration.identifier, <Expression>declaration.initializer, Type.void));
}
}
}
return initializers.length ? this.module.createBlock(null, initializers, NativeType.None) : this.module.createNop();
}
compileWhileStatement(statement: WhileStatement): ExpressionRef {
const label: string = this.currentFunction.enterBreakContext();
const condition: ExpressionRef = this.compileExpression(statement.condition, Type.i32);
const breakLabel: string = "break$" + label;
const continueLabel: string = "continue$" + label;
const body: ExpressionRef = this.compileStatement(statement.statement);
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)
], NativeType.None))
)
], NativeType.None);
}
// expressions
compileExpression(expression: Expression, contextualType: Type, conversionKind: ConversionKind = ConversionKind.IMPLICIT): ExpressionRef {
this.currentType = contextualType;
let expr: ExpressionRef;
switch (expression.kind) {
case NodeKind.ASSERTION:
expr = this.compileAssertionExpression(<AssertionExpression>expression, contextualType);
break;
case NodeKind.BINARY:
expr = this.compileBinaryExpression(<BinaryExpression>expression, contextualType);
break;
case NodeKind.CALL:
expr = this.compileCallExpression(<CallExpression>expression, contextualType);
break;
case NodeKind.ELEMENTACCESS:
expr = this.compileElementAccessExpression(<ElementAccessExpression>expression, contextualType);
break;
case NodeKind.IDENTIFIER:
case NodeKind.FALSE:
case NodeKind.NULL:
case NodeKind.THIS:
case NodeKind.TRUE:
expr = this.compileIdentifierExpression(<IdentifierExpression>expression, contextualType);
break;
case NodeKind.LITERAL:
expr = this.compileLiteralExpression(<LiteralExpression>expression, contextualType);
break;
case NodeKind.NEW:
expr = this.compileNewExpression(<NewExpression>expression, contextualType);
break;
case NodeKind.PARENTHESIZED:
expr = this.compileParenthesizedExpression(<ParenthesizedExpression>expression, contextualType);
break;
case NodeKind.PROPERTYACCESS:
expr = this.compilePropertyAccessExpression(<PropertyAccessExpression>expression, contextualType);
break;
case NodeKind.TERNARY:
expr = this.compileTernaryExpression(<TernaryExpression>expression, contextualType);
break;
case NodeKind.UNARYPOSTFIX:
expr = this.compileUnaryPostfixExpression(<UnaryPostfixExpression>expression, contextualType);
break;
case NodeKind.UNARYPREFIX:
expr = this.compileUnaryPrefixExpression(<UnaryPrefixExpression>expression, contextualType);
break;
default:
throw new Error("unexpected expression kind");
}
if (conversionKind != ConversionKind.NONE) {
expr = this.convertExpression(expr, this.currentType, contextualType, conversionKind, expression);
this.currentType = contextualType;
}
return expr;
}
convertExpression(expr: ExpressionRef, fromType: Type, toType: Type, conversionKind: ConversionKind, reportNode: Node): ExpressionRef {
if (conversionKind == ConversionKind.NONE)
return expr;
// void to any
if (fromType.kind == TypeKind.VOID) {
this.error(DiagnosticCode.Operation_not_supported, reportNode.range);
throw new Error("unexpected conversion from void");
}
// 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.TruncF32_I64, expr);
else {
expr = mod.createUnary(UnaryOp.TruncF32_I32, expr);
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.TruncF32_U64, expr);
else {
expr = mod.createUnary(UnaryOp.TruncF32_U32, expr);
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.TruncF64_I64, expr);
else {
expr = mod.createUnary(UnaryOp.TruncF64_I32, expr);
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.TruncF64_U64, expr);
else {
expr = mod.createUnary(UnaryOp.TruncF64_U32, expr);
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.ConvertI64_F32, expr);
else
expr = mod.createUnary(UnaryOp.ConvertU64_F32, expr);
} else
if (fromType.isSignedInteger)
expr = mod.createUnary(UnaryOp.ConvertI32_F32, expr);
else
expr = mod.createUnary(UnaryOp.ConvertU32_F32, expr);
// int to f64
} else {
if (fromType.isLongInteger) {
losesInformation = true;
if (fromType.isSignedInteger)
expr = mod.createUnary(UnaryOp.ConvertI64_F64, expr);
else
expr = mod.createUnary(UnaryOp.ConvertU64_F64, expr);
} else
if (fromType.isSignedInteger)
expr = mod.createUnary(UnaryOp.ConvertI32_F64, expr);
else
expr = mod.createUnary(UnaryOp.ConvertU32_F64, expr);
}
// int to int
} else {
if (fromType.isLongInteger) {
// i64 to i32
if (!toType.isLongInteger) {
losesInformation = true;
expr = mod.createUnary(UnaryOp.WrapI64, expr);
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);
// i32 to smaller/change of signage i32
} else if (toType.isSmallInteger && (fromType.size > toType.size || (fromType.size == toType.size && fromType.kind != toType.kind))) {
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());
return expr;
}
compileAssertionExpression(expression: AssertionExpression, contextualType: Type): ExpressionRef {
const toType: Type | null = this.program.resolveType(expression.toType, this.currentFunction.contextualTypeArguments); // reports
return toType && toType != contextualType
? this.compileExpression(expression.expression, <Type>toType, ConversionKind.EXPLICIT)
: this.compileExpression(expression.expression, contextualType);
}
compileBinaryExpression(expression: BinaryExpression, contextualType: Type): ExpressionRef {
let op: BinaryOp;
let left: ExpressionRef;
let right: ExpressionRef;
let compound: Token = 0;
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.isLongInteger
? BinaryOp.LtI64
: BinaryOp.LtI32;
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.isLongInteger
? BinaryOp.GtI64
: BinaryOp.GtI32;
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.isLongInteger
? BinaryOp.LeI64
: BinaryOp.LeI32;
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.isLongInteger
? BinaryOp.GeI64
: BinaryOp.GeI32;
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;
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.isLongInteger
? BinaryOp.DivI64
: BinaryOp.DivI32;
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)
throw new Error("not implemented"); // TODO: internal fmod, possibly simply imported from JS
op = this.currentType.isLongInteger
? BinaryOp.RemI64
: BinaryOp.RemI32;
break;
case Token.LESSTHAN_LESSTHAN_EQUALS:
compound = Token.EQUALS;
case Token.LESSTHAN_LESSTHAN:
left = this.compileExpression(expression.left, contextualType.isAnyFloat ? Type.i64 : contextualType, contextualType == Type.void ? ConversionKind.NONE : ConversionKind.IMPLICIT);
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, contextualType == Type.void ? ConversionKind.NONE : ConversionKind.IMPLICIT);
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, contextualType == Type.void ? ConversionKind.NONE : ConversionKind.IMPLICIT);
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, contextualType == Type.void ? ConversionKind.NONE : ConversionKind.IMPLICIT);
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, contextualType == Type.void ? ConversionKind.NONE : ConversionKind.IMPLICIT);
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, contextualType == Type.void ? ConversionKind.NONE : ConversionKind.IMPLICIT);
right = this.compileExpression(expression.right, this.currentType);
op = this.currentType.isLongInteger
? BinaryOp.XorI64
: BinaryOp.XorI32;
break;
default:
throw new Error("not implemented");
}
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);
}
compileAssignment(expression: Expression, valueExpression: Expression, contextualType: Type): ExpressionRef {
this.currentType = this.determineExpressionType(expression, contextualType);
return this.compileAssignmentWithValue(expression, this.compileExpression(valueExpression, this.currentType, ConversionKind.IMPLICIT), contextualType != Type.void);
}
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);
}
this.currentType = Type.void;
return this.module.createSetLocal((<Local>element).index, valueWithCorrectType);
}
if (element.kind == ElementKind.GLOBAL) {
this.compileGlobal(<Global>element);
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);
if (tee) {
if (!(<Global>element).type)
return this.module.createUnreachable();
const globalNativeType: NativeType = typeToNativeType(<Type>(<Global>element).type);
this.currentType = <Type>(<Global>element).type;
return this.module.createBlock(null, [ // teeGlobal
this.module.createSetGlobal((<Global>element).internalName, valueWithCorrectType),
this.module.createGetGlobal((<Global>element).internalName, globalNativeType)
], globalNativeType);
}
this.currentType = Type.void;
return this.module.createSetGlobal((<Global>element).internalName, valueWithCorrectType);
}
// TODO: fields, setters
throw new Error("not implemented");
}
compileCallExpression(expression: CallExpression, contextualType: Type): ExpressionRef {
const element: Element | null = this.program.resolveElement(expression.expression, this.currentFunction); // reports
if (!element)
return this.module.createUnreachable();
if (element.kind == ElementKind.FUNCTION_PROTOTYPE) {
const functionPrototype: FunctionPrototype = <FunctionPrototype>element;
let functionInstance: Function | null = null;
if (functionPrototype.isBuiltin) {
const k: i32 = expression.typeArguments.length;
const resolvedTypeArguments: Type[] = new Array(k);
sb.length = 0;
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)
return this.module.createUnreachable();
resolvedTypeArguments[i] = resolvedType;
sb.push(resolvedType.toString());
}
functionInstance = <Function | null>functionPrototype.instances.get(sb.join(","));
if (!functionInstance) {
let arg0: ExpressionRef, arg1: ExpressionRef;
if (functionPrototype.internalName == "sizeof") { // no parameters
this.currentType = this.options.target == Target.WASM64 ? Type.usize64 : Type.usize32;
if (k != 1) {
this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, expression.range, "1", k.toString());
return this.module.createUnreachable();
}
if (expression.arguments.length != 0) {
this.error(DiagnosticCode.Expected_0_arguments_but_got_1, expression.range, "0", expression.arguments.length.toString());
return this.module.createUnreachable();
}
return this.options.target == Target.WASM64
? this.module.createI64(resolvedTypeArguments[0].byteSize, 0)
: this.module.createI32(resolvedTypeArguments[0].byteSize);
} else if (functionPrototype.internalName == "load") {
this.currentType = resolvedTypeArguments[0];
if (k != 1) {
this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, expression.range, "1", k.toString());
return this.module.createUnreachable();
}
if (expression.arguments.length != 1) {
this.error(DiagnosticCode.Expected_0_arguments_but_got_1, expression.range, "1", expression.arguments.length.toString());
return this.module.createUnreachable();
}
arg0 = this.compileExpression(expression.arguments[0], Type.usize32, ConversionKind.IMPLICIT); // reports
this.currentType = resolvedTypeArguments[0];
if (!arg0)
return this.module.createUnreachable();
return this.module.createLoad(resolvedTypeArguments[0].byteSize, resolvedTypeArguments[0].isSignedInteger, arg0, typeToNativeType(resolvedTypeArguments[0]));
} else if (functionPrototype.internalName == "store") {
this.currentType = Type.void;
if (k != 1) {
this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, expression.range, "1", k.toString());
return this.module.createUnreachable();
}
if (expression.arguments.length != 2) {
this.error(DiagnosticCode.Expected_0_arguments_but_got_1, expression.range, "2", expression.arguments.length.toString());
return this.module.createUnreachable();
}
arg0 = this.compileExpression(expression.arguments[0], Type.usize32, ConversionKind.IMPLICIT); // reports
this.currentType = Type.void;
if (!arg0)
return this.module.createUnreachable();
arg1 = this.compileExpression(expression.arguments[1], resolvedTypeArguments[0], ConversionKind.IMPLICIT);
this.currentType = Type.void;
if (!arg1)
return this.module.createUnreachable();
return this.module.createStore(resolvedTypeArguments[0].byteSize, arg0, arg1, typeToNativeType(resolvedTypeArguments[0]));
}
this.error(DiagnosticCode.Operation_not_supported, expression.range);
return this.module.createUnreachable();
}
} else {
// TODO: infer type arguments from parameter types if omitted
functionInstance = (<FunctionPrototype>element).resolveInclTypeArguments(expression.typeArguments, this.currentFunction.contextualTypeArguments, expression); // reports
}
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();
}
/** 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;
// validate and compile arguments
const parameters: Parameter[] = functionInstance.parameters;
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)
);
return this.module.createUnreachable();
}
const operands: ExpressionRef[] = new Array(parameterCount);
for (let i: i32 = 0; i < parameterCount; ++i) {
if (argumentExpressions.length > i) {
operands[i] = this.compileExpression(argumentExpressions[i], parameters[i].type);
} else {
const initializer: Expression | null = parameters[i].initializer;
if (initializer) { // omitted, uses initializer
// FIXME: here, the initializer is compiled in the caller's scope.
// a solution could be to use a stub for each possible overload, calling the
// full function with optional arguments being part of the stub's body.
operands[i] = this.compileExpression(initializer, parameters[i].type);
} 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)
);
return this.module.createUnreachable();
}
}
}
this.currentType = functionInstance.returnType;
if (functionInstance.isBuiltin) {
let tempLocal: Local;
switch (functionInstance.template.internalName) {
case "clz": // i32/i64.clz
if (this.currentType.isLongInteger)
return this.module.createUnary(UnaryOp.ClzI64, operands[0]);
else if (this.currentType.isAnyInteger)
return this.module.createUnary(UnaryOp.ClzI32, operands[0]);
break;
case "ctz": // i32/i64.ctz
if (this.currentType.isLongInteger)
return this.module.createUnary(UnaryOp.CtzI64, operands[0]);
else if (this.currentType.isAnyInteger)
return this.module.createUnary(UnaryOp.CtzI32, operands[0]);
break;
case "popcnt": // i32/i64.popcnt
if (this.currentType.isLongInteger)
return this.module.createUnary(UnaryOp.PopcntI64, operands[0]);
else if (this.currentType.isAnyInteger)
return this.module.createUnary(UnaryOp.PopcntI32, operands[0]);
break;
case "rotl": // i32/i64.rotl
if (this.currentType.isLongInteger)
return this.module.createBinary(BinaryOp.RotlI64, operands[0], operands[1]);
else if (this.currentType.isAnyInteger)
return this.module.createBinary(BinaryOp.RotlI32, operands[0], operands[1]);
break;
case "rotr": // i32/i64.rotr
if (this.currentType.isLongInteger)
return this.module.createBinary(BinaryOp.RotrI64, operands[0], operands[1]);
else if (this.currentType.isAnyInteger)
return this.module.createBinary(BinaryOp.RotrI32, operands[0], operands[1]);
break;
case "abs": // f32/f64.abs
if (this.currentType == Type.f64)
return this.module.createUnary(UnaryOp.AbsF64, operands[0]);
else if (this.currentType == Type.f32)
return this.module.createUnary(UnaryOp.AbsF32, operands[0]);
break;
case "ceil": // f32/f64.ceil
if (this.currentType == Type.f64)
return this.module.createUnary(UnaryOp.CeilF64, operands[0]);
else if (this.currentType == Type.f32)
return this.module.createUnary(UnaryOp.CeilF32, operands[0]);
break;
case "copysign": // f32/f64.copysign
if (this.currentType == Type.f64)
return this.module.createBinary(BinaryOp.CopysignF64, operands[0], operands[1]);
else if (this.currentType == Type.f32)
return this.module.createBinary(BinaryOp.CopysignF32, operands[0], operands[1]);
break;
case "floor": // f32/f64.floor
if (this.currentType == Type.f64)
return this.module.createUnary(UnaryOp.FloorF64, operands[0]);
else if (this.currentType == Type.f32)
return this.module.createUnary(UnaryOp.FloorF32, operands[0]);
break;
case "max": // f32/f64.max
if (this.currentType == Type.f64)
return this.module.createBinary(BinaryOp.MaxF64, operands[0], operands[1]);
else if (this.currentType == Type.f32)
return this.module.createBinary(BinaryOp.MaxF32, operands[0], operands[1]);
break;
case "min": // f32/f64.min
if (this.currentType == Type.f64)
return this.module.createBinary(BinaryOp.MinF64, operands[0], operands[1]);
else if (this.currentType == Type.f32)
return this.module.createBinary(BinaryOp.MinF32, operands[0], operands[1]);
break;
case "nearest": // f32/f64.nearest
if (this.currentType == Type.f64)
return this.module.createUnary(UnaryOp.NearestF64, operands[0]);
else if (this.currentType == Type.f32)
return this.module.createUnary(UnaryOp.NearestF32, operands[0]);
break;
case "sqrt": // f32/f64.sqrt
if (this.currentType == Type.f64)
return this.module.createUnary(UnaryOp.SqrtF64, operands[0]);
else if (this.currentType == Type.f32)
return this.module.createUnary(UnaryOp.SqrtF32, operands[0]);
break;
case "trunc": // f32/f64.trunc
if (this.currentType == Type.f64)
return this.module.createUnary(UnaryOp.TruncF64, operands[0]);
else if (this.currentType == Type.f32)
return this.module.createUnary(UnaryOp.TruncF32, operands[0]);
break;
case "current_memory":
return this.module.createHost(HostOp.CurrentMemory);
case "grow_memory":
// this.warning(DiagnosticCode.Operation_is_unsafe, reportNode.range); // unsure
return this.module.createHost(HostOp.GrowMemory, null, operands);
case "unreachable":
this.currentType = previousType;
return this.module.createUnreachable();
case "isNaN": // value != value
if (functionInstance.typeArguments[0] == Type.f64) {
tempLocal = this.currentFunction.addLocal(Type.f64);
return this.module.createBinary(BinaryOp.NeF64,
this.module.createTeeLocal(tempLocal.index, operands[0]),
this.module.createGetLocal(tempLocal.index, NativeType.F64)
);
} else if (functionInstance.typeArguments[0] == Type.f32) {
tempLocal = this.currentFunction.addLocal(Type.f32);
return this.module.createBinary(BinaryOp.NeF32,
this.module.createTeeLocal(tempLocal.index, operands[0]),
this.module.createGetLocal(tempLocal.index, NativeType.F32)
);
}
break;
case "isFinite": // v=[abs(value) != Infinity, false]; return value == value ? v[0] : v[1]
if (functionInstance.typeArguments[0] == Type.f64) {
tempLocal = this.currentFunction.addLocal(Type.f64);
return this.module.createSelect(
this.module.createBinary(BinaryOp.NeF64,
this.module.createUnary(UnaryOp.AbsF64,
this.module.createTeeLocal(tempLocal.index, operands[0])
),
this.module.createF64(Infinity)
),
this.module.createI32(0),
this.module.createBinary(BinaryOp.EqF64,
this.module.createGetLocal(tempLocal.index, NativeType.F64),
this.module.createGetLocal(tempLocal.index, NativeType.F64)
)
);
} else if (functionInstance.typeArguments[0] == Type.f32) {
tempLocal = this.currentFunction.addLocal(Type.f32);
return this.module.createSelect(
this.module.createBinary(BinaryOp.NeF32,
this.module.createUnary(UnaryOp.AbsF32,
this.module.createTeeLocal(tempLocal.index, operands[0])
),
this.module.createF32(Infinity)
),
this.module.createI32(0),
this.module.createBinary(BinaryOp.EqF32,
this.module.createGetLocal(tempLocal.index, NativeType.F32),
this.module.createGetLocal(tempLocal.index, NativeType.F32)
)
);
}
break;
case "assert":
return this.options.noDebug
? this.module.createNop()
: this.module.createIf(
this.module.createUnary(UnaryOp.EqzI32, operands[0]),
this.module.createUnreachable()
);
}
this.error(DiagnosticCode.Operation_not_supported, reportNode.range);
return this.module.createUnreachable();
}
if (!functionInstance.isCompiled)
this.compileFunction(functionInstance);
// imported function
if (functionInstance.isImport)
return this.module.createCallImport(functionInstance.internalName, operands, typeToNativeType(functionInstance.returnType));
// internal function
return this.module.createCall(functionInstance.internalName, operands, typeToNativeType(functionInstance.returnType));
}
compileElementAccessExpression(expression: ElementAccessExpression, contextualType: Type): ExpressionRef {
const element: Element | null = this.program.resolveElement(expression.expression, this.currentFunction); // reports
if (!element)
return this.module.createUnreachable();
throw new Error("not implemented");
}
compileIdentifierExpression(expression: IdentifierExpression, contextualType: Type): ExpressionRef {
// null
if (expression.kind == NodeKind.NULL) {
if (contextualType.classType) // keep contextualType
return this.options.target == Target.WASM64 ? this.module.createI64(0, 0) : this.module.createI32(0);
if (this.options.target == Target.WASM64) {
this.currentType = Type.u64;
return this.module.createI64(0, 0);
} else {
this.currentType = Type.u32;
return this.module.createI32(0);
}
// true
} else if (expression.kind == NodeKind.TRUE) {
this.currentType = Type.bool;
return this.module.createI32(1);
// false
} else if (expression.kind == NodeKind.FALSE) {
this.currentType = Type.bool;
return this.module.createI32(0);
// this
} else if (expression.kind == 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();
}
if (expression.kind == NodeKind.IDENTIFIER) {
// NaN
if ((<IdentifierExpression>expression).name == "NaN")
if (this.currentType.kind == TypeKind.F32)
return this.module.createF32(NaN);
else {
this.currentType = Type.f64;
return this.module.createF64(NaN);
}
// Infinity
if ((<IdentifierExpression>expression).name == "Infinity")
if (this.currentType.kind == TypeKind.F32)
return this.module.createF32(Infinity);
else {
this.currentType = Type.f64;
return this.module.createF64(Infinity);
}
}
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 = (<Local>element).type));
}
// global
if (element.kind == ElementKind.GLOBAL) {
if ((<Global>element).type)
this.currentType = <Type>(<Global>element).type;
return this.compileGlobal(<Global>element) // reports
? this.module.createGetGlobal((<Global>element).internalName, typeToNativeType(this.currentType = <Type>(<Global>element).type))
: this.module.createUnreachable();
}
// field
// if (element.kind == ElementKind.FIELD)
// throw new Error("not implemented");
// getter
if (element.kind == ElementKind.FUNCTION_PROTOTYPE && (<FunctionPrototype>element).isGetter)
throw new Error("not implemented");
this.error(DiagnosticCode.Operation_not_supported, expression.range);
return this.module.createUnreachable();
}
compileLiteralExpression(expression: LiteralExpression, contextualType: Type): ExpressionRef {
switch (expression.literalKind) {
// case LiteralKind.ARRAY:
case LiteralKind.FLOAT: {
const floatValue: f64 = (<FloatLiteralExpression>expression).value;
if (contextualType == Type.f32)
return this.module.createF32(Math.fround(floatValue));
this.currentType = Type.f64;
return this.module.createF64(floatValue);
}
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);
if (contextualType.isLongInteger)
return this.module.createI64(intValue.lo, intValue.hi);
if (!intValue.fitsInI32) {
this.currentType = Type.i64;
return this.module.createI64(intValue.lo, intValue.hi);
}
this.currentType = Type.i32;
return this.module.createI32(intValue.toI32());
}
// case LiteralKind.OBJECT:
// case LiteralKind.REGEXP:
// case LiteralKind.STRING:
}
throw new Error("not implemented");
}
compileNewExpression(expression: NewExpression, contextualType: Type): ExpressionRef {
throw new Error("not implemented");
}
compileParenthesizedExpression(expression: ParenthesizedExpression, contextualType: Type): ExpressionRef {
return this.compileExpression(expression.expression, contextualType);
}
compilePropertyAccessExpression(expression: PropertyAccessExpression, contextualType: Type): ExpressionRef {
throw new Error("not implemented");
}
compileTernaryExpression(expression: TernaryExpression, contextualType: Type): ExpressionRef {
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);
}
compileUnaryPostfixExpression(expression: UnaryPostfixExpression, contextualType: Type): ExpressionRef {
const operator: Token = expression.operator;
// make a getter for the expression (also obtains the type)
const getValue: ExpressionRef = this.compileExpression(expression.expression, contextualType, contextualType == Type.void ? ConversionKind.NONE : ConversionKind.IMPLICIT);
// use a temp local for the intermediate value
const tempLocal: Local = this.currentFunction.addLocal(this.currentType);
let op: BinaryOp;
let nativeType: NativeType;
let nativeOne: ExpressionRef;
if (tempLocal.type == Type.f32) {
op = operator == Token.PLUS_PLUS ? BinaryOp.AddF32 : BinaryOp.SubF32;
nativeType = NativeType.F32;
nativeOne = this.module.createF32(1);
} else if (tempLocal.type == Type.f64) {
op = operator == Token.PLUS_PLUS ? BinaryOp.AddF64 : BinaryOp.SubF64;
nativeType = NativeType.F64;
nativeOne = this.module.createF64(1);
} else if (tempLocal.type.isLongInteger) {
op = operator == Token.PLUS_PLUS ? BinaryOp.AddI64 : BinaryOp.SubI64;
nativeType = NativeType.I64;
nativeOne = this.module.createI64(1, 0);
} else {
op = operator == Token.PLUS_PLUS ? BinaryOp.AddI32 : BinaryOp.SubI32;
nativeType = NativeType.I32;
nativeOne = this.module.createI32(1);
}
// make a setter that sets the new value (temp value +/- 1)
const setValue: ExpressionRef = this.compileAssignmentWithValue(expression.expression,
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;
return this.module.createBlock(null, [
this.module.createSetLocal(tempLocal.index, getValue), // +++ this.module.createTeeLocal(tempLocal.index, getValue),
setValue,
this.module.createGetLocal(tempLocal.index, nativeType) // ---
], nativeType);
}
compileUnaryPrefixExpression(expression: UnaryPrefixExpression, contextualType: Type): ExpressionRef {
const operandExpression: Expression = expression.expression;
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
? this.module.createBinary(BinaryOp.SubI64, this.module.createI64(0, 0), operand)
: this.module.createBinary(BinaryOp.SubI32, this.module.createI32(0), operand);
break;
case Token.PLUS_PLUS:
operand = this.compileExpression(operandExpression, contextualType, contextualType == Type.void ? ConversionKind.NONE : ConversionKind.IMPLICIT);
return this.currentType == Type.f32
? this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.AddF32, operand, this.module.createF32(1)), contextualType != Type.void)
: this.currentType == Type.f64
? this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.AddF64, operand, this.module.createF64(1)), contextualType != Type.void)
: this.currentType.isLongInteger
? 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);
case Token.MINUS_MINUS:
operand = this.compileExpression(operandExpression, contextualType, contextualType == Type.void ? ConversionKind.NONE : ConversionKind.IMPLICIT);
return this.currentType == Type.f32
? this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.SubF32, operand, this.module.createF32(1)), contextualType != Type.void)
: this.currentType == Type.f64
? this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.SubF64, operand, this.module.createF64(1)), contextualType != Type.void)
: this.currentType.isLongInteger
? 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
? 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);
}
}
// helpers
function typeToNativeType(type: Type): NativeType {
return type.kind == TypeKind.F32
? NativeType.F32
: type.kind == TypeKind.F64
? NativeType.F64
: type.isLongInteger
? NativeType.I64
: type.isAnyInteger || type.kind == TypeKind.BOOL
? NativeType.I32
: NativeType.None;
}
function typesToNativeTypes(types: Type[]): NativeType[] {
const k: i32 = types.length;
const ret: NativeType[] = new Array(k);
for (let i: i32 = 0; i < k; ++i)
ret[i] = typeToNativeType(types[i]);
return ret;
}
function typeToNativeZero(module: Module, type: Type): ExpressionRef {
return type.kind == TypeKind.F32
? module.createF32(0)
: type.kind == TypeKind.F64
? module.createF64(0)
: type.isLongInteger
? module.createI64(0, 0)
: module.createI32(0);
}
function typeToBinaryenOne(module: Module, type: Type): ExpressionRef {
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;
for (let i: i32 = 0, k: i32 = paramTypes.length; i < k; ++i)
sb.push(typeToSignatureNamePart(paramTypes[i]));
sb.push(typeToSignatureNamePart(returnType));
return sb.join("");
}