diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b26d4ad989..d37fc1ffc9 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,8 +30,6 @@ jobs: npm run build - name: Test distribution files run: npm test - - name: Reconfigure for release - run: npm run release - name: Set up version run: | VERSION=$(node -e "console.log(require('./package.json').version)") diff --git a/.gitignore b/.gitignore index dc54075dfb..cb7d0b9ef8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ node_modules/ out/ raw/ .history +*.backup diff --git a/cli/asc.js b/cli/asc.js index 8b64c4352f..ab852e1c0e 100644 --- a/cli/asc.js +++ b/cli/asc.js @@ -600,34 +600,143 @@ exports.main = function main(argv, options, callback) { module.setShrinkLevel(shrinkLevel); module.setDebugInfo(args.debug); - var runPasses = []; + const runPasses = []; if (args.runPasses) { if (typeof args.runPasses === "string") { args.runPasses = args.runPasses.split(","); } if (args.runPasses.length) { args.runPasses.forEach(pass => { - if (runPasses.indexOf(pass) < 0) + if (runPasses.indexOf(pass = pass.trim()) < 0) runPasses.push(pass); }); } } - // Optimize the module if requested - if (optimizeLevel > 0 || shrinkLevel > 0) { - stats.optimizeCount++; - stats.optimizeTime += measure(() => { - module.optimize(); - }); + function doOptimize() { + const hasARC = args.runtime == "half" || args.runtime == "full"; + const passes = []; + function add(pass) { passes.push(pass); } + + // Optimize the module if requested + if (optimizeLevel > 0 || shrinkLevel > 0) { + // Binaryen's default passes with Post-AssemblyScript passes added. + // see: Binaryen/src/pass.cpp + + // PassRunner::addDefaultGlobalOptimizationPrePasses + add("duplicate-function-elimination"); + + // PassRunner::addDefaultFunctionOptimizationPasses + if (optimizeLevel >= 3 || shrinkLevel >= 1) { + add("ssa-nomerge"); + } + if (optimizeLevel >= 4) { + add("flatten"); + add("local-cse"); + } + // if (hasARC) { // differs + // if (optimizeLevel < 4) { + // add("flatten"); + // } + // add("post-assemblyscript"); + // } + add("dce"); + add("remove-unused-brs"); + add("remove-unused-names"); + add("optimize-instructions"); + if (optimizeLevel >= 2 || shrinkLevel >= 2) { + add("pick-load-signs"); + } + if (optimizeLevel >= 3 || shrinkLevel >= 2) { + add("precompute-propagate"); + } else { + add("precompute"); + } + if (optimizeLevel >= 2 || shrinkLevel >= 2) { + add("code-pushing"); + } + add("simplify-locals-nostructure"); + add("vacuum"); + add("reorder-locals"); + add("remove-unused-brs"); + if (optimizeLevel >= 3 || shrinkLevel >= 2) { + add("merge-locals"); + } + add("coalesce-locals"); + add("simplify-locals"); + add("vacuum"); + add("reorder-locals"); + add("coalesce-locals"); + add("reorder-locals"); + add("vacuum"); + if (optimizeLevel >= 3 || shrinkLevel >= 1) { + add("code-folding"); + } + add("merge-blocks"); + add("remove-unused-brs"); + add("remove-unused-names"); + add("merge-blocks"); + if (optimizeLevel >= 3 || shrinkLevel >= 2) { + add("precompute-propagate"); + } else { + add("precompute"); + } + add("optimize-instructions"); + if (optimizeLevel >= 2 || shrinkLevel >= 1) { + add("rse"); + } + // if (hasARC) { // differs + // add("post-assemblyscript-finalize"); + // } + add("vacuum"); + + // PassRunner::addDefaultGlobalOptimizationPostPasses + if (optimizeLevel >= 2 || shrinkLevel >= 1) { + add("dae-optimizing"); + } + if (optimizeLevel >= 2 || shrinkLevel >= 2) { + add("inlining-optimizing"); + } + add("duplicate-function-elimination"); + add("duplicate-import-elimination"); + if (optimizeLevel >= 2 || shrinkLevel >= 2) { + add("simplify-globals-optimizing"); + } else { + add("simplify-globals"); + } + add("remove-unused-module-elements"); + add("memory-packing"); + add("directize"); + add("inlining-optimizing"); // differs + if (optimizeLevel >= 2 || shrinkLevel >= 1) { + add("generate-stack-ir"); + add("optimize-stack-ir"); + } + } + + // Append additional passes if requested and execute + module.runPasses(passes.concat(runPasses)); } - // Run additional passes if requested - if (runPasses.length) { + stats.optimizeTime += measure(() => { stats.optimizeCount++; - stats.optimizeTime += measure(() => { - module.runPasses(runPasses.map(pass => pass.trim())); - }); - } + doOptimize(); + if (args.converge) { + let last = module.toBinary(); + do { + stats.optimizeCount++; + doOptimize(); + let next = module.toBinary(); + if (next.output.length >= last.output.length) { + if (next.output.length > last.output.length) { + stderr.write("Last converge was suboptimial." + EOL); + } + break; + } + last = next; + } while (true); + } + }); // Prepare output if (!args.noEmit) { diff --git a/cli/asc.json b/cli/asc.json index e3d909ef3e..bb5b2e1134 100644 --- a/cli/asc.json +++ b/cli/asc.json @@ -33,6 +33,11 @@ "description": "How much to focus on shrinking code size. [0-2, s=1, z=2]", "type": "i" }, + "converge": { + "description": "Re-optimizes until no further improvements can be made.", + "type": "b", + "default": false + }, "validate": { "description": "Validates the module using Binaryen. Exits if invalid.", "type": "b", diff --git a/package-lock.json b/package-lock.json index f2ab0bc590..a534c88894 100644 --- a/package-lock.json +++ b/package-lock.json @@ -567,9 +567,9 @@ "dev": true }, "binaryen": { - "version": "89.0.0-nightly.20191113", - "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-89.0.0-nightly.20191113.tgz", - "integrity": "sha512-scODswmj6Xf/Adjy1PRsjf8fHxpIIUYpH7HjKUaZ/RdZYjqB1Zq3X4izpLfcK+hKGNah92DpAJ3i/adEayMe9g==" + "version": "89.0.0-nightly.20191116", + "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-89.0.0-nightly.20191116.tgz", + "integrity": "sha512-UJ0WGQbRX4yzyXnk0Ti7PU7YTHPsiGoLroGT8qtxw6UATpmJmiNOjLw4tBLjXxGeg3Kl1MWv+Fe6eCGe7gPHkg==" }, "bluebird": { "version": "3.7.1", diff --git a/package.json b/package.json index 88dadf83b9..636022145d 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "url": "https://github.com/AssemblyScript/assemblyscript/issues" }, "dependencies": { - "binaryen": "89.0.0-nightly.20191113", + "binaryen": "89.0.0-nightly.20191116", "long": "^4.0.0", "source-map-support": "^0.5.16", "ts-node": "^6.2.0", @@ -63,7 +63,8 @@ "make": "npm run clean && npm test && npm run build && npm test", "all": "npm run check && npm run make", "docs": "typedoc --tsconfig tsconfig-docs.json --mode modules --name \"AssemblyScript Compiler API\" --out ./docs/api --ignoreCompilerErrors --excludeNotExported --excludePrivate --excludeExternals --exclude **/std/** --includeDeclarations --readme src/README.md", - "release": "node scripts/release" + "prepublishOnly": "node scripts/prepublish", + "postpublish": "node scripts/postpublish" }, "releaseFiles": [ "lib/rtrace/index.d.ts", diff --git a/scripts/postpublish-files.json b/scripts/postpublish-files.json new file mode 100644 index 0000000000..c2a9b27bf0 --- /dev/null +++ b/scripts/postpublish-files.json @@ -0,0 +1,5 @@ +[ + "package.json", + "index.js", + "index.d.ts" +] diff --git a/scripts/postpublish.js b/scripts/postpublish.js new file mode 100644 index 0000000000..4c0ddb5fe9 --- /dev/null +++ b/scripts/postpublish.js @@ -0,0 +1,22 @@ +// Reconfigures the repository after publishing + +const fs = require("fs"); +const path = require("path"); +const devFiles = require("./postpublish-files.json"); + +console.log("Restoring development files ..."); + +devFiles.forEach(originalName => { + const backupName = originalName + ".backup"; + const backupPath = path.join(__dirname, "..", backupName); + if (!fs.existsSync(backupPath)) { + console.log("- " + backupName + " does not exist"); + } else { + console.log("- " + backupName + " -> " + originalName); + fs.copyFileSync( + backupPath, + path.join(__dirname, "..", originalName) + ); + fs.unlinkSync(backupPath); + } +}); diff --git a/scripts/release.js b/scripts/prepublish.js similarity index 75% rename from scripts/release.js rename to scripts/prepublish.js index d265a81eed..d18b7c373f 100644 --- a/scripts/release.js +++ b/scripts/prepublish.js @@ -1,8 +1,25 @@ -// Reconfigures the repository before publishing a release +// Reconfigures the repository before publishing const fs = require("fs"); const path = require("path"); const pkg = require("../package.json"); +const devFiles = require("./postpublish-files.json"); + +if (!pkg.releaseFiles) { + console.log("Package has already been updated"); + return; +} + +console.log("Backing up development files ..."); + +devFiles.forEach(originalName => { + const backupName = originalName + ".backup"; + console.log("- " + originalName + " -> " + backupName); + fs.copyFileSync( + path.join(__dirname, "..", originalName), + path.join(__dirname, "..", backupName) + ); +}); console.log("Updating package.json ..."); diff --git a/src/builtins.ts b/src/builtins.ts index 0578325ec5..0290b16c6c 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -905,6 +905,7 @@ export function compileCall( case TypeKind.USIZE: { value = "usize"; break; } case TypeKind.V128: { value = "v128"; break; } case TypeKind.ANYREF: { value = "anyref"; break; } + case TypeKind.EXNREF: { value = "exnref"; break; } default: assert(false); case TypeKind.VOID: { value = "void"; break; } } @@ -4788,7 +4789,7 @@ export function compileAbort( // essentially ignoring the message GC-wise. Doesn't matter anyway on a crash. messageArg = compiler.compileExpression(message, stringInstance.type, Constraints.CONV_IMPLICIT | Constraints.WILL_RETAIN); } else { - messageArg = stringInstance.type.toNativeZero(module); + messageArg = compiler.makeZero(stringInstance.type); } var filenameArg = compiler.ensureStaticString(reportNode.range.source.normalizedPath); diff --git a/src/common.ts b/src/common.ts index 4c8106a25a..734e1ad622 100644 --- a/src/common.ts +++ b/src/common.ts @@ -198,6 +198,7 @@ export namespace CommonSymbols { export const Uint64Array = "Uint64Array"; export const Float32Array = "Float32Array"; export const Float64Array = "Float64Array"; + export const Error = "Error"; // runtime export const abort = "abort"; export const pow = "pow"; diff --git a/src/compiler.ts b/src/compiler.ts index 161bd21b8f..863c5ca940 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -28,6 +28,8 @@ import { ExpressionId, FunctionTypeRef, GlobalRef, + EventRef, + FeatureFlags, getExpressionId, getExpressionType, getConstValueI32, @@ -41,9 +43,10 @@ import { getLocalGetIndex, isLocalTee, getLocalSetIndex, - FeatureFlags, needsExplicitUnreachable, - getLocalSetValue + getLocalSetValue, + getGlobalGetName, + isGlobalMutable } from "./module"; import { @@ -307,6 +310,8 @@ export class Compiler extends DiagnosticEmitter { runtimeFeatures: RuntimeFeatures = RuntimeFeatures.NONE; /** Expressions known to have skipped an autorelease. Usually function returns. */ skippedAutoreleases: Set = new Set(); + /** Registered event types. */ + events: Map = new Map(); /** Compiles a {@link Program} to a {@link Module} using the specified options. */ static compile(program: Program, options: Options | null = null): Module { @@ -885,8 +890,8 @@ export class Compiler extends DiagnosticEmitter { return false; } - // The MVP does not yet support initializer expressions other than constant values (and constant - // get_globals), hence such initializations must be performed in the start function for now. + // The MVP does not yet support initializer expressions other than constants and gets of + // imported immutable globals, hence such initializations must be performed in the start. var initializeInStart = false; // Evaluate initializer if present @@ -903,6 +908,7 @@ export class Compiler extends DiagnosticEmitter { this.currentFlow = previousFlow; } + // If not a constant, attempt to precompute if (getExpressionId(initExpr) != ExpressionId.Const) { if (isDeclaredConstant) { initExpr = module.precomputeExpression(initExpr); @@ -912,6 +918,18 @@ export class Compiler extends DiagnosticEmitter { } } + // Handle special case of initializing from imported immutable global + if (initializeInStart && getExpressionId(initExpr) == ExpressionId.GlobalGet) { + let fromName = assert(getGlobalGetName(initExpr)); + if (!isGlobalMutable(module.getGlobal(fromName))) { + let elementsByName = this.program.elementsByName; + if (elementsByName.has(fromName)) { + let global = elementsByName.get(fromName)!; + if (global.is(CommonFlags.AMBIENT)) initializeInStart = false; + } + } + } + // Explicitly inline if annotated if (isDeclaredInline) { if (initializeInStart) { @@ -957,7 +975,7 @@ export class Compiler extends DiagnosticEmitter { // Initialize to zero if there's no initializer } else { - initExpr = type.toNativeZero(module); + initExpr = this.makeZero(type); } var internalName = global.internalName; @@ -969,7 +987,7 @@ export class Compiler extends DiagnosticEmitter { assert(findDecorator(DecoratorKind.INLINE, global.decoratorNodes)).range, "inline" ); } - module.addGlobal(internalName, nativeType, true, type.toNativeZero(module)); + module.addGlobal(internalName, nativeType, true, this.makeZero(type)); if (type.isManaged && !initAutoreleaseSkipped) initExpr = this.makeRetain(initExpr); this.currentBody.push( module.global_set(internalName, initExpr) @@ -1124,6 +1142,20 @@ export class Compiler extends DiagnosticEmitter { return typeRef; } + /** Either reuses or creates the event type matching the specified name. */ + ensureEventType( + name: string, + parameterTypes: Type[] | null + ): EventRef { + var events = this.events; + if (events.has(name)) return events.get(name)!; + var module = this.module; + var funcType = this.ensureFunctionType(parameterTypes, Type.void); + var eventType = module.addEvent(name, 0, funcType); + events.set(name, eventType); + return eventType; + } + /** Compiles the body of a function within the specified flow. */ compileFunctionBody( /** Function to compile. */ @@ -2657,7 +2689,7 @@ export class Compiler extends DiagnosticEmitter { // TODO: Detect this condition inside of a loop instead? initializers.push( module.local_set(local.index, - type.toNativeZero(module) + this.makeZero(type) ) ); flow.setLocalFlag(local.index, LocalFlags.CONDITIONALLY_RETAINED); @@ -6773,7 +6805,7 @@ export class Compiler extends DiagnosticEmitter { } } } - operands.push(parameterTypes[i].toNativeZero(module)); + operands.push(this.makeZero(parameterTypes[i])); allOptionalsAreConstant = false; } if (!allOptionalsAreConstant) { @@ -6887,7 +6919,7 @@ export class Compiler extends DiagnosticEmitter { } let parameterTypes = signature.parameterTypes; for (let i = numArguments; i < maxArguments; ++i) { - operands.push(parameterTypes[i].toNativeZero(module)); + operands.push(this.makeZero(parameterTypes[i])); } } @@ -7364,7 +7396,7 @@ export class Compiler extends DiagnosticEmitter { ? BinaryOp.NeI64 : BinaryOp.NeI32, expr, - actualType.toNativeZero(module) + this.makeZero(actualType) ); } @@ -7562,7 +7594,7 @@ export class Compiler extends DiagnosticEmitter { Constraints.CONV_IMPLICIT ) ) - : elementType.toNativeZero(module); + : this.makeZero(elementType); if (getExpressionId(expr) == ExpressionId.Const) { assert(getExpressionType(expr) == nativeElementType); } else { @@ -7937,7 +7969,7 @@ export class Compiler extends DiagnosticEmitter { ctor, argumentExpressions, reportNode, - this.options.usizeType.toNativeZero(this.module), + this.makeZero(this.options.usizeType), constraints ); if (getExpressionType(expr) != NativeType.None) { // possibly IMM_DROPPED @@ -8225,7 +8257,7 @@ export class Compiler extends DiagnosticEmitter { ? BinaryOp.AddI64 : BinaryOp.AddI32, getValue, - this.currentType.toNativeOne(module) + this.makeOne(this.currentType) ); break; } @@ -8314,7 +8346,7 @@ export class Compiler extends DiagnosticEmitter { ? BinaryOp.SubI64 : BinaryOp.SubI32, getValue, - this.currentType.toNativeOne(module) + this.makeOne(this.currentType) ); break; } @@ -8481,7 +8513,7 @@ export class Compiler extends DiagnosticEmitter { this.options.isWasm64 ? BinaryOp.SubI64 : BinaryOp.SubI32, - this.currentType.toNativeZero(module), + this.makeZero(this.currentType), expr ); break; @@ -8553,7 +8585,7 @@ export class Compiler extends DiagnosticEmitter { ? BinaryOp.AddI64 : BinaryOp.AddI32, expr, - this.currentType.toNativeOne(module) + this.makeOne(this.currentType) ); break; } @@ -8624,7 +8656,7 @@ export class Compiler extends DiagnosticEmitter { ? BinaryOp.SubI64 : BinaryOp.SubI32, expr, - this.currentType.toNativeOne(module) + this.makeOne(this.currentType) ); break; } @@ -8721,7 +8753,7 @@ export class Compiler extends DiagnosticEmitter { ? BinaryOp.XorI64 : BinaryOp.XorI32, expr, - this.currentType.toNativeNegOne(module) + this.makeNegOne(this.currentType) ); break; } @@ -8920,6 +8952,69 @@ export class Compiler extends DiagnosticEmitter { // === Specialized code generation ============================================================== + /** Makes a constant zero of the specified type. */ + makeZero(type: Type): ExpressionRef { + var module = this.module; + switch (type.kind) { + default: assert(false); + case TypeKind.I8: + case TypeKind.I16: + case TypeKind.I32: + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.U32: + case TypeKind.BOOL: return module.i32(0); + case TypeKind.ISIZE: + case TypeKind.USIZE: if (type.size != 64) return module.i32(0); + case TypeKind.I64: + case TypeKind.U64: return module.i64(0); + case TypeKind.F32: return module.f32(0); + case TypeKind.F64: return module.f64(0); + case TypeKind.V128: return module.v128(v128_zero); + } + } + + /** Makes a constant one of the specified type. */ + makeOne(type: Type): ExpressionRef { + var module = this.module; + switch (type.kind) { + default: assert(false); + case TypeKind.I8: + case TypeKind.I16: + case TypeKind.I32: + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.U32: + case TypeKind.BOOL: return module.i32(1); + case TypeKind.ISIZE: + case TypeKind.USIZE: if (type.size != 64) return module.i32(1); + case TypeKind.I64: + case TypeKind.U64: return module.i64(1); + case TypeKind.F32: return module.f32(1); + case TypeKind.F64: return module.f64(1); + } + } + + /** Makes a constant negative one of the specified type. */ + makeNegOne(type: Type): ExpressionRef { + var module = this.module; + switch (type.kind) { + default: assert(false); + case TypeKind.I8: + case TypeKind.I16: + case TypeKind.I32: + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.U32: return module.i32(-1); + case TypeKind.ISIZE: + case TypeKind.USIZE: if (type.size != 64) return module.i32(-1); + case TypeKind.I64: + case TypeKind.U64: return module.i64(-1, -1); + case TypeKind.F32: return module.f32(-1); + case TypeKind.F64: return module.f64(-1); + } + } + /** Creates a comparison whether an expression is 'true' in a broader sense. */ makeIsTrueish(expr: ExpressionRef, type: Type): ExpressionRef { var module = this.module; @@ -9053,7 +9148,7 @@ export class Compiler extends DiagnosticEmitter { ); if (fieldType.isManaged) initExpr = this.makeRetain(initExpr); } else { // initialize with zero - initExpr = fieldType.toNativeZero(module); + initExpr = this.makeZero(fieldType); } stmts.push( module.store(fieldType.byteSize, @@ -9108,6 +9203,8 @@ export class Compiler extends DiagnosticEmitter { // helpers +const v128_zero = new Uint8Array(16); + function mangleImportName( element: Element, declaration: DeclarationStatement diff --git a/src/flow.ts b/src/flow.ts index df14c84452..396554a7a5 100644 --- a/src/flow.ts +++ b/src/flow.ts @@ -325,6 +325,7 @@ export class Flow { case NativeType.F64: { temps = parentFunction.tempF64s; break; } case NativeType.V128: { temps = parentFunction.tempV128s; break; } case NativeType.Anyref: { temps = parentFunction.tempAnyrefs; break; } + case NativeType.Exnref: { temps = parentFunction.tempExnrefs; break; } default: throw new Error("concrete type expected"); } var local: Local; @@ -375,7 +376,7 @@ export class Flow { var parentFunction = this.parentFunction; var temps: Local[]; assert(local.type != null); // internal error - switch ((local.type).toNativeType()) { + switch (local.type.toNativeType()) { case NativeType.I32: { temps = parentFunction.tempI32s || (parentFunction.tempI32s = []); break; @@ -400,6 +401,10 @@ export class Flow { temps = parentFunction.tempAnyrefs || (parentFunction.tempAnyrefs = []); break; } + case NativeType.Exnref: { + temps = parentFunction.tempExnrefs || (parentFunction.tempExnrefs = []); + break; + } default: throw new Error("concrete type expected"); } assert(local.index >= 0); diff --git a/src/glue/binaryen.d.ts b/src/glue/binaryen.d.ts index 5f5b0befcb..9c4d32bdfc 100644 --- a/src/glue/binaryen.d.ts +++ b/src/glue/binaryen.d.ts @@ -700,8 +700,14 @@ declare function _BinaryenGetExportByIndex(module: BinaryenModuleRef, index: Bin declare type BinaryenGlobalRef = usize; declare function _BinaryenAddGlobal(module: BinaryenModuleRef, name: usize, type: BinaryenType, mutable: bool, init: BinaryenExpressionRef): BinaryenGlobalRef; +declare function _BinaryenGetGlobal(module: BinaryenModuleRef, name: usize): BinaryenGlobalRef; declare function _BinaryenRemoveGlobal(module: BinaryenModuleRef, name: usize): void; +declare function _BinaryenGlobalGetName(global: BinaryenGlobalRef): usize; +declare function _BinaryenGlobalGetType(global: BinaryenGlobalRef): BinaryenType; +declare function _BinaryenGlobalIsMutable(global: BinaryenGlobalRef): bool; +declare function _BinaryenGlobalGetInit(global: BinaryenGlobalRef): BinaryenExpressionRef; + declare type BinaryenEventRef = usize; declare function _BinaryenAddEvent(module: BinaryenModuleRef, name: usize, attribute: u32, type: BinaryenFunctionTypeRef): BinaryenEventRef; diff --git a/src/module.ts b/src/module.ts index 00e9e73d56..0fe47d92f0 100644 --- a/src/module.ts +++ b/src/module.ts @@ -487,39 +487,6 @@ export class Module { private constructor() { } - // types - - addFunctionType( - name: string, - result: NativeType, - paramTypes: NativeType[] | null - ): FunctionRef { - var cStr = this.allocStringCached(name); - var cArr = allocI32Array(paramTypes); - try { - return _BinaryenAddFunctionType(this.ref, cStr, result, cArr, paramTypes ? paramTypes.length : 0); - } finally { - memory.free(cArr); - } - } - - getFunctionTypeBySignature( - result: NativeType, - paramTypes: NativeType[] | null - ): FunctionTypeRef { - var cArr = allocI32Array(paramTypes); - try { - return _BinaryenGetFunctionTypeBySignature(this.ref, result, cArr, paramTypes ? paramTypes.length : 0); - } finally { - memory.free(cArr); - } - } - - removeFunctionType(name: string): void { - var cStr = this.allocStringCached(name); - _BinaryenRemoveFunctionType(this.ref, cStr); - } - // constants i32(value: i32): ExpressionRef { @@ -974,7 +941,40 @@ export class Module { return _BinaryenSIMDLoad(this.ref, op, offset, align, ptr); } - // meta + // function types + + addFunctionType( + name: string, + result: NativeType, + paramTypes: NativeType[] | null + ): FunctionTypeRef { + var cStr = this.allocStringCached(name); + var cArr = allocI32Array(paramTypes); + try { + return _BinaryenAddFunctionType(this.ref, cStr, result, cArr, paramTypes ? paramTypes.length : 0); + } finally { + memory.free(cArr); + } + } + + getFunctionTypeBySignature( + result: NativeType, + paramTypes: NativeType[] | null + ): FunctionTypeRef { + var cArr = allocI32Array(paramTypes); + try { + return _BinaryenGetFunctionTypeBySignature(this.ref, result, cArr, paramTypes ? paramTypes.length : 0); + } finally { + memory.free(cArr); + } + } + + removeFunctionType(name: string): void { + var cStr = this.allocStringCached(name); + _BinaryenRemoveFunctionType(this.ref, cStr); + } + + // globals addGlobal( name: string, @@ -986,6 +986,13 @@ export class Module { return _BinaryenAddGlobal(this.ref, cStr, type, mutable, initializer); } + getGlobal( + name: string + ): GlobalRef { + var cStr = this.allocStringCached(name); + return _BinaryenGetGlobal(this.ref, cStr); + } + removeGlobal( name: string ): void { @@ -993,15 +1000,33 @@ export class Module { _BinaryenRemoveGlobal(this.ref, cStr); } + // events + addEvent( name: string, attribute: u32, - type: FunctionRef + type: FunctionTypeRef ): EventRef { var cStr = this.allocStringCached(name); return _BinaryenAddEvent(this.ref, cStr, attribute, type); } + getEvent( + name: string + ): EventRef { + var cStr = this.allocStringCached(name); + return _BinaryenGetEvent(this.ref, cStr); + } + + removeEvent( + name: string + ): void { + var cStr = this.allocStringCached(name); + _BinaryenRemoveEvent(this.ref, cStr); + } + + // functions + addFunction( name: string, type: FunctionTypeRef, @@ -1050,6 +1075,12 @@ export class Module { _BinaryenRemoveFunctionType(this.ref, tempName); } + setStart(func: FunctionRef): void { + _BinaryenSetStart(this.ref, func); + } + + // exports + addFunctionExport( internalName: string, externalName: string @@ -1100,6 +1131,8 @@ export class Module { _BinaryenRemoveExport(this.ref, cStr); } + // imports + addFunctionImport( internalName: string, externalModuleName: string, @@ -1161,6 +1194,8 @@ export class Module { _BinaryenAddEventImport(this.ref, cStr1, cStr2, cStr3, attribute, eventType); } + // memory + /** Unlimited memory constant. */ static readonly UNLIMITED_MEMORY: Index = -1; @@ -1203,6 +1238,8 @@ export class Module { } } + // table + setFunctionTable( initial: Index, maximum: Index, @@ -1222,9 +1259,7 @@ export class Module { } } - setStart(func: FunctionRef): void { - _BinaryenSetStart(this.ref, func); - } + // sections addCustomSection(name: string, contents: Uint8Array): void { var cStr = this.allocStringCached(name); @@ -1236,6 +1271,8 @@ export class Module { } } + // meta + getOptimizeLevel(): i32 { return _BinaryenGetOptimizeLevel(); } @@ -1700,6 +1737,24 @@ export function getHostName(expr: ExpressionRef): string | null { return readString(_BinaryenHostGetNameOperand(expr)); } +// function types + +export function getFunctionTypeName(ftype: FunctionTypeRef): string | null { + return readString(_BinaryenFunctionTypeGetName(ftype)); +} + +export function getFunctionTypeParamCount(ftype: FunctionTypeRef): Index { + return _BinaryenFunctionTypeGetNumParams(ftype); +} + +export function getFunctionTypeParam(ftype: FunctionTypeRef, index: Index): NativeType { + return _BinaryenFunctionTypeGetParam(ftype, index); +} + +export function getFunctionTypeResult(ftype: FunctionTypeRef): NativeType { + return _BinaryenFunctionTypeGetResult(ftype); +} + // functions export function getFunctionBody(func: FunctionRef): ExpressionRef { @@ -1722,6 +1777,46 @@ export function getFunctionResultType(func: FunctionRef): NativeType { return _BinaryenFunctionGetResult(func); } +// globals + +export function getGlobalName(global: GlobalRef): string | null { + return readString(_BinaryenGlobalGetName(global)); +} + +export function getGlobalType(global: GlobalRef): NativeType { + return _BinaryenGlobalGetType(global); +} + +export function isGlobalMutable(global: GlobalRef): bool { + return _BinaryenGlobalIsMutable(global); +} + +export function getGlobalInit(global: GlobalRef): ExpressionRef { + return _BinaryenGlobalGetInit(global); +} + +// events + +export function getEventName(event: EventRef): string | null { + return readString(_BinaryenEventGetName(event)); +} + +export function getEventAttribute(event: EventRef): u32 { + return _BinaryenEventGetAttribute(event); +} + +export function getEventType(event: EventRef): string | null { + return readString(_BinaryenEventGetType(event)); +} + +export function getEventParamCount(event: EventRef): Index { + return _BinaryenEventGetNumParams(event); +} + +export function getEventParam(event: EventRef, index: Index): NativeType { + return _BinaryenEventGetParam(event, index); +} + export class Relooper { module: Module; diff --git a/src/program.ts b/src/program.ts index 190fd53f04..fdda734d7f 100644 --- a/src/program.ts +++ b/src/program.ts @@ -1317,17 +1317,20 @@ export class Program extends DiagnosticEmitter { var name = declaration.name.text; var decorators = declaration.decorators; var element: DeclaredElement; + var acceptedFlags: DecoratorFlags = DecoratorFlags.UNSAFE; + if (parent.is(CommonFlags.AMBIENT)) { + acceptedFlags |= DecoratorFlags.EXTERNAL; + } if (declaration.is(CommonFlags.STATIC)) { // global variable assert(parent.kind != ElementKind.INTERFACE_PROTOTYPE); + acceptedFlags |= DecoratorFlags.LAZY; + if (declaration.is(CommonFlags.READONLY)) { + acceptedFlags |= DecoratorFlags.INLINE; + } element = new Global( name, parent, - this.checkDecorators(decorators, - (declaration.is(CommonFlags.READONLY) - ? DecoratorFlags.INLINE - : DecoratorFlags.NONE - ) | DecoratorFlags.LAZY | DecoratorFlags.UNSAFE - ), + this.checkDecorators(decorators, acceptedFlags), declaration ); if (!parent.add(name, element)) return; @@ -1337,7 +1340,7 @@ export class Program extends DiagnosticEmitter { name, parent, declaration, - this.checkDecorators(decorators, DecoratorFlags.UNSAFE) + this.checkDecorators(decorators, acceptedFlags) ); if (!parent.addInstance(name, element)) return; } @@ -1358,6 +1361,9 @@ export class Program extends DiagnosticEmitter { | DecoratorFlags.OPERATOR_PREFIX | DecoratorFlags.OPERATOR_POSTFIX; } + if (parent.is(CommonFlags.AMBIENT)) { + acceptedFlags |= DecoratorFlags.EXTERNAL; + } var element = new FunctionPrototype( name, parent, @@ -2901,6 +2907,7 @@ export class Function extends TypedElement { tempF64s: Local[] | null = null; tempV128s: Local[] | null = null; tempAnyrefs: Local[] | null = null; + tempExnrefs: Local[] | null = null; // used by flows to keep track of break labels nextBreakId: i32 = 0; diff --git a/src/types.ts b/src/types.ts index abf42c8458..f0d4f55456 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,9 +11,7 @@ import { } from "./program"; import { - NativeType, - ExpressionRef, - Module + NativeType } from "./module"; /** Indicates the kind of a type. */ @@ -63,6 +61,8 @@ export const enum TypeKind { /** A host reference. */ ANYREF, + /** An internal exception reference. */ + EXNREF, // other @@ -341,70 +341,31 @@ export class Type { /** Converts this type to its respective native type. */ toNativeType(): NativeType { switch (this.kind) { - default: return NativeType.I32; + default: assert(false); + case TypeKind.I8: + case TypeKind.I16: + case TypeKind.I32: + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.U32: + case TypeKind.BOOL: return NativeType.I32; + case TypeKind.ISIZE: + case TypeKind.USIZE: if (this.size != 64) return NativeType.I32; case TypeKind.I64: case TypeKind.U64: return NativeType.I64; - case TypeKind.ISIZE: - case TypeKind.USIZE: return this.size == 64 ? NativeType.I64 : NativeType.I32; case TypeKind.F32: return NativeType.F32; case TypeKind.F64: return NativeType.F64; case TypeKind.V128: return NativeType.V128; case TypeKind.ANYREF: return NativeType.Anyref; - case TypeKind.VOID: return NativeType.None; - } - } - - /** Converts this type to its native `0` value. */ - toNativeZero(module: Module): ExpressionRef { - switch (this.kind) { - case TypeKind.ANYREF: - case TypeKind.VOID: assert(false); - default: return module.i32(0); - case TypeKind.ISIZE: - case TypeKind.USIZE: if (this.size != 64) return module.i32(0); - case TypeKind.I64: - case TypeKind.U64: return module.i64(0); - case TypeKind.F32: return module.f32(0); - case TypeKind.F64: return module.f64(0); - case TypeKind.V128: return module.v128(v128_zero); - } - } - - /** Converts this type to its native `1` value. */ - toNativeOne(module: Module): ExpressionRef { - switch (this.kind) { - case TypeKind.V128: - case TypeKind.ANYREF: - case TypeKind.VOID: assert(false); - default: return module.i32(1); - case TypeKind.ISIZE: - case TypeKind.USIZE: if (this.size != 64) return module.i32(1); - case TypeKind.I64: - case TypeKind.U64: return module.i64(1); - case TypeKind.F32: return module.f32(1); - case TypeKind.F64: return module.f64(1); - } - } - - /** Converts this type to its native `-1` value. */ - toNativeNegOne(module: Module): ExpressionRef { - switch (this.kind) { - case TypeKind.V128: - case TypeKind.ANYREF: - case TypeKind.VOID: assert(false); - default: return module.i32(-1); - case TypeKind.ISIZE: - case TypeKind.USIZE: if (this.size != 64) return module.i32(-1); - case TypeKind.I64: - case TypeKind.U64: return module.i64(-1, -1); - case TypeKind.F32: return module.f32(-1); - case TypeKind.F64: return module.f64(-1); + case TypeKind.EXNREF: return NativeType.Exnref; + case TypeKind.VOID: return NativeType.None; } } /** Converts this type to its signature string. */ toSignatureString(): string { switch (this.kind) { + default: assert(false); // same naming scheme as Binaryen case TypeKind.I8: case TypeKind.U8: @@ -413,18 +374,17 @@ export class Type { case TypeKind.I32: case TypeKind.U32: case TypeKind.BOOL: return "i"; + case TypeKind.ISIZE: + case TypeKind.USIZE: if (this.size != 64) return "i"; case TypeKind.I64: case TypeKind.U64: return "j"; - case TypeKind.ISIZE: - case TypeKind.USIZE: return this.size == 64 ? "j" : "i"; case TypeKind.F32: return "f"; case TypeKind.F64: return "d"; case TypeKind.V128: return "V"; case TypeKind.ANYREF: return "a"; + case TypeKind.EXNREF: return "e"; case TypeKind.VOID: return "v"; - default: assert(false); } - return "i"; } // Types @@ -559,6 +519,11 @@ export class Type { TypeFlags.REFERENCE, 0 ); + /** An internal exception reference. */ + static readonly exnref: Type = new Type(TypeKind.EXNREF, + TypeFlags.REFERENCE, 0 + ); + /** No return type. */ static readonly void: Type = new Type(TypeKind.VOID, TypeFlags.NONE, 0); diff --git a/tests/compiler/bool.optimized.wat b/tests/compiler/bool.optimized.wat index 5d46ff41ec..94e48f1a89 100644 --- a/tests/compiler/bool.optimized.wat +++ b/tests/compiler/bool.optimized.wat @@ -4,13 +4,10 @@ (data (i32.const 8) "\0e\00\00\00\01\00\00\00\01\00\00\00\0e\00\00\00b\00o\00o\00l\00.\00t\00s") (export "memory" (memory $0)) (start $start) - (func $start:bool (; 0 ;) (type $FUNCSIG$v) + (func $start (; 0 ;) (type $FUNCSIG$v) nop ) - (func $start (; 1 ;) (type $FUNCSIG$v) - call $start:bool - ) - (func $null (; 2 ;) (type $FUNCSIG$v) + (func $null (; 1 ;) (type $FUNCSIG$v) nop ) ) diff --git a/tests/compiler/builtins.optimized.wat b/tests/compiler/builtins.optimized.wat index 12067737ed..bc18547f75 100644 --- a/tests/compiler/builtins.optimized.wat +++ b/tests/compiler/builtins.optimized.wat @@ -479,9 +479,6 @@ global.set $builtins/f f64.const 25 global.set $builtins/F - i32.const 1 - i32.const 2 - call $start:builtins~anonymous|0 i32.const 8 i32.load8_s drop diff --git a/tests/compiler/call-inferred.optimized.wat b/tests/compiler/call-inferred.optimized.wat index 237fd9e1c9..1480a74fbc 100644 --- a/tests/compiler/call-inferred.optimized.wat +++ b/tests/compiler/call-inferred.optimized.wat @@ -5,14 +5,11 @@ (global $~lib/argc (mut i32) (i32.const 0)) (export "memory" (memory $0)) (start $start) - (func $start:call-inferred (; 0 ;) (type $FUNCSIG$v) + (func $start (; 0 ;) (type $FUNCSIG$v) i32.const 0 global.set $~lib/argc ) - (func $start (; 1 ;) (type $FUNCSIG$v) - call $start:call-inferred - ) - (func $null (; 2 ;) (type $FUNCSIG$v) + (func $null (; 1 ;) (type $FUNCSIG$v) nop ) ) diff --git a/tests/compiler/class-static-function.optimized.wat b/tests/compiler/class-static-function.optimized.wat index de95f9b786..afab1428af 100644 --- a/tests/compiler/class-static-function.optimized.wat +++ b/tests/compiler/class-static-function.optimized.wat @@ -16,17 +16,6 @@ (func $start (; 2 ;) (type $FUNCSIG$v) i32.const 0 global.set $~lib/argc - call $class-static-function/Example.staticFunc - i32.const 42 - i32.ne - if - i32.const 0 - i32.const 24 - i32.const 11 - i32.const 0 - call $~lib/builtins/abort - unreachable - end ) (func $null (; 3 ;) (type $FUNCSIG$v) nop diff --git a/tests/compiler/features/reference-types.optimized.wat b/tests/compiler/features/reference-types.optimized.wat index cf8faebb89..5fb12cfaa9 100644 --- a/tests/compiler/features/reference-types.optimized.wat +++ b/tests/compiler/features/reference-types.optimized.wat @@ -26,7 +26,7 @@ if i32.const 0 i32.const 24 - i32.const 16 + i32.const 19 i32.const 0 call $~lib/builtins/abort unreachable @@ -42,6 +42,9 @@ ) (func $features/reference-types/internal (; 6 ;) (type $FUNCSIG$aa) (param $0 anyref) (result anyref) local.get $0 + call $features/reference-types/external + call $features/reference-types/external + call $features/reference-types/external ) (func $start (; 7 ;) (type $FUNCSIG$v) call $start:features/reference-types diff --git a/tests/compiler/function-expression.optimized.wat b/tests/compiler/function-expression.optimized.wat index fdeb8b08d0..5a8a4043a6 100644 --- a/tests/compiler/function-expression.optimized.wat +++ b/tests/compiler/function-expression.optimized.wat @@ -44,47 +44,11 @@ i32.const 1 global.set $~lib/argc i32.const 1 - call $start:function-expression~anonymous|0 - i32.const 1 - i32.ne - if - i32.const 0 - i32.const 24 - i32.const 4 - i32.const 0 - call $~lib/builtins/abort - unreachable - end - i32.const 1 global.set $~lib/argc - i32.const 2 - call $start:function-expression~anonymous|0 - i32.const 2 - i32.ne - if - i32.const 0 - i32.const 24 - i32.const 9 - i32.const 0 - call $~lib/builtins/abort - unreachable - end i32.const 0 global.set $~lib/argc - call $start:function-expression~someName i32.const 0 global.set $~lib/argc - call $start:function-expression~anonymous|2 - i32.const 1 - i32.ne - if - i32.const 0 - i32.const 24 - i32.const 16 - i32.const 0 - call $~lib/builtins/abort - unreachable - end i32.const 5 call $function-expression/testOmitted i32.const 3 @@ -138,34 +102,8 @@ end i32.const 2 global.set $~lib/argc - i32.const 1 - i32.const 2 - call $start:function-expression~anonymous|4 - i32.const 1 - i32.ne - if - i32.const 0 - i32.const 24 - i32.const 35 - i32.const 0 - call $~lib/builtins/abort - unreachable - end i32.const 2 global.set $~lib/argc - i32.const 1 - i32.const 2 - call $start:function-expression~anonymous|5 - i32.const 42 - i32.ne - if - i32.const 0 - i32.const 24 - i32.const 36 - i32.const 0 - call $~lib/builtins/abort - unreachable - end ) (func $start (; 9 ;) (type $FUNCSIG$v) call $start:function-expression diff --git a/tests/compiler/getter-call.optimized.wat b/tests/compiler/getter-call.optimized.wat index 9b0b82a4ff..47a9e9bc30 100644 --- a/tests/compiler/getter-call.optimized.wat +++ b/tests/compiler/getter-call.optimized.wat @@ -88,7 +88,7 @@ drop i32.const 0 global.set $~lib/argc - call $getter-call/C#get:x~anonymous|0 + i32.const 42 ) (func $start (; 4 ;) (type $FUNCSIG$v) i32.const 16 diff --git a/tests/compiler/getter-setter.optimized.wat b/tests/compiler/getter-setter.optimized.wat index a3928a12c0..a5cd7cc71c 100644 --- a/tests/compiler/getter-setter.optimized.wat +++ b/tests/compiler/getter-setter.optimized.wat @@ -7,7 +7,7 @@ (global $getter-setter/Foo._bar (mut i32) (i32.const 0)) (export "memory" (memory $0)) (start $start) - (func $start:getter-setter (; 1 ;) (type $FUNCSIG$v) + (func $start (; 1 ;) (type $FUNCSIG$v) global.get $getter-setter/Foo._bar if i32.const 0 @@ -22,10 +22,7 @@ i32.const 2 global.set $getter-setter/Foo._bar ) - (func $start (; 2 ;) (type $FUNCSIG$v) - call $start:getter-setter - ) - (func $null (; 3 ;) (type $FUNCSIG$v) + (func $null (; 2 ;) (type $FUNCSIG$v) nop ) ) diff --git a/tests/compiler/inlining.optimized.wat b/tests/compiler/inlining.optimized.wat index 833540bcfb..1b308865be 100644 --- a/tests/compiler/inlining.optimized.wat +++ b/tests/compiler/inlining.optimized.wat @@ -22,23 +22,7 @@ (func $inlining/func_fe~anonymous|0 (; 2 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) local.get $0 ) - (func $inlining/test_funcs (; 3 ;) (type $FUNCSIG$v) - i32.const 1 - global.set $~lib/argc - i32.const 2 - call $inlining/func_fe~anonymous|0 - i32.const 2 - i32.ne - if - i32.const 0 - i32.const 24 - i32.const 68 - i32.const 2 - call $~lib/builtins/abort - unreachable - end - ) - (func $~lib/rt/stub/maybeGrowMemory (; 4 ;) (type $FUNCSIG$vi) (param $0 i32) + (func $~lib/rt/stub/maybeGrowMemory (; 3 ;) (type $FUNCSIG$vi) (param $0 i32) (local $1 i32) (local $2 i32) local.get $0 @@ -80,7 +64,7 @@ local.get $0 global.set $~lib/rt/stub/offset ) - (func $~lib/rt/stub/__alloc (; 5 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) + (func $~lib/rt/stub/__alloc (; 4 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) (local $2 i32) (local $3 i32) (local $4 i32) @@ -125,7 +109,7 @@ i32.store offset=12 local.get $3 ) - (func $inlining/test_ctor (; 6 ;) (type $FUNCSIG$v) + (func $inlining/test_ctor (; 5 ;) (type $FUNCSIG$v) (local $0 i32) i32.const 16 i32.const 5 @@ -205,15 +189,16 @@ unreachable end ) - (func $start (; 7 ;) (type $FUNCSIG$v) - call $inlining/test_funcs + (func $start (; 6 ;) (type $FUNCSIG$v) + i32.const 1 + global.set $~lib/argc i32.const 48 global.set $~lib/rt/stub/startOffset i32.const 48 global.set $~lib/rt/stub/offset call $inlining/test_ctor ) - (func $null (; 8 ;) (type $FUNCSIG$v) + (func $null (; 7 ;) (type $FUNCSIG$v) nop ) ) diff --git a/tests/compiler/instanceof.optimized.wat b/tests/compiler/instanceof.optimized.wat index 474ae7b02f..2573e29cf1 100644 --- a/tests/compiler/instanceof.optimized.wat +++ b/tests/compiler/instanceof.optimized.wat @@ -7,7 +7,7 @@ (global $instanceof/an (mut i32) (i32.const 0)) (export "memory" (memory $0)) (start $start) - (func $start:instanceof (; 1 ;) (type $FUNCSIG$v) + (func $start (; 1 ;) (type $FUNCSIG$v) global.get $instanceof/an if i32.const 0 @@ -20,10 +20,7 @@ i32.const 1 global.set $instanceof/an ) - (func $start (; 2 ;) (type $FUNCSIG$v) - call $start:instanceof - ) - (func $null (; 3 ;) (type $FUNCSIG$v) + (func $null (; 2 ;) (type $FUNCSIG$v) nop ) ) diff --git a/tests/compiler/portable-conversions.optimized.wat b/tests/compiler/portable-conversions.optimized.wat index 59de98a923..1dc45dbbae 100644 --- a/tests/compiler/portable-conversions.optimized.wat +++ b/tests/compiler/portable-conversions.optimized.wat @@ -4,13 +4,10 @@ (data (i32.const 8) ".\00\00\00\01\00\00\00\01\00\00\00.\00\00\00p\00o\00r\00t\00a\00b\00l\00e\00-\00c\00o\00n\00v\00e\00r\00s\00i\00o\00n\00s\00.\00t\00s") (export "memory" (memory $0)) (start $start) - (func $start:portable-conversions (; 0 ;) (type $FUNCSIG$v) + (func $start (; 0 ;) (type $FUNCSIG$v) nop ) - (func $start (; 1 ;) (type $FUNCSIG$v) - call $start:portable-conversions - ) - (func $null (; 2 ;) (type $FUNCSIG$v) + (func $null (; 1 ;) (type $FUNCSIG$v) nop ) ) diff --git a/tests/features.json b/tests/features.json index 312a4138c4..fc4884d8c5 100644 --- a/tests/features.json +++ b/tests/features.json @@ -29,5 +29,13 @@ "v8_flags": [ "--experimental-wasm-bigint" ] + }, + "exception-handling": { + "asc_flags": [ + "--enable exception-handling" + ], + "v8_flags": [ + "--experimental-wasm-eh" + ] } }