/// import {isFunction, Symbol, SymbolKind} from "../../compiler/core/symbol"; import {ByteArray, ByteArray_set32, ByteArray_setString} from "../../utils/bytearray"; import {CheckContext} from "../../compiler/analyzer/type-checker"; import {alignToNextMultipleOf, toHex} from "../../utils/utils"; import {isExpression, isUnary, isUnaryPostfix, Node, NODE_FLAG_IMPORT, NodeKind} from "../../compiler/core/node"; import {Type} from "../../compiler/core/type"; import {Compiler} from "../../compiler/compiler"; import {WasmOpcode} from "./opcode"; import {getBuiltinOpcode, isBuiltin} from "./builtins-helper"; import {assert} from "../../utils/assert"; import {WasmType, WasmTypeToString} from "./core/wasm-type"; import {log, logData} from "./utils/logger"; import {Bitness} from "../bitness"; import {WasmSection} from "./core/wasm-section"; import {WasmExternalKind} from "./core/wasm-external-kind"; import {WasmGlobal} from "./core/wasm-global"; import {WasmFunction} from "./core/wasm-function"; import {WasmLocal} from "./core/wasm-local"; import {WasmSharedOffset} from "./core/wasm-shared-offset"; import {WasmAssembler} from "./assembler/wasm-assembler"; import {Terminal} from "../../utils/terminal"; import {getTypedArrayElementSize, getWasmFunctionName, symbolToWasmType, typeToDataType} from "./utils"; import {WasmOptimizer} from "./optimizer/wasm-optimizer"; import {SignatureSection} from "./wasm/sections/signature-section"; import {ImportSection} from "./wasm/sections/import-section"; import {FunctionSection} from "./wasm/sections/function-section"; import {MemorySection} from "./wasm/sections/memory-section"; import {WasmBinary} from "./wasm/wasm-binary"; import {GlobalSection} from "./wasm/sections/global-section"; import {StartSection} from "./wasm/sections/start-section"; import {CodeSection} from "./wasm/sections/code-section"; import {ExportSection} from "./wasm/sections/export-section"; import {BinaryImporter, getMergedCallIndex, isBinaryImport} from "../../importer/binary-importer"; class WasmModuleEmitter { memoryInitializer: ByteArray; private HEAP_BASE_INDEX: int32 = -1; mallocFunctionIndex: int32 = -1; freeFunctionIndex: int32 = -1; startFunctionIndex: int32 = -1; context: CheckContext; startFunction: WasmFunction; currentFunction: WasmFunction; assembler: WasmAssembler; constructor(public bitness: Bitness) { this.assembler = new WasmAssembler(); } growMemoryInitializer(): void { let array = this.memoryInitializer; let current = array.length; let length = this.context.nextGlobalVariableOffset; while (current < length) { array.append(0); current = current + 1; } } emitModule(): void { this.emitTypes(); this.emitImportTable(); this.emitFunctionDeclarations(); // this.emitTables(); this.emitMemory(); this.emitGlobalDeclarations(); this.emitExportTable(); this.emitStart(); this.emitElements(); this.emitFunctionBodies(); this.emitDataSegments(); this.emitNames(); this.assembler.finish(); } emitTypes(): void { let section = this.assembler.startSection(WasmSection.Signature) as SignatureSection; let signatures = section.signatures; let offset = 0; this.assembler.writeUnsignedLEB128(signatures.length); signatures.forEach((signature, index) => { // Emit signature this.assembler.activeCode.append(`(type (;${index};) (func`); log(section.payload, offset, WasmType.func, "func sig " + index); this.assembler.writeUnsignedLEB128(WasmType.func); //form, the value for the func type constructor log(section.payload, offset, signature.argumentTypes.length, "num params"); this.assembler.writeUnsignedLEB128(signature.argumentTypes.length); //param_count, the number of parameters to the function if (signature.argumentTypes.length > 0) { this.assembler.activeCode.append(` (param`); } signature.argumentTypes.forEach(type => { log(section.payload, offset, type, WasmType[type]); this.assembler.writeUnsignedLEB128(type); //value_type, the parameter types of the function this.assembler.activeCode.append(` ${WasmTypeToString[type]}`); }); if (signature.argumentTypes.length > 0) { this.assembler.activeCode.append(`)`); } if (signature.returnType !== WasmType.VOID) { log(section.payload, offset, 1, "num results"); this.assembler.writeUnsignedLEB128(1); //return_count, the number of results from the function log(section.payload, offset, signature.returnType, WasmType[signature.returnType]); this.assembler.writeUnsignedLEB128(signature.returnType); this.assembler.activeCode.append(` (result ${WasmTypeToString[signature.returnType]})`); } else { this.assembler.writeUnsignedLEB128(0); } this.assembler.activeCode.append("))\n"); }); this.assembler.endSection(section); } emitImportTable(): void { if (this.assembler.module.importCount == 0) { return; } let section = this.assembler.startSection(WasmSection.Import) as ImportSection; let imports = section.imports; let offset = 0; log(section.payload, offset, imports.length, "num imports"); this.assembler.writeUnsignedLEB128(imports.length); imports.forEach((_import, index) => { log(section.payload, offset, null, `import func (${index}) ${_import.namespace} ${_import.name}`); this.assembler.activeCode.append(`(import "${_import.namespace}" "${_import.name}" (func (;${index};) (type ${_import.signatureIndex})))\n`); this.assembler.writeWasmString(_import.namespace); this.assembler.writeWasmString(_import.name); this.assembler.writeUnsignedLEB128(WasmExternalKind.Function); this.assembler.writeUnsignedLEB128(_import.signatureIndex); }); this.assembler.endSection(section); } emitFunctionDeclarations(): void { if (this.assembler.module.functionCount === 0) { return; } let section = this.assembler.startSection(WasmSection.Function) as FunctionSection; let functions = section.functions; let offset = 0; log(section.payload, offset, functions.length, "num functions"); this.assembler.writeUnsignedLEB128(functions.length); let importCount = this.assembler.module.importCount; functions.forEach((fn, index) => { log(section.payload, offset, fn.signatureIndex, `func ${importCount + index} sig ${getWasmFunctionName(fn.symbol)}`); this.assembler.writeUnsignedLEB128(fn.signatureIndex); }); this.assembler.endSection(section); } emitTables(): void { //TODO } emitMemory(): void { let section = this.assembler.startSection(WasmSection.Memory) as MemorySection; let memory = section.memory; if (memory.length > 1) { Terminal.warn("More than 1 memory found, In the MVP, the number of memories must be no more than 1.") } this.assembler.module.allocateExport("memory", WasmExternalKind.Memory, 0); let offset = 0; log(section.payload, offset, memory.length, "num memories"); this.assembler.writeUnsignedLEB128(1); //indicating the number of memories defined by the namespace, In the MVP, the number of memories must be no more than 1. //resizable_limits log(section.payload, offset, 0, "memory flags"); this.assembler.writeUnsignedLEB128(WasmBinary.SET_MAX_MEMORY ? 0x1 : 0); //flags, bit 0x1 is set if the maximum field is present log(section.payload, offset, WasmBinary.SIZE_IN_PAGES, "memory initial pages"); this.assembler.writeUnsignedLEB128(WasmBinary.SIZE_IN_PAGES); //initial length (in units of table elements or wasm pages) if (WasmBinary.SET_MAX_MEMORY) { log(section.payload, offset, WasmBinary.MAX_MEMORY, "maximum memory"); this.assembler.writeUnsignedLEB128(WasmBinary.MAX_MEMORY);// maximum, only present if specified by flags } this.assembler.activeCode.append("(memory $0 1)\n"); this.assembler.endSection(section); } emitGlobalDeclarations(): void { if (this.assembler.module.globals.length === 0) { return; } let section = this.assembler.startSection(WasmSection.Global) as GlobalSection; let globals = section.globals; let offset = 0; this.assembler.writeUnsignedLEB128(globals.length); this.assembler.stackTracer.setGlobals(globals); // Initialize mspace before initializing globals if (Compiler.mallocRequired) { // write mspace_init let mspace_init_index = getMergedCallIndex("mspace_init"); this.assembler.startFunctionChunk(this.startFunction, this.startFunctionIndex); this.assembler.appendOpcode(0, WasmOpcode.I32_CONST); this.assembler.writeUnsignedLEB128(40); this.assembler.appendOpcode(0, WasmOpcode.CALL); this.assembler.writeUnsignedLEB128(mspace_init_index); this.assembler.appendOpcode(0, WasmOpcode.SET_GLOBAL); this.assembler.writeUnsignedLEB128(this.HEAP_BASE_INDEX); this.assembler.endFunctionChunk(); } globals.forEach((global, index) => { let wasmType: WasmType = symbolToWasmType(global.symbol, this.bitness); let value = global.symbol.node.variableValue(); section.payload.append(wasmType); //content_type this.assembler.writeUnsignedLEB128(global.mutable ? 1 : 0); //mutability, 0 if immutable, 1 if mutable. let rawValue = 0; if (value) { if (value.kind === NodeKind.NULL || value.kind === NodeKind.UNDEFINED) { rawValue = 0; } else if (value.rawValue !== undefined) { rawValue = value.rawValue; } else { // Emit evaluation to start function this.addGlobalToStartFunction(global); } } this.assembler.appendOpcode(offset, WasmOpcode[`${WasmType[wasmType]}_CONST`], rawValue); switch (wasmType) { case WasmType.I32: this.assembler.writeUnsignedLEB128(rawValue); break; case WasmType.I64: this.assembler.writeUnsignedLEB128(rawValue); break; case WasmType.F32: this.assembler.writeFloat(rawValue); break; case WasmType.F64: this.assembler.writeDouble(rawValue); break; } let wasmTypeStr = WasmTypeToString[wasmType]; this.assembler.activeCode.append(`(global (;${index};) (mut ${wasmTypeStr}) (${wasmTypeStr}.const ${rawValue}))\n`); this.assembler.appendOpcode(offset, WasmOpcode.END); }); this.assembler.endSection(section); } addGlobalToStartFunction(global: WasmGlobal): void { let value = global.symbol.node.variableValue(); this.assembler.startFunctionChunk(this.startFunction, this.startFunctionIndex); this.emitNode(0, value); this.assembler.appendOpcode(0, WasmOpcode.SET_GLOBAL); this.assembler.writeUnsignedLEB128(global.symbol.offset); this.assembler.endFunctionChunk(); } emitExportTable(): void { if (this.assembler.module.exportCount === 0) { return; } let offset = 0; let section = this.assembler.startSection(WasmSection.Export) as ExportSection; let importCount = this.assembler.module.importCount; let exports = section.exports; log(section.payload, offset, exports.length, "num exports"); this.assembler.writeUnsignedLEB128(exports.length); //Export main memory // let memoryName: string = "memory"; // log(section.payload, offset, memoryName.length, "export name length"); // log(section.payload, null, null, `${toHex(section.payload.position + offset + 4)}: ${memoryName} // export name`); // this.assembler.writeWasmString(memoryName); // log(section.payload, offset, WasmExternalKind.Memory, "export kind"); // this.assembler.activePayload.writeUnsignedByte(WasmExternalKind.Memory); // log(section.payload, offset, 0, "export memory index"); // this.assembler.writeUnsignedLEB128(0); // this.assembler.activeCode.append(`(export "memory" (memory $0))\n`); exports.forEach((_export) => { let isFunctionExport = _export.kind === WasmExternalKind.Function; let exportIndex = isFunctionExport ? importCount + _export.index : _export.index; log(section.payload, offset, _export.as.length, "export name length"); log(section.payload, null, null, `${toHex(section.payload.position + offset + 4)}: ${_export.as} // export name`); this.assembler.writeWasmString(_export.as); log(section.payload, offset, _export.kind, "export kind"); this.assembler.writeUnsignedLEB128(_export.kind); log(section.payload, offset, exportIndex, "export index"); this.assembler.writeUnsignedLEB128(exportIndex); if (isFunctionExport) { this.assembler.activeCode.append(`(export "${_export.as}" (func $${_export.name}))\n`); } else if (_export.kind === WasmExternalKind.Memory) { this.assembler.activeCode.append(`(export "${_export.as}" (memory $${_export.index}))\n`); } }); this.assembler.endSection(section); } emitStart(): void { if (this.startFunctionIndex != -1) { let section = this.assembler.startSection(WasmSection.Start) as StartSection; let offset = 0; let importCount = this.assembler.module.importCount; log(section.payload, offset, this.startFunctionIndex, "start function index"); this.assembler.activeCode.append(`(start ${importCount + this.startFunctionIndex})\n`); this.assembler.writeUnsignedLEB128(importCount + this.startFunctionIndex); this.assembler.endSection(section); } } emitElements(): void { //TODO } emitFunctionBodies(): void { if (this.assembler.module.functionCount === 0) { return; } let offset = 0; let signatures = (this.assembler.module.binary.getSection(WasmSection.Signature) as SignatureSection).signatures; let functions = (this.assembler.module.binary.getSection(WasmSection.Function) as FunctionSection).functions; let section = this.assembler.startSection(WasmSection.Code) as CodeSection; // FIXME: functions might overwrite section.functions = functions; log(section.payload, offset, this.assembler.module.functionCount, "num functions"); this.assembler.writeUnsignedLEB128(this.assembler.module.functionCount); functions.forEach((fn, index) => { this.currentFunction = fn; let sectionOffset = offset + section.payload.position; let wasmFunctionName = getWasmFunctionName(fn.symbol); if (!fn.isExternal) { let bodyData = new ByteArray(); fn.body = bodyData; log(bodyData, sectionOffset, fn.locals.length, "local var count"); this.assembler.startFunction(fn, index); /* wasm text format */ this.assembler.activeCode.emitIndent(); this.assembler.activeCode.append(`(func $${wasmFunctionName} (type ${fn.signatureIndex}) `); fn.argumentVariables.forEach((argumentEntry) => { this.assembler.activeCode.append(`(param $${argumentEntry.name} ${WasmTypeToString[argumentEntry.type]}) `); }); let signature = signatures[fn.signatureIndex]; if (signature.returnType !== WasmType.VOID) { this.assembler.activeCode.append(`(result ${WasmTypeToString[signature.returnType]})`); } this.assembler.activeCode.append("\n", 2); if (fn.localVariables.length > 0) { bodyData.writeUnsignedLEB128(fn.localVariables.length); //local_count // TODO: Optimize local declarations fn.localVariables.forEach((localVariableEntry) => { log(bodyData, sectionOffset, 1, "local index"); bodyData.writeUnsignedLEB128(1); //count log(bodyData, sectionOffset, localVariableEntry.type, WasmType[localVariableEntry.type]); bodyData.append(localVariableEntry.type); //value_type this.assembler.activeCode.append(`(local $${localVariableEntry.name} ${WasmTypeToString[localVariableEntry.type]}) `); }); this.assembler.activeCode.append("\n"); } else { bodyData.writeUnsignedLEB128(0); } let lastChild; if (fn.isConstructor) { // this is __ctr function this.emitConstructor(sectionOffset, fn) } let child = fn.symbol.node.functionBody().firstChild; while (child != null) { lastChild = child; this.emitNode(sectionOffset, child); child = child.nextSibling; } if (fn.chunks.length > 0) { this.assembler.activeCode.clearIndent(2); fn.chunks.forEach((chunk, index) => { bodyData.copy(chunk.payload); bodyData.log += chunk.payload.log; chunk.code.removeLastLinebreak(); this.assembler.activeCode.appendRaw(chunk.code.finish()); }); } else { if (lastChild && lastChild.kind !== NodeKind.RETURN && fn.returnType != WasmType.VOID) { this.assembler.appendOpcode(sectionOffset, WasmOpcode.RETURN); } } if (fn.returnType === WasmType.VOID) { // Drop stack if not empty this.assembler.dropStack(); } this.assembler.appendOpcode(sectionOffset, WasmOpcode.END, null, true); //end, 0x0b, indicating the end of the body this.assembler.endFunction(); this.assembler.activeCode.removeLastLinebreak(); this.assembler.activeCode.append(`)\n`); } else { console.log("External function " + fn.name); } section.payload.writeUnsignedLEB128(fn.body.length); log(section.payload, offset, null, ` - func body ${this.assembler.module.importCount + (index)} (${wasmFunctionName})`); log(section.payload, offset, fn.body.length, "func body size"); section.payload.log += fn.body.log; section.payload.copy(fn.body); }); this.assembler.endSection(section); } emitDataSegments(): void { this.growMemoryInitializer(); let memoryInitializer = this.memoryInitializer; let initializerLength = memoryInitializer.length; let initialHeapPointer = alignToNextMultipleOf(WasmBinary.MEMORY_INITIALIZER_BASE + initializerLength, 8); // Store initial heap base pointer // memoryInitializer.writeUnsignedInt(initialHeapPointer, this.mspaceBasePointer); let section = this.assembler.startSection(WasmSection.Data); let offset = 0; // This only writes one single section containing everything log(section.payload, offset, 1, "num data segments"); this.assembler.writeUnsignedLEB128(1); //data_segment log(section.payload, offset, null, " - data segment header 0"); log(section.payload, offset, 0, "memory index"); this.assembler.writeUnsignedLEB128(0); //index, the linear memory index (0 in the MVP) //offset, an i32 initializer expression that computes the offset at which to place the data this.assembler.appendOpcode(offset, WasmOpcode.I32_CONST); log(section.payload, offset, WasmBinary.MEMORY_INITIALIZER_BASE, "i32 literal"); this.assembler.writeUnsignedLEB128(WasmBinary.MEMORY_INITIALIZER_BASE); //const value this.assembler.appendOpcode(offset, WasmOpcode.END); log(section.payload, offset, initializerLength, "data segment size"); this.assembler.writeUnsignedLEB128(initializerLength); //size, size of data (in bytes) log(section.payload, offset, null, " - data segment data 0"); //data, sequence of size bytes // Copy the entire memory initializer (also includes zero-initialized data for now) this.assembler.activeCode.append(`(data (i32.const ${WasmBinary.MEMORY_INITIALIZER_BASE}) " `); let i = 0; let value; while (i < initializerLength) { for (let j = 0; j < 16; j++) { if (i + j < initializerLength) { value = memoryInitializer.get(i + j); section.payload.append(value); this.assembler.activeCode.append("\\" + toHex(value, 2)); logData(section.payload, offset, value, j == 0); } } section.payload.log += "\n"; i = i + 16; } this.assembler.activeCode.append('")\n'); // section.payload.copy(memoryInitializer, initializerLength); this.assembler.endSection(section); } // Custom section for debug names // emitNames(): void { let section = this.assembler.startSection(WasmSection.Custom, "name"); let functions = (this.assembler.module.binary.getSection(WasmSection.Function) as FunctionSection).functions; let subsectionFunc: ByteArray = new ByteArray(); let subsectionLocal: ByteArray = new ByteArray(); subsectionFunc.writeUnsignedLEB128(this.assembler.module.functionCount); subsectionLocal.writeUnsignedLEB128(this.assembler.module.functionCount); functions.forEach((fn, index) => { let fnIndex = this.assembler.module.importCount + index; subsectionFunc.writeUnsignedLEB128(fnIndex); subsectionFunc.writeWasmString(fn.name); subsectionLocal.writeUnsignedLEB128(fnIndex); if (fn.locals !== undefined) { subsectionLocal.writeUnsignedLEB128(fn.locals.length); fn.locals.forEach((local, index) => { subsectionLocal.writeUnsignedLEB128(index); subsectionLocal.writeWasmString(local.name); }); } }); //subsection for function names this.assembler.writeUnsignedLEB128(1); // name_type this.assembler.writeUnsignedLEB128(subsectionFunc.length); // name_payload_len section.payload.copy(subsectionFunc); // name_payload_data //subsection for local names this.assembler.writeUnsignedLEB128(2); // name_type this.assembler.writeUnsignedLEB128(subsectionLocal.length); // name_payload_len section.payload.copy(subsectionLocal); // name_payload_data this.assembler.endSection(section); } mergeBinaryImports(): void { // TODO: Merge only imported functions and it's dependencies this.assembler.mergeBinaries(BinaryImporter.binaries); } prepareToEmit(node: Node): void { if (node.kind == NodeKind.STRING) { let text = node.stringValue; let length = text.length; let offset = this.context.allocateGlobalVariableOffset(length * 2 + 4, 4); node.intValue = offset; this.growMemoryInitializer(); let memoryInitializer = this.memoryInitializer; // Emit a length-prefixed string ByteArray_set32(memoryInitializer, offset, length); ByteArray_setString(memoryInitializer, offset + 4, text); } else if (node.kind == NodeKind.VARIABLE) { let symbol = node.symbol; /*if (symbol.kind == SymbolKind.VARIABLE_GLOBAL) { let sizeOf = symbol.resolvedType.variableSizeOf(this.context); let value = symbol.node.variableValue(); let memoryInitializer = this.memoryInitializer; // Copy the initial value into the memory initializer this.growMemoryInitializer(); let offset = symbol.offset; if (sizeOf == 1) { if (symbol.resolvedType.isUnsigned()) { memoryInitializer.writeUnsignedByte(value.intValue, offset); } else { memoryInitializer.writeByte(value.intValue, offset); } } else if (sizeOf == 2) { if (symbol.resolvedType.isUnsigned()) { memoryInitializer.writeUnsignedShort(value.intValue, offset); } else { memoryInitializer.writeShort(value.intValue, offset); } } else if (sizeOf == 4) { if (symbol.resolvedType.isFloat()) { memoryInitializer.writeFloat(value.floatValue, offset); } else { if (symbol.resolvedType.isUnsigned()) { memoryInitializer.writeUnsignedInt(value.intValue, offset); } else { memoryInitializer.writeInt(value.intValue, offset); } } } else if (sizeOf == 8) { if (symbol.resolvedType.isDouble()) { memoryInitializer.writeDouble(value.rawValue, offset); } else { //TODO Implement Int64 write if (symbol.resolvedType.isUnsigned()) { //memoryInitializer.writeUnsignedInt64(value.rawValue, offset); } else { //memoryInitializer.writeInt64(value.rawValue, offset); } } } else assert(false);*/ if (symbol.kind == SymbolKind.VARIABLE_GLOBAL) { let global = this.assembler.module.allocateGlobal(symbol, this.bitness); // Make sure the heap offset is tracked if (symbol.name == "HEAP_BASE") { assert(this.HEAP_BASE_INDEX == -1); this.HEAP_BASE_INDEX = symbol.offset; } } } else if (node.kind == NodeKind.FUNCTION && (node.symbol.kind != SymbolKind.FUNCTION_INSTANCE || node.symbol.kind == SymbolKind.FUNCTION_INSTANCE && !node.parent.isTemplate())) { let symbol = node.symbol; let wasmFunctionName: string = getWasmFunctionName(symbol); // if (isBinaryImport(wasmFunctionName)) { // node = node.nextSibling; // return; // } let returnType: Node = node.functionReturnType(); let wasmReturnType = this.getWasmType(returnType.resolvedType); let shared = new WasmSharedOffset(); let isConstructor: boolean = symbol.name == "constructor"; // Make sure to include the implicit "this" variable as a normal argument let argument = node.isExternalImport() ? node.functionFirstArgumentIgnoringThis() : node.functionFirstArgument(); let argumentVariables: WasmLocal[] = []; let argumentTypes: WasmType[] = []; while (argument != returnType) { let wasmType = this.getWasmType(argument.variableType().resolvedType); argumentVariables.push(new WasmLocal(wasmType, argument.symbol.name, argument.symbol, true)); argumentTypes.push(wasmType); shared.nextLocalOffset = shared.nextLocalOffset + 1; argument = argument.nextSibling; } let [signatureIndex, signature] = this.assembler.module.allocateSignature(argumentTypes, wasmReturnType); let body = node.functionBody(); // Functions without bodies are imports if (body == null) { if (!isBuiltin(wasmFunctionName)) { let moduleName = symbol.kind == SymbolKind.FUNCTION_INSTANCE ? symbol.parent().name : "global"; symbol.offset = this.assembler.module.importCount; if (isBinaryImport(wasmFunctionName)) { // this.assembler.module.allocateImport(signature, signatureIndex, "internal", symbol.name); symbol.node.flags |= NODE_FLAG_IMPORT; } else { this.assembler.module.allocateImport(signature, signatureIndex, moduleName, symbol.name); } } node = node.nextSibling; return; } else { symbol.offset = this.assembler.module.functionCount; } let fn = this.assembler.module.allocateFunction(wasmFunctionName, signature, signatureIndex, symbol, node.isExport()); fn.argumentVariables = argumentVariables; fn.isConstructor = isConstructor; fn.returnType = wasmReturnType; // Make sure "malloc" is tracked if (symbol.kind == SymbolKind.FUNCTION_GLOBAL && symbol.name == "malloc") { assert(this.mallocFunctionIndex == -1); this.mallocFunctionIndex = symbol.offset; } if (symbol.kind == SymbolKind.FUNCTION_GLOBAL && symbol.name == "free") { assert(this.freeFunctionIndex == -1); this.freeFunctionIndex = symbol.offset; } // Make "__WASM_INITIALIZER" as start function if (symbol.kind == SymbolKind.FUNCTION_GLOBAL && symbol.name == "__WASM_INITIALIZER") { assert(this.startFunctionIndex == -1); this.startFunctionIndex = symbol.offset; this.startFunction = fn; this.startFunction.body = new ByteArray(); } wasmAssignLocalVariableOffsets(fn, body, shared, this.bitness); fn.locals = argumentVariables.concat(fn.localVariables); } let child = node.firstChild; while (child != null) { this.prepareToEmit(child); child = child.nextSibling; } } emitBinaryExpression(byteOffset: int32, node: Node, opcode: uint8): void { this.emitNode(byteOffset, node.binaryLeft()); this.emitNode(byteOffset, node.binaryRight()); this.assembler.appendOpcode(byteOffset, opcode); } emitLoadFromMemory(byteOffset: int32, type: Type, relativeBase: Node, offset: int32): void { let opcode; // Relative address if (relativeBase != null) { this.emitNode(byteOffset, relativeBase); } // Absolute address else { opcode = WasmOpcode.I32_CONST; this.assembler.appendOpcode(byteOffset, opcode); log(this.assembler.activePayload, byteOffset, 0, "i32 literal"); this.assembler.writeUnsignedLEB128(0); } let sizeOf = type.variableSizeOf(this.context); if (sizeOf == 1) { opcode = type.isUnsigned() ? WasmOpcode.I32_LOAD8_U : WasmOpcode.I32_LOAD8_S; this.assembler.appendOpcode(byteOffset, opcode); log(this.assembler.activePayload, byteOffset, 0, "alignment"); this.assembler.writeUnsignedLEB128(0); } else if (sizeOf == 2) { opcode = type.isUnsigned() ? WasmOpcode.I32_LOAD16_U : WasmOpcode.I32_LOAD16_S; this.assembler.appendOpcode(byteOffset, opcode); log(this.assembler.activePayload, byteOffset, 1, "alignment"); this.assembler.writeUnsignedLEB128(1); } else if (sizeOf == 4 || type.isClass()) { if (type.isFloat()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.F32_LOAD); } else { this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_LOAD); } log(this.assembler.activePayload, byteOffset, 2, "alignment"); this.assembler.writeUnsignedLEB128(2); } else if (sizeOf == 8) { if (type.isDouble()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.F64_LOAD); } else { this.assembler.appendOpcode(byteOffset, WasmOpcode.I64_LOAD); } log(this.assembler.activePayload, byteOffset, 3, "alignment"); this.assembler.writeUnsignedLEB128(3); } else { assert(false); } log(this.assembler.activePayload, byteOffset, offset, "load offset"); this.assembler.writeUnsignedLEB128(offset); } emitStoreToMemory(byteOffset: int32, type: Type, relativeBase: Node, offset: int32, value: Node): void { // Relative address if (relativeBase != null ) { this.emitNode(byteOffset, relativeBase); } // Absolute address else { this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST); log(this.assembler.activePayload, byteOffset, 0, "i32 literal"); this.assembler.writeUnsignedLEB128(0); } this.emitNode(byteOffset, value); let sizeOf = type.variableSizeOf(this.context); if (sizeOf == 1) { this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_STORE8); log(this.assembler.activePayload, byteOffset, 0, "alignment"); this.assembler.writeUnsignedLEB128(0); } else if (sizeOf == 2) { this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_STORE16); log(this.assembler.activePayload, byteOffset, 1, "alignment"); this.assembler.writeUnsignedLEB128(1); } else if (sizeOf == 4 || type.isClass()) { if (type.isFloat()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.F32_STORE); } else { this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_STORE); } log(this.assembler.activePayload, byteOffset, 2, "alignment"); this.assembler.writeUnsignedLEB128(2); } else if (sizeOf == 8) { if (type.isDouble()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.F64_STORE); } else if (type.isLong()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.I64_STORE); } log(this.assembler.activePayload, byteOffset, 3, "alignment"); this.assembler.writeUnsignedLEB128(3); } else { assert(false); } log(this.assembler.activePayload, byteOffset, offset, "load offset"); this.assembler.writeUnsignedLEB128(offset); } /** * Emit instance * @param array * @param byteOffset * @param node */ emitInstance(byteOffset: int32, node: Node): void { let constructorNode = node.constructorNode(); if (constructorNode !== undefined) { let callSymbol = constructorNode.symbol; let type = node.newType(); let size; if (type.resolvedType.isArray()) { /** * If the new type if an array append total byte length and element size **/ let elementNode = type.firstGenericType(); let elementType = elementNode.resolvedType; let isClassElement = elementType.isClass(); //ignore 64 bit pointer size = isClassElement ? 4 : elementType.allocationSizeOf(this.context); assert(size > 0); let lengthNode = node.arrayLength(); if (lengthNode.kind == NodeKind.INT32) { let length = size * lengthNode.intValue; this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, length); this.assembler.writeLEB128(length); //array byteLength } else { this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, size); this.assembler.writeLEB128(size); this.emitNode(byteOffset, lengthNode); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_MUL); //array byteLength } this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, size); this.assembler.writeLEB128(size); // array element size let callIndex: int32 = this.getWasmFunctionCallIndex(callSymbol); this.assembler.appendOpcode(byteOffset, WasmOpcode.CALL); log(this.assembler.activePayload, byteOffset, callIndex, `call func index (${callIndex})`); this.assembler.writeUnsignedLEB128(callIndex); } else if (type.resolvedType.isTypedArray()) { // let elementSize = getTypedArrayElementSize(type.resolvedType.symbol.name); // this.assembler.appendOpcode(byteOffset, WasmOpcode.GET_LOCAL); // this.assembler.writeLEB128(0); // this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST); // this.assembler.writeLEB128(elementSize); // this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_SHL); // this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST); // this.assembler.writeLEB128(size); // this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_ADD); } else { // Emit constructor argumentVariables let child = node.firstChild.nextSibling; while (child != null) { this.emitNode(byteOffset, child); child = child.nextSibling; } let callIndex: int32 = this.getWasmFunctionCallIndex(callSymbol); this.assembler.appendOpcode(byteOffset, WasmOpcode.CALL, callIndex); this.assembler.writeUnsignedLEB128(callIndex); } } } /** * Emit constructor function where malloc happens * @param array * @param byteOffset * @param fn */ emitConstructor(byteOffset: int32, fn: WasmFunction): void { let constructorNode: Node = fn.symbol.node; let type = constructorNode.parent.symbol; let size = type.resolvedType.allocationSizeOf(this.context); assert(size > 0); if (type.resolvedType.isArray()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.GET_LOCAL, 0); this.assembler.writeUnsignedLEB128(0); // array parameter byteLength this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, size); this.assembler.writeLEB128(size); // size of array class, default is 8 bytes this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_ADD); } else if (type.resolvedType.isTypedArray()) { let elementSize = getTypedArrayElementSize(type.resolvedType.symbol.name); this.assembler.appendOpcode(byteOffset, WasmOpcode.GET_LOCAL, 0); this.assembler.writeUnsignedLEB128(0); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, elementSize); this.assembler.writeLEB128(elementSize); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_SHL); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, size); this.assembler.writeLEB128(size); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_ADD); } else { // Pass the object size as the first argument this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, size); this.assembler.writeLEB128(size); } // Allocate memory let mallocIndex = this.calculateWasmFunctionIndex(this.mallocFunctionIndex); this.assembler.appendOpcode(byteOffset, WasmOpcode.CALL, mallocIndex); this.assembler.writeUnsignedLEB128(mallocIndex); this.assembler.appendOpcode(byteOffset, WasmOpcode.SET_LOCAL, fn.signature.argumentTypes.length); this.assembler.writeUnsignedLEB128(fn.signature.argumentTypes.length); // Set self pointer to first local variable which is immediate after the argument variable } emitNode(byteOffset: int32, node: Node): int32 { // Assert assert(!isExpression(node) || node.resolvedType != null); if (node.kind == NodeKind.BLOCK) { /** * Skip emitting block if parent is 'if' or 'loop' since it is already a block */ let skipBlock = node.parent.kind === NodeKind.IF; if (!skipBlock) { this.assembler.appendOpcode(byteOffset, WasmOpcode.BLOCK); if (node.returnNode !== undefined) { // log(this.assembler.activePayload, byteOffset, this.currentFunction.returnType, WasmType[this.currentFunction.returnType]); this.assembler.append(byteOffset, this.currentFunction.returnType); this.assembler.activeCode.removeLastLinebreak(); this.assembler.activeCode.append(" (result " + WasmTypeToString[this.currentFunction.returnType] + ")\n", 1); } else { // log(this.assembler.activePayload, byteOffset, WasmType.block_type); this.assembler.append(byteOffset, WasmType.block_type); } } let child = node.firstChild; while (child != null) { this.emitNode(byteOffset, child); child = child.nextSibling; } if (!skipBlock) { this.assembler.activeCode.clearIndent(1); this.assembler.activeCode.indent -= 1; this.assembler.appendOpcode(byteOffset, WasmOpcode.END); } } else if (node.kind == NodeKind.WHILE) { let value = node.whileValue(); let body = node.whileBody(); // Ignore "while (false) { ... }" if (value.kind == NodeKind.BOOLEAN && value.intValue == 0) { return 0; } this.assembler.appendOpcode(byteOffset, WasmOpcode.BLOCK); //log(this.assembler.activePayload, WasmType.block_type); this.assembler.append(byteOffset, WasmType.block_type); this.assembler.appendOpcode(byteOffset, WasmOpcode.LOOP); //log(this.assembler.activePayload, 0, WasmType.block_type, WasmType[WasmType.block_type]); this.assembler.append(byteOffset, WasmType.block_type); if (value.kind != NodeKind.BOOLEAN) { this.emitNode(byteOffset, value); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_EQZ); this.assembler.appendOpcode(byteOffset, WasmOpcode.BR_IF); this.assembler.writeUnsignedLEB128(1); // Break out of the immediately enclosing loop } let child = body.firstChild; while (child != null) { this.emitNode(byteOffset, child); child = child.nextSibling; } // Jump back to the top (this doesn't happen automatically) this.assembler.appendOpcode(byteOffset, WasmOpcode.BR); this.assembler.writeUnsignedLEB128(0); // Continue back to the immediately enclosing loop this.assembler.appendOpcode(byteOffset, WasmOpcode.END); // end inner block this.assembler.appendOpcode(byteOffset, WasmOpcode.END); // end outer block } else if (node.kind == NodeKind.FOR) { let initializationStmt = node.forInitializationStatement(); let terminationStmt = node.forTerminationStatement(); let updateStmt = node.forUpdateStatements(); let body = node.forBody(); this.assembler.appendOpcode(byteOffset, WasmOpcode.BLOCK); //log(this.assembler.activePayload, WasmType.block_type); this.assembler.append(byteOffset, WasmType.block_type); this.emitNode(byteOffset, initializationStmt); this.assembler.appendOpcode(byteOffset, WasmOpcode.LOOP); //log(this.assembler.activePayload, 0, WasmType.block_type, WasmType[WasmType.block_type]); this.assembler.append(byteOffset, WasmType.block_type); if (terminationStmt.kind != NodeKind.BOOLEAN) { this.emitNode(byteOffset, terminationStmt); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_EQZ); this.assembler.appendOpcode(byteOffset, WasmOpcode.BR_IF); this.assembler.writeUnsignedLEB128(1); // Break out of the immediately enclosing loop } let child = body.firstChild; while (child != null) { this.emitNode(byteOffset, child); child = child.nextSibling; } this.emitNode(byteOffset, updateStmt); // Jump back to the top (this doesn't happen automatically) this.assembler.appendOpcode(byteOffset, WasmOpcode.BR); this.assembler.writeUnsignedLEB128(0); // Continue back to the immediately enclosing loop this.assembler.appendOpcode(byteOffset, WasmOpcode.END); // end inner block this.assembler.appendOpcode(byteOffset, WasmOpcode.END); // end outer block } else if (node.kind == NodeKind.BREAK || node.kind == NodeKind.CONTINUE) { let label = 0; let parent = node.parent; while (parent != null && parent.kind != NodeKind.WHILE) { if (parent.kind == NodeKind.BLOCK) { label = label + 1; } parent = parent.parent; } assert(label > 0); this.assembler.appendOpcode(byteOffset, WasmOpcode.BR); this.assembler.writeUnsignedLEB128(label - (node.kind == NodeKind.BREAK ? 0 : 1)); } else if (node.kind == NodeKind.EMPTY) { return 0; } else if (node.kind == NodeKind.EXPRESSIONS) { let child = node.firstChild; while (child) { this.emitNode(byteOffset, child.expressionValue()); child = child.nextSibling; } } else if (node.kind == NodeKind.EXPRESSION) { this.emitNode(byteOffset, node.expressionValue()); } else if (node.kind == NodeKind.RETURN) { let value = node.returnValue(); if (value != null) { this.emitNode(byteOffset, value); } this.assembler.appendOpcode(byteOffset, WasmOpcode.RETURN); } else if (node.kind == NodeKind.VARIABLES) { let count = 0; let child = node.firstChild; while (child != null) { assert(child.kind == NodeKind.VARIABLE); count = count + this.emitNode(byteOffset, child); child = child.nextSibling; } return count; } else if (node.kind == NodeKind.IF) { let branch = node.ifFalse(); this.emitNode(byteOffset, node.ifValue()); this.assembler.appendOpcode(byteOffset, WasmOpcode.IF); let returnNode = node.ifReturnNode(); let needEmptyElse = false; if (returnNode == null && branch === null) { this.assembler.append(0, WasmType.block_type, WasmType[WasmType.block_type]); } else { if (returnNode !== null) { let returnType: WasmType = symbolToWasmType(returnNode.resolvedType.symbol); this.assembler.append(0, returnType, WasmType[returnType]); this.assembler.activeCode.removeLastLinebreak(); this.assembler.activeCode.append(` (result ${WasmTypeToString[returnType]})\n`); if (branch == null) { needEmptyElse = true; } } else { this.assembler.append(0, WasmType.block_type, WasmType[WasmType.block_type]); } } this.emitNode(byteOffset, node.ifTrue()); if (branch != null) { this.assembler.activeCode.indent -= 1; this.assembler.activeCode.clearIndent(1); this.assembler.appendOpcode(byteOffset, WasmOpcode.IF_ELSE); this.emitNode(byteOffset, branch); } else if (needEmptyElse) { this.assembler.activeCode.indent -= 1; this.assembler.activeCode.clearIndent(1); this.assembler.appendOpcode(byteOffset, WasmOpcode.IF_ELSE); let dataType: string = typeToDataType(returnNode.resolvedType, this.bitness); this.assembler.appendOpcode(byteOffset, WasmOpcode[`${dataType}_CONST`]); if (dataType === "I32" || dataType === "I64") { this.assembler.writeUnsignedLEB128(0); } else if (dataType === "F32") { this.assembler.writeFloat(0); } else if (dataType === "F64") { this.assembler.writeDouble(0); } } this.assembler.appendOpcode(byteOffset, WasmOpcode.END); } else if (node.kind == NodeKind.HOOK) { this.emitNode(byteOffset, node.hookValue()); this.assembler.appendOpcode(byteOffset, WasmOpcode.IF); let trueValue = node.hookTrue(); let trueValueType = symbolToWasmType(trueValue.resolvedType.symbol); this.assembler.append(0, trueValueType, WasmType[trueValueType]); this.emitNode(byteOffset, trueValue); this.assembler.appendOpcode(byteOffset, WasmOpcode.IF_ELSE); this.emitNode(byteOffset, node.hookFalse()); this.assembler.appendOpcode(byteOffset, WasmOpcode.END); } else if (node.kind == NodeKind.VARIABLE) { let value = node.variableValue(); if (node.symbol.name == "this" && this.currentFunction.symbol.name == "constructor") { // skip this } else if (node.symbol.kind == SymbolKind.VARIABLE_LOCAL) { if (value && value.kind != NodeKind.NAME && value.kind != NodeKind.CALL && value.kind != NodeKind.NEW && value.kind != NodeKind.DOT && value.rawValue) { if (node.symbol.resolvedType.isFloat()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.F32_CONST, value.floatValue); this.assembler.writeFloat(value.floatValue); } else if (node.symbol.resolvedType.isDouble()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.F64_CONST, value.doubleValue); this.assembler.writeDouble(value.doubleValue); } else if (node.symbol.resolvedType.isLong()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.I64_CONST, value.longValue); this.assembler.writeLEB128(value.longValue); } else { this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, value.intValue); this.assembler.writeLEB128(value.intValue); } } else { if (value != null) { this.emitNode(byteOffset, value); } else { // Default value if (node.symbol.resolvedType.isFloat()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.F32_CONST, 0); this.assembler.writeFloat(0); } else if (node.symbol.resolvedType.isDouble()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.F64_CONST, 0); this.assembler.writeDouble(0); } else if (node.symbol.resolvedType.isLong()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.I64_CONST, 0); this.assembler.writeLEB128(0); } else { this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, 0); this.assembler.writeLEB128(0); } } } let skipSetLocal = value && isUnaryPostfix(value.kind); if (skipSetLocal == false) { this.assembler.appendOpcode(byteOffset, WasmOpcode.SET_LOCAL, node.symbol.offset); this.assembler.writeUnsignedLEB128(node.symbol.offset); } } else { assert(false); } } else if (node.kind == NodeKind.NAME) { let symbol = node.symbol; if (symbol.kind == SymbolKind.VARIABLE_ARGUMENT || symbol.kind == SymbolKind.VARIABLE_LOCAL) { // FIXME This should handle in checker. if (symbol.name === "this" && this.currentFunction.symbol.name === "constructor") { this.assembler.appendOpcode(byteOffset, WasmOpcode.GET_LOCAL, this.currentFunction.signature.argumentTypes.length); this.assembler.writeUnsignedLEB128(this.currentFunction.signature.argumentTypes.length); } else { this.assembler.appendOpcode(byteOffset, WasmOpcode.GET_LOCAL, symbol.offset); this.assembler.writeUnsignedLEB128(symbol.offset); } } else if (symbol.kind == SymbolKind.VARIABLE_GLOBAL) { // FIXME: Final spec allow immutable global variables this.assembler.appendOpcode(byteOffset, WasmOpcode.GET_GLOBAL, symbol.offset); this.assembler.writeUnsignedLEB128(symbol.offset); // this.emitLoadFromMemory(byteOffset, symbol.resolvedType, null, MEMORY_INITIALIZER_BASE + symbol.offset); } else { assert(false); } } else if (node.kind == NodeKind.DEREFERENCE) { this.emitLoadFromMemory(byteOffset, node.resolvedType.underlyingType(this.context), node.unaryValue(), 0); } else if (node.kind == NodeKind.POINTER_INDEX) { this.emitLoadFromMemory(byteOffset, node.resolvedType.underlyingType(this.context), node.pointer(), node.pointerOffset()); } else if (node.kind == NodeKind.NULL) { this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, 0); this.assembler.writeLEB128(0); } else if (node.kind == NodeKind.INT32 || node.kind == NodeKind.BOOLEAN) { this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, node.intValue); this.assembler.writeLEB128(node.intValue || 0); } else if (node.kind == NodeKind.INT64) { this.assembler.appendOpcode(byteOffset, WasmOpcode.I64_CONST, node.longValue); this.assembler.writeLEB128(node.longValue); } else if (node.kind == NodeKind.FLOAT32) { this.assembler.appendOpcode(byteOffset, WasmOpcode.F32_CONST, node.floatValue); this.assembler.writeFloat(node.floatValue); } else if (node.kind == NodeKind.FLOAT64) { this.assembler.appendOpcode(byteOffset, WasmOpcode.F64_CONST, node.doubleValue); this.assembler.writeDouble(node.doubleValue); } else if (node.kind == NodeKind.STRING) { let value = WasmBinary.MEMORY_INITIALIZER_BASE + node.intValue; this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, value); this.assembler.writeLEB128(value); } else if (node.kind == NodeKind.CALL) { let value = node.callValue(); let symbol = value.symbol; assert(isFunction(symbol.kind)); // Write out the implicit "this" argument if (!symbol.node.isExternalImport() && symbol.kind == SymbolKind.FUNCTION_INSTANCE) { let dotTarget = value.dotTarget(); this.emitNode(byteOffset, dotTarget); if (dotTarget.kind == NodeKind.NEW) { this.emitInstance(byteOffset, dotTarget); } } let child = value.nextSibling; while (child != null) { this.emitNode(byteOffset, child); child = child.nextSibling; } let wasmFunctionName: string = getWasmFunctionName(symbol); if (isBuiltin(wasmFunctionName)) { this.assembler.appendOpcode(byteOffset, getBuiltinOpcode(symbol.name)); } else { let callIndex: int32; if (isBinaryImport(wasmFunctionName)) { callIndex = getMergedCallIndex(wasmFunctionName); } else { callIndex = this.getWasmFunctionCallIndex(symbol); } this.assembler.appendOpcode(byteOffset, WasmOpcode.CALL, callIndex); this.assembler.writeUnsignedLEB128(callIndex); } } else if (node.kind == NodeKind.NEW) { this.emitInstance(byteOffset, node); } else if (node.kind == NodeKind.DELETE) { let value = node.deleteValue(); this.emitNode(byteOffset, value); let freeIndex = this.calculateWasmFunctionIndex(this.freeFunctionIndex); this.assembler.appendOpcode(byteOffset, WasmOpcode.CALL, freeIndex); this.assembler.writeUnsignedLEB128(freeIndex); } else if (node.kind == NodeKind.POSITIVE) { this.emitNode(byteOffset, node.unaryValue()); } else if (node.kind == NodeKind.NEGATIVE) { let resolvedType = node.unaryValue().resolvedType; if (resolvedType.isFloat()) { this.emitNode(byteOffset, node.unaryValue()); this.assembler.appendOpcode(byteOffset, WasmOpcode.F32_NEG); } else if (resolvedType.isDouble()) { this.emitNode(byteOffset, node.unaryValue()); this.assembler.appendOpcode(byteOffset, WasmOpcode.F64_NEG); } else if (resolvedType.isInteger()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, 0); this.assembler.writeLEB128(0); this.emitNode(byteOffset, node.unaryValue()); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_SUB); } else if (resolvedType.isLong()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.I64_CONST, 0); this.assembler.writeLEB128(0); this.emitNode(byteOffset, node.unaryValue()); this.assembler.appendOpcode(byteOffset, WasmOpcode.I64_SUB); } } else if (node.kind == NodeKind.COMPLEMENT) { this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, ~0); this.assembler.writeLEB128(~0); this.emitNode(byteOffset, node.unaryValue()); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_XOR); } else if (node.kind == NodeKind.NOT) { this.emitNode(byteOffset, node.unaryValue()); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_EQZ); } else if (node.kind == NodeKind.CAST) { let value = node.castValue(); let context = this.context; let from = value.resolvedType.underlyingType(context); let type = node.resolvedType.underlyingType(context); let fromSize = from.variableSizeOf(context); let typeSize = type.variableSizeOf(context); //FIXME: Handle 8,16 bit integer to float casting // Sign-extend // if ( // from == context.int32Type && // type == context.int8Type || type == context.int16Type // ) { // let shift = 32 - typeSize * 8; // this.emitNode(byteOffset, value); // this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST); // log(byteOffset, shift, "i32 literal"); // this.assembler.writeLEB128(shift); // this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_SHR_S); // this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST); // log(byteOffset, shift, "i32 literal"); // this.assembler.writeLEB128(shift); // this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_SHL); // } // // // Mask // else if ( // from == context.int32Type || from == context.uint32Type && // type == context.uint8Type || type == context.uint16Type // ) { // this.emitNode(byteOffset, value); // this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST); // let _value = type.integerBitMask(this.context); // log(byteOffset, _value, "i32 literal"); // this.assembler.writeLEB128(_value); // this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_AND); // } // --- 32 bit Integer casting --- // i32 > i64 if ( (from == context.nullType || from == context.booleanType || from == context.int32Type || from == context.uint32Type) && (type == context.int64Type || type == context.uint64Type) ) { if (value.kind == NodeKind.NULL) { this.assembler.appendOpcode(byteOffset, WasmOpcode.I64_CONST, 0); this.assembler.writeLEB128(0); } else if (value.kind == NodeKind.BOOLEAN) { let intValue = value.intValue || 0; this.assembler.appendOpcode(byteOffset, WasmOpcode.I64_CONST, intValue); this.assembler.writeLEB128(intValue); } else if (value.kind == NodeKind.INT32) { this.assembler.appendOpcode(byteOffset, WasmOpcode.I64_CONST, value.longValue); this.assembler.writeLEB128(value.longValue); } else { let isUnsigned = value.resolvedType.isUnsigned(); this.emitNode(byteOffset, value); this.assembler.appendOpcode(byteOffset, isUnsigned ? WasmOpcode.I64_EXTEND_U_I32 : WasmOpcode.I64_EXTEND_S_I32); } } // i32 > f32 else if ( (from == context.nullType || from == context.booleanType || from == context.int32Type || from == context.uint32Type) && type == context.float32Type ) { if (value.kind == NodeKind.NULL) { this.assembler.appendOpcode(byteOffset, WasmOpcode.F32_CONST, 0); this.assembler.writeFloat(0); } else if (value.kind == NodeKind.BOOLEAN) { let floatValue = value.intValue || 0; this.assembler.appendOpcode(byteOffset, WasmOpcode.F32_CONST, floatValue); this.assembler.writeFloat(floatValue); } else if (value.kind == NodeKind.INT32) { let floatValue = value.floatValue || 0; this.assembler.appendOpcode(byteOffset, WasmOpcode.F32_CONST, floatValue); this.assembler.writeFloat(floatValue); } else { let isUnsigned = value.resolvedType.isUnsigned(); this.emitNode(byteOffset, value); this.assembler.appendOpcode(byteOffset, isUnsigned ? WasmOpcode.F32_CONVERT_U_I32 : WasmOpcode.F32_CONVERT_S_I32); } } // i32 > f64 else if ( (from == context.nullType || from == context.int32Type || from == context.uint32Type) && type == context.float64Type ) { if (value.kind == NodeKind.NULL) { this.assembler.appendOpcode(byteOffset, WasmOpcode.F64_CONST, 0); this.assembler.writeDouble(0); } else if (value.kind == NodeKind.BOOLEAN) { let doubleValue = value.doubleValue || 0; this.assembler.appendOpcode(byteOffset, WasmOpcode.F64_CONST, doubleValue); this.assembler.writeDouble(doubleValue); } else if (value.kind == NodeKind.INT32) { let doubleValue = value.doubleValue || 0; this.assembler.appendOpcode(byteOffset, WasmOpcode.F64_CONST, doubleValue); this.assembler.writeDouble(doubleValue); } else { let isUnsigned = value.resolvedType.isUnsigned(); this.emitNode(byteOffset, value); this.assembler.appendOpcode(byteOffset, isUnsigned ? WasmOpcode.F64_CONVERT_U_I32 : WasmOpcode.F64_CONVERT_S_I32); } } //----- // --- 64 bit Integer casting --- // i64 > i32 else if ( (from == context.int64Type || from == context.uint64Type) && (type == context.int32Type || type == context.uint32Type) ) { if (value.kind == NodeKind.INT64) { let intValue = value.intValue || 0; this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, intValue); this.assembler.writeLEB128(intValue); } else { this.emitNode(byteOffset, value); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_WRAP_I64); } } // i64 > f32 else if ( (from == context.int64Type || from == context.uint64Type) && type == context.float32Type ) { if (value.kind == NodeKind.INT32) { let floatValue = value.floatValue || 0; this.assembler.appendOpcode(byteOffset, WasmOpcode.F32_CONST, floatValue); this.assembler.writeFloat(floatValue); } else { let isUnsigned = value.resolvedType.isUnsigned(); this.emitNode(byteOffset, value); this.assembler.appendOpcode(byteOffset, isUnsigned ? WasmOpcode.F32_CONVERT_U_I64 : WasmOpcode.F32_CONVERT_S_I64); } } // i64 > f64 else if ( (from == context.int64Type || from == context.uint64Type) && type == context.float64Type) { if (value.kind == NodeKind.INT64) { let doubleValue = value.doubleValue || 0; this.assembler.appendOpcode(byteOffset, WasmOpcode.F64_CONST, doubleValue); this.assembler.writeDouble(doubleValue); } else { let isUnsigned = value.resolvedType.isUnsigned(); this.emitNode(byteOffset, value); this.assembler.appendOpcode(byteOffset, isUnsigned ? WasmOpcode.F64_CONVERT_U_I64 : WasmOpcode.F64_CONVERT_S_I64); } } //------ // --- 32 bit float casting --- // f32 > i32 else if ( from == context.float32Type && (type == context.uint8Type || type == context.int8Type || type == context.uint16Type || type == context.int16Type || type == context.uint32Type || type == context.int32Type) ) { if (value.kind == NodeKind.FLOAT32) { let intValue = value.intValue || 0; this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, intValue); this.assembler.writeLEB128(intValue); } else { let isUnsigned = type.isUnsigned(); this.emitNode(byteOffset, value); this.assembler.appendOpcode(byteOffset, isUnsigned ? WasmOpcode.I32_TRUNC_U_F32 : WasmOpcode.I32_TRUNC_S_F32); } } // f32 > i64 else if ( from == context.float32Type && (type == context.int64Type || type == context.uint64Type) ) { if (value.kind == NodeKind.FLOAT32) { let longValue = value.longValue || 0; this.assembler.appendOpcode(byteOffset, WasmOpcode.I64_CONST, longValue); this.assembler.writeLEB128(longValue); } else { let isUnsigned = type.isUnsigned(); this.emitNode(byteOffset, value); this.assembler.appendOpcode(byteOffset, isUnsigned ? WasmOpcode.I64_TRUNC_U_F32 : WasmOpcode.I64_TRUNC_S_F32); } } // f32 > f64 else if (from == context.float32Type && type == context.float64Type) { if (value.kind == NodeKind.FLOAT32) { let doubleValue = value.doubleValue || 0; this.assembler.appendOpcode(byteOffset, WasmOpcode.F64_CONST, doubleValue); this.assembler.writeDouble(doubleValue); } else { this.emitNode(byteOffset, value); this.assembler.appendOpcode(byteOffset, WasmOpcode.F64_PROMOTE_F32); } } //---- // --- 64 bit float casting --- // f64 > i32 else if ( from == context.float64Type && (type == context.uint8Type || type == context.int8Type || type == context.uint16Type || type == context.int16Type || type == context.uint32Type || type == context.int32Type) ) { if (value.kind == NodeKind.FLOAT64) { let intValue = value.intValue || 0; this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, intValue); this.assembler.writeLEB128(intValue); } else { let isUnsigned = type.isUnsigned(); this.emitNode(byteOffset, value); this.assembler.appendOpcode(byteOffset, isUnsigned ? WasmOpcode.I32_TRUNC_U_F64 : WasmOpcode.I32_TRUNC_S_F64); } } // f64 > i64 else if ( from == context.float64Type && (type == context.int64Type || type == context.uint64Type) ) { if (value.kind == NodeKind.FLOAT64) { let longValue = value.longValue || 0; this.assembler.appendOpcode(byteOffset, WasmOpcode.I64_CONST, longValue); this.assembler.writeLEB128(longValue); } else { let isUnsigned = type.isUnsigned(); this.emitNode(byteOffset, value); this.assembler.appendOpcode(byteOffset, isUnsigned ? WasmOpcode.I64_TRUNC_U_F64 : WasmOpcode.I64_TRUNC_S_F64); } } // f64 > f32 else if (from == context.float64Type && type == context.float32Type) { if (value.kind == NodeKind.FLOAT64) { let floatValue = value.floatValue || 0; this.assembler.appendOpcode(byteOffset, WasmOpcode.F32_CONST, floatValue); this.assembler.writeFloat(floatValue); } else { this.emitNode(byteOffset, value); this.assembler.appendOpcode(byteOffset, WasmOpcode.F32_DEMOTE_F64); } } // No cast needed else { this.emitNode(byteOffset, value); } } else if (node.kind == NodeKind.DOT) { let symbol = node.symbol; if (symbol.kind == SymbolKind.VARIABLE_INSTANCE) { this.emitLoadFromMemory(byteOffset, symbol.resolvedType, node.dotTarget(), symbol.offset); } else { assert(false); } } else if (node.kind == NodeKind.ASSIGN) { let left = node.binaryLeft(); let right = node.binaryRight(); let symbol = left.symbol; if (left.kind == NodeKind.DEREFERENCE) { this.emitStoreToMemory(byteOffset, left.resolvedType.underlyingType(this.context), left.unaryValue(), 0, right); } else if (left.kind == NodeKind.POINTER_INDEX) { this.emitStoreToMemory(byteOffset, left.resolvedType.underlyingType(this.context), left.pointer(), left.pointerOffset(), right); } else if (symbol.kind == SymbolKind.VARIABLE_INSTANCE) { this.emitStoreToMemory(byteOffset, symbol.resolvedType, left.dotTarget(), symbol.offset, right); } else if (symbol.kind == SymbolKind.VARIABLE_GLOBAL) { this.emitNode(byteOffset, right); this.assembler.appendOpcode(byteOffset, WasmOpcode.SET_GLOBAL); this.assembler.writeUnsignedLEB128(symbol.offset); // this.emitStoreToMemory(byteOffset, symbol.resolvedType, null, MEMORY_INITIALIZER_BASE + symbol.offset, right); } else if (symbol.kind == SymbolKind.VARIABLE_ARGUMENT || symbol.kind == SymbolKind.VARIABLE_LOCAL) { this.emitNode(byteOffset, right); if (!isUnaryPostfix(right.kind)) { this.assembler.appendOpcode(byteOffset, WasmOpcode.SET_LOCAL, symbol.offset); this.assembler.writeUnsignedLEB128(symbol.offset); } } else { assert(false); } } else if (node.kind == NodeKind.LOGICAL_AND) { this.emitNode(byteOffset, node.binaryLeft()); this.emitNode(byteOffset, node.binaryRight()); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_AND); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, 1); this.assembler.writeLEB128(1); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_EQ); } else if (node.kind == NodeKind.LOGICAL_OR) { this.emitNode(byteOffset, node.binaryLeft()); this.emitNode(byteOffset, node.binaryRight()); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_OR); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST); log(this.assembler.activePayload, byteOffset, 1, "i32 literal"); this.assembler.writeLEB128(1); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_EQ); } else if (isUnary(node.kind)) { let kind = node.kind; if (kind == NodeKind.POSTFIX_INCREMENT || kind == NodeKind.POSTFIX_DECREMENT) { let value = node.unaryValue(); let dataType: string = typeToDataType(value.resolvedType, this.bitness); //TODO handle instance variable if (node.parent.kind == NodeKind.VARIABLE) { this.emitNode(byteOffset, value); this.assembler.appendOpcode(byteOffset, WasmOpcode.SET_LOCAL, node.parent.symbol.offset); this.assembler.writeUnsignedLEB128(node.parent.symbol.offset); } else if (node.parent.kind == NodeKind.ASSIGN) { this.emitNode(byteOffset, value); let left = node.parent.binaryLeft(); this.assembler.appendOpcode(byteOffset, WasmOpcode.SET_LOCAL, left.symbol.offset); this.assembler.writeUnsignedLEB128(left.symbol.offset); } this.emitNode(byteOffset, value); if (node.parent.kind != NodeKind.RETURN) { assert( value.resolvedType.isInteger() || value.resolvedType.isLong() || value.resolvedType.isFloat() || value.resolvedType.isDouble() ); let size = value.resolvedType.pointerTo ? value.resolvedType.pointerTo.allocationSizeOf(this.context) : value.resolvedType.allocationSizeOf(this.context); if (size == 1 || size == 2) { if (value.kind == NodeKind.INT32 || value.resolvedType.isInteger()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, 1); this.assembler.writeLEB128(1); } else { Terminal.error("Wrong type"); } } else if (size == 4) { if (value.kind == NodeKind.INT32 || value.resolvedType.isInteger()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, 1); this.assembler.writeLEB128(1); } else if (value.kind == NodeKind.FLOAT32 || value.resolvedType.isFloat()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.F32_CONST, 1.0); this.assembler.writeFloat(1); } else { Terminal.error("Wrong type"); } } else if (size == 8) { if (value.kind == NodeKind.INT64 || value.resolvedType.isLong()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.I64_CONST, 1); this.assembler.writeLEB128(1); } else if (value.kind == NodeKind.FLOAT64 || value.resolvedType.isDouble()) { this.assembler.appendOpcode(byteOffset, WasmOpcode.F64_CONST, 1.0); this.assembler.writeDouble(1); } else { Terminal.error("Wrong type"); } } //TODO extend to other operations let operation = kind == NodeKind.POSTFIX_INCREMENT ? "ADD" : "SUB"; this.assembler.appendOpcode(byteOffset, WasmOpcode[`${dataType}_${operation}`]); if (value.symbol.kind == SymbolKind.VARIABLE_GLOBAL) { this.assembler.appendOpcode(byteOffset, WasmOpcode.SET_GLOBAL, value.symbol.offset); this.assembler.writeLEB128(value.symbol.offset); } else if (value.symbol.kind == SymbolKind.VARIABLE_LOCAL || value.symbol.kind == SymbolKind.VARIABLE_ARGUMENT) { this.assembler.appendOpcode(byteOffset, WasmOpcode.SET_LOCAL, value.symbol.offset); this.assembler.writeLEB128(value.symbol.offset); } else if (value.symbol.kind == SymbolKind.VARIABLE_INSTANCE) { //FIXME //this.emitStoreToMemory(byteOffset, value.symbol.resolvedType, value.dotTarget(), value.symbol.offset, node); } } } } else { let isUnsigned = node.isUnsignedOperator(); let left = node.binaryLeft(); let right = node.binaryRight(); let isFloat: boolean = left.resolvedType.isFloat() || right.resolvedType.isFloat(); let isDouble: boolean = left.resolvedType.isDouble() || right.resolvedType.isDouble(); let dataTypeLeft: string = typeToDataType(left.resolvedType, this.bitness); let dataTypeRight: string = typeToDataType(right.resolvedType, this.bitness); if (node.kind == NodeKind.ADD) { this.emitNode(byteOffset, left); if (left.resolvedType.pointerTo == null) { this.emitNode(byteOffset, right); } // Need to multiply the right by the size of the pointer target else { assert( right.resolvedType.isInteger() || right.resolvedType.isLong() || right.resolvedType.isFloat() || right.resolvedType.isDouble() ); let size = left.resolvedType.pointerTo.allocationSizeOf(this.context); if (size == 2) { if (right.kind == NodeKind.INT32) { let _value = right.intValue << 1; this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, _value); this.assembler.writeLEB128(_value); } else { this.emitNode(byteOffset, right); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, 1); this.assembler.writeLEB128(1); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_SHL); } } else if (size == 4) { if (right.kind == NodeKind.INT32) { let _value = right.intValue << 2; this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, _value); this.assembler.writeLEB128(_value); } else if (right.kind == NodeKind.FLOAT32) { this.assembler.appendOpcode(byteOffset, WasmOpcode.F32_CONST, right.floatValue); this.assembler.writeFloat(right.floatValue); } else { this.emitNode(byteOffset, right); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_CONST, 2); this.assembler.writeLEB128(2); this.assembler.appendOpcode(byteOffset, WasmOpcode.I32_SHL); } } else if (size == 8) { if (right.kind == NodeKind.INT64) { this.assembler.appendOpcode(byteOffset, WasmOpcode.I64_CONST, right.longValue); this.assembler.writeLEB128(right.longValue); } else if (right.kind == NodeKind.FLOAT64) { this.assembler.appendOpcode(byteOffset, WasmOpcode.F64_CONST, right.doubleValue); this.assembler.writeDouble(right.doubleValue); } } else { this.emitNode(byteOffset, right); } } this.assembler.appendOpcode(byteOffset, WasmOpcode[`${dataTypeLeft}_ADD`]); } else if (node.kind == NodeKind.BITWISE_AND) { if (isFloat || isDouble) { let error = "Cannot do bitwise operations on floating point number" Terminal.error(error); throw error; } this.emitBinaryExpression(byteOffset, node, WasmOpcode[`${dataTypeLeft}_AND`]); } else if (node.kind == NodeKind.BITWISE_OR) { if (isFloat || isDouble) { let error = "Cannot do bitwise operations on floating point number"; Terminal.error(error); throw error; } this.emitBinaryExpression(byteOffset, node, WasmOpcode[`${dataTypeLeft}_OR`]); } else if (node.kind == NodeKind.BITWISE_XOR) { this.emitBinaryExpression(byteOffset, node, WasmOpcode[`${dataTypeLeft}_XOR`]); } else if (node.kind == NodeKind.EQUAL) { this.emitBinaryExpression(byteOffset, node, WasmOpcode[`${dataTypeLeft}_EQ`]); } else if (node.kind == NodeKind.MULTIPLY) { this.emitBinaryExpression(byteOffset, node, WasmOpcode[`${dataTypeLeft}_MUL`]); } else if (node.kind == NodeKind.NOT_EQUAL) { this.emitBinaryExpression(byteOffset, node, WasmOpcode[`${dataTypeLeft}_NE`]); } else if (node.kind == NodeKind.SHIFT_LEFT) { if (isFloat || isDouble) { let error = "Cannot do bitwise operations on floating point number"; Terminal.error(error); throw error; } this.emitBinaryExpression(byteOffset, node, WasmOpcode[`${dataTypeLeft}_SHL`]); } else if (node.kind == NodeKind.SUBTRACT) { this.emitBinaryExpression(byteOffset, node, WasmOpcode[`${dataTypeLeft}_SUB`]); } else if (node.kind == NodeKind.DIVIDE) { let opcode = (isFloat || isDouble) ? WasmOpcode[`${dataTypeLeft}_DIV`] : (isUnsigned ? WasmOpcode[`${dataTypeLeft}_DIV_U`] : WasmOpcode[`${dataTypeLeft}_DIV_S`]); this.emitBinaryExpression(byteOffset, node, opcode); } else if (node.kind == NodeKind.GREATER_THAN) { let opcode = (isFloat || isDouble) ? WasmOpcode[`${dataTypeLeft}_GT`] : (isUnsigned ? WasmOpcode[`${dataTypeLeft}_GT_U`] : WasmOpcode[`${dataTypeLeft}_GT_S`]); this.emitBinaryExpression(byteOffset, node, opcode); } else if (node.kind == NodeKind.GREATER_THAN_EQUAL) { let opcode = (isFloat || isDouble) ? WasmOpcode[`${dataTypeLeft}_GE`] : (isUnsigned ? WasmOpcode[`${dataTypeLeft}_GE_U`] : WasmOpcode[`${dataTypeLeft}_GE_S`]); this.emitBinaryExpression(byteOffset, node, opcode); } else if (node.kind == NodeKind.LESS_THAN) { let opcode = (isFloat || isDouble) ? WasmOpcode[`${dataTypeLeft}_LT`] : (isUnsigned ? WasmOpcode[`${dataTypeLeft}_LT_U`] : WasmOpcode[`${dataTypeLeft}_LT_S`]); this.emitBinaryExpression(byteOffset, node, opcode); } else if (node.kind == NodeKind.LESS_THAN_EQUAL) { let opcode = (isFloat || isDouble) ? WasmOpcode[`${dataTypeLeft}_LE`] : (isUnsigned ? WasmOpcode[`${dataTypeLeft}_LE_U`] : WasmOpcode[`${dataTypeLeft}_LE_S`]); this.emitBinaryExpression(byteOffset, node, opcode); } else if (node.kind == NodeKind.REMAINDER) { if (isFloat || isDouble) { let error = "Floating point remainder is not yet supported in WebAssembly. Please import javascript function to handle this"; Terminal.error(error); throw error; } this.emitBinaryExpression(byteOffset, node, isUnsigned ? WasmOpcode[`${dataTypeLeft}_REM_U`] : WasmOpcode[`${dataTypeLeft}_REM_S`]); } else if (node.kind == NodeKind.SHIFT_RIGHT) { if (isFloat || isDouble) { let error = "Cannot do bitwise operations on floating point number"; Terminal.error(error); throw error; } this.emitBinaryExpression(byteOffset, node, isUnsigned ? WasmOpcode[`${dataTypeLeft}_SHR_U`] : WasmOpcode[`${dataTypeLeft}_SHR_S`]); } else { assert(false); } } return 1; } calculateWasmFunctionIndex(index: int32): int32 { return this.assembler.module.importCount + index; } getWasmFunctionCallIndex(symbol: Symbol): int32 { return (symbol.node.isImport() || symbol.node.isExternalImport()) ? symbol.offset : this.assembler.module.importCount + symbol.offset; } getWasmType(type: Type): WasmType { let context = this.context; if (type == context.booleanType || type.isClass() || type.isInteger() || (this.bitness == Bitness.x32 && type.isReference())) { return WasmType.I32; } else if (type.isLong() || (this.bitness == Bitness.x64 && type.isReference())) { return WasmType.I64; } else if (type.isDouble()) { return WasmType.F64; } else if (type.isFloat()) { return WasmType.F32; } if (type == context.voidType) { return WasmType.VOID; } assert(false); return WasmType.VOID; } } function wasmAssignLocalVariableOffsets(fn: WasmFunction, node: Node, shared: WasmSharedOffset, bitness: Bitness): void { if (node.kind == NodeKind.VARIABLE) { assert(node.symbol.kind == SymbolKind.VARIABLE_LOCAL); // node.symbol.offset = shared.nextLocalOffset; shared.nextLocalOffset = shared.nextLocalOffset + 1; shared.localCount = shared.localCount + 1; let local = new WasmLocal( symbolToWasmType(node.symbol, bitness), node.symbol.internalName, node.symbol, false ); node.symbol.offset = fn.argumentVariables.length + fn.localVariables.length; fn.localVariables.push(new WasmLocal(local.type, local.symbol.name)); } let child = node.firstChild; while (child != null) { wasmAssignLocalVariableOffsets(fn, child, shared, bitness); child = child.nextSibling; } } export function wasmEmit(compiler: Compiler, bitness: Bitness = Bitness.x32, optimize: boolean = true): void { let wasmEmitter = new WasmModuleEmitter(bitness); wasmEmitter.context = compiler.context; wasmEmitter.memoryInitializer = new ByteArray(); if (Compiler.mallocRequired) { // Merge imported malloc.wasm binaries wasmEmitter.mergeBinaryImports(); } // Emission requires two passes wasmEmitter.prepareToEmit(compiler.global); wasmEmitter.assembler.sealFunctions(); compiler.outputWASM = wasmEmitter.assembler.module.binary.data; wasmEmitter.emitModule(); if (optimize) { WasmOptimizer.optimize(compiler.outputWASM) } compiler.outputWAST = wasmEmitter.assembler.module.text; }