diff --git a/README.md b/README.md index 893c406c..b561ab2a 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,10 @@ A few early examples to get an idea: A PSON decoder implemented in AssemblyScript. * **[TLSF memory allocator](./examples/tlsf)**
- An early port of TLSF to AssemblyScript. + An port of TLSF to AssemblyScript. + +* **[μgc garbage collector](./examples/ugc)**
+ An port of μgc to AssemblyScript. Or browse the [compiler tests](./tests/compiler) for a more in-depth overview of what's supported already. One of them is a [showcase](./tests/compiler/showcase.ts). diff --git a/examples/tlsf/README.md b/examples/tlsf/README.md index 6af330a7..2ff61a18 100644 --- a/examples/tlsf/README.md +++ b/examples/tlsf/README.md @@ -1,5 +1,5 @@ -TLSF -==== +TLSF memory allocator +===================== A port of [Matt Conte's implementation](https://github.com/mattconte/tlsf) of the [TLSF](http://www.gii.upv.es/tlsf/) memory allocator to AssemblyScript. diff --git a/examples/tlsf/assembly/LICENSE b/examples/tlsf/assembly/LICENSE index 7caf9595..a1544a95 100644 --- a/examples/tlsf/assembly/LICENSE +++ b/examples/tlsf/assembly/LICENSE @@ -1,4 +1,4 @@ -tlsf.ts is based on: +tlsf.ts is based on https://github.com/mattconte/tlsf Two Level Segregated Fit memory allocator, version 3.1. Written by Matthew Conte diff --git a/examples/tlsf/assembly/tlsf.ts b/examples/tlsf/assembly/tlsf.ts index c9eaf9df..16b2a412 100644 --- a/examples/tlsf/assembly/tlsf.ts +++ b/examples/tlsf/assembly/tlsf.ts @@ -35,7 +35,7 @@ class BlockHeader { static readonly OVERHEAD: usize = sizeof(); // User data starts directly after the size field in a used block. - static readonly USERDATA_OFFSET: usize = sizeof() + sizeof(); + static readonly DATA_OFFSET: usize = sizeof() + sizeof(); // A free block must be large enough to store its header minus the size of // the prev_phys_block field, and no larger than the number of addressable @@ -114,12 +114,12 @@ class BlockHeader { /** Gets the block header matching the specified data pointer. */ static fromDataPtr(ptr: usize): BlockHeader { - return changetype(ptr - BlockHeader.USERDATA_OFFSET); + return changetype(ptr - BlockHeader.DATA_OFFSET); } /** Returns the address of this block's data. */ toDataPtr(): usize { - return changetype(this) + BlockHeader.USERDATA_OFFSET; + return changetype(this) + BlockHeader.DATA_OFFSET; } /** Gets the next block after this one using the specified size. */ @@ -174,7 +174,7 @@ class BlockHeader { return this.size >= BlockHeader.SIZE + size; } - /* Splits a block into two, the second of which is free. */ + /** Splits a block into two, the second of which is free. */ split(size: usize): BlockHeader { // Calculate the amount of space left in the remaining block. var remain = BlockHeader.fromOffset( @@ -194,7 +194,7 @@ class BlockHeader { return remain; } - /* Absorb a free block's storage into this (adjacent previous) free block. */ + /** Absorb a free block's storage into this (adjacent previous) free block. */ absorb(block: BlockHeader): void { assert(!this.isLast, "previous block can't be last" @@ -205,7 +205,7 @@ class BlockHeader { } } -/* The TLSF control structure. */ +/** The TLSF control structure. */ @explicit class Control extends BlockHeader { // Empty lists point here, indicating free @@ -289,7 +289,7 @@ class Control extends BlockHeader { // Empty lists point here, indicating free this.insertFreeBlock(block, fl_out, sl_out); } - /* Inserts a free block into the free block list. */ + /** Inserts a free block into the free block list. */ insertFreeBlock(block: BlockHeader, fl: i32, sl: i32): void { var current = this.blocks(fl, sl); assert(current, @@ -311,7 +311,7 @@ class Control extends BlockHeader { // Empty lists point here, indicating free this.sl_bitmap_set(fl, this.sl_bitmap(fl) | (1 << sl)) } - /* Removes a free block from the free list.*/ + /** Removes a free block from the free list.*/ removeFreeBlock(block: BlockHeader, fl: i32, sl: i32): void { var prev = block.prev_free; var next = block.next_free; diff --git a/examples/ugc/README.md b/examples/ugc/README.md new file mode 100644 index 00000000..80fb613d --- /dev/null +++ b/examples/ugc/README.md @@ -0,0 +1,20 @@ +μgc garbage collector +===================== + +A port of [Bach Le's μgc garbage collector library](https://github.com/bullno1/ugc) to AssemblyScript. + +Instructions +------------ + +To build [assembly/ugc.ts](./assembly/ugc.ts) to an untouched and an optimized `.wasm` including their respective `.wast` representations, run: + +``` +$> npm run build +``` + +Afterwards, to run the included [test](./tests/index.js): + +``` +$> npm install +$> npm test +``` diff --git a/examples/ugc/assembly/LICENSE b/examples/ugc/assembly/LICENSE new file mode 100644 index 00000000..16f15994 --- /dev/null +++ b/examples/ugc/assembly/LICENSE @@ -0,0 +1,25 @@ +ugc.ts is based on https://github.com/bullno1/ugc + +Copyright (c) 2017, Bach Le +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/examples/ugc/assembly/tsconfig.json b/examples/ugc/assembly/tsconfig.json new file mode 100644 index 00000000..6e52b21c --- /dev/null +++ b/examples/ugc/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/examples/ugc/assembly/ugc.ts b/examples/ugc/assembly/ugc.ts new file mode 100644 index 00000000..90696948 --- /dev/null +++ b/examples/ugc/assembly/ugc.ts @@ -0,0 +1,287 @@ +/////////////////////////// μgc Garbage Collector ///////////////////////////// +// based on https://github.com/bullno1/ugc - BSD (see LICENSE file) // +/////////////////////////////////////////////////////////////////////////////// + +// States +const IDLE: u8 = 0; +const MARK: u8 = 1; +const SWEEP: u8 = 2; + +// Gray tag +const GRAY: u32 = 2; + +/** Header for a managed object. */ +@explicit +class ObjectHeader { + + /////////////////////////////// Constants /////////////////////////////////// + + static readonly SIZE: usize = 2 * sizeof(); + + ///////////////////////////////// Fields //////////////////////////////////// + + tagged_next: usize; + tagged_prev: usize; + + get next(): ObjectHeader { + return changetype(this.tagged_next & ~3); + } + + set next(value: ObjectHeader) { + this.tagged_next = changetype(value) | (this.tagged_next & 3); + } + + get prev(): ObjectHeader { + return changetype(this.tagged_prev & ~3); + } + + set prev(value: ObjectHeader) { + this.tagged_prev = changetype(value) | (this.tagged_prev & 3); + } + + get color(): u32 { + return this.tagged_next & 3; + } + + set color(value: u32) { + assert(value < 3); + this.tagged_next = this.tagged_next | value; + } + + ///////////////////////////////// Methods /////////////////////////////////// + + push(element: ObjectHeader): void { + element.next = this; + element.prev = this.prev; + this.prev.next = element; + this.prev = element; + } + + unlink(): void { + var next = this.next; + var prev = this.prev; + next.prev = prev; + prev.next = next; + } + + clear(): void { + this.next = this; + this.prev = this; + } +} + +/** Garbage collector data. */ +@explicit +class Control { + + /////////////////////////////// Constants /////////////////////////////////// + + static readonly SIZE: usize = 7 * sizeof() + 2 * sizeof(); + static readonly PAUSED_BIT: u8 = 1 << 7; + + ///////////////////////////////// Fields //////////////////////////////////// + + // 'from' and 'to' point here + private __set1_tagged_next: usize; + private __set1_tagged_prev: usize; + private __set2_tagged_next: usize; + private __set2_tagged_prev: usize; + + from: ObjectHeader; + to: ObjectHeader; + iterator: ObjectHeader; + state: u8; // MSB indicates paused + white: u8; + + /** Tests whether the collector is currently paused. */ + get paused(): bool { return (this.state & Control.PAUSED_BIT) != 0; } + /** Sets whether the collector is currently paused. */ + set paused(paused: bool) { this.state = paused ? this.state |= Control.PAUSED_BIT : this.state &= ~Control.PAUSED_BIT; } + + ///////////////////////////////// Methods /////////////////////////////////// + + /** Creates a new instance. */ + static create(mem: usize): Control { + var control = changetype(mem); + var set1 = changetype(mem); + var set2 = changetype(mem + 2 * sizeof()); + set1.clear(); + set2.clear(); + control.state = IDLE; + control.white = 0; + control.from = set1; + control.to = set2; + control.iterator = control.to; + return control; + } + + /** Registers a new object to be managed. */ + register(obj: ObjectHeader): void { + this.from.push(obj); + obj.color = this.white; + } + + /** + * Registers a new reference from one object to another. + * + * Whenever an object stores a reference to another object, this function + * MUST be called to ensure that the GC works correctly. + * + * Root objects (stack, globals) are treated differently so there is no need + * to call this function when a store to them occurs. + */ + addRef(parent: ObjectHeader, child: ObjectHeader): void { + var parent_color = parent.color; + var child_color = child.color; + var white = this.white; + var black = white ^ 1; + if (parent_color == black && child_color == white) { + this.makeGray(parent); + } + } + + /** + * Make the GC perform one unit of work. + * + * What happens depends on the current GC's state. + * + * - In IDLE state, it will scan the root by calling the scan callback then + * switch to MARK state. + * - In MARK state, it will mark one object and discover its children using + * the scan callback. When there is no object left to mark, the GC will + * scan the root once more to account for changes during the mark phase. + * When all live objects are marked, it will switch to SWEEP state. + * - In SWEEP state, it will release one object. When all garbage are + * released, it wil switch to UGC_IDLE state. + */ + step(): void { + var obj: ObjectHeader; + switch (this.state) { + + case IDLE: + gc_scan_fn(this, null); + this.state = MARK; + break; + + case MARK: + obj = this.iterator.next; + var white = this.white; + + if (obj != this.to) { + this.iterator = obj; + obj.color = white ^ 1; + gc_scan_fn(this, obj); + } else { + gc_scan_fn(this, null); + obj = this.iterator.next; + if (obj == this.to) { + var from = this.from; + this.from = this.to; + this.to = from; + this.white = white ^ 1; + this.iterator = from.next; + this.state = SWEEP; + } + } + break; + + case SWEEP: + obj = this.iterator; + if (obj != this.to) { + this.iterator = obj.next; + gc_free_fn(this, obj); + } else { + this.to.clear(); + this.state = IDLE; + } + break; + } + } + + /** + * Performs a collection cycle. + * + * Start the GC if it's not already running and only return once the GC has + * finished collecting all garbage identified at the point of calling. + * + * If the GC is already in the SWEEP state, it will leave newly created + * garbage for the next cycle. + */ + collect(): void { + if (this.state == IDLE) + this.step(); + while (this.state != IDLE) + this.step(); + } + + /** Informs the GC of a referred object during the mark phase. */ + visit(obj: ObjectHeader): void { + if (this.state == SWEEP) + return; + if (obj.color == this.white) + this.makeGray(obj); + } + + makeGray(obj: ObjectHeader): void { + if (obj != this.iterator) { + obj.unlink(); + this.to.push(obj); + } else { + this.iterator = this.iterator.prev; + } + obj.color = GRAY; + } +} + +var GC = Control.create(HEAP_BASE); +var GC_BASE = HEAP_BASE + Control.SIZE; + +GC.register(changetype(GC_BASE)); + +// Exported interface + +/** Pauses automatic garbage collection. */ +export function gc_pause(): void { + GC.paused = true; +} + +/** Resumes automatic garbage collection. */ +export function gc_resume(): void { + GC.paused = false; +} + +/** Performs a collection cycle. Ignores pauses. */ +export function gc_collect(): void { + var paused = GC.paused; + GC.paused = false; + GC.collect(); + GC.paused = paused; +} + +// TODO: these functions must be generated by the compiler and combined by +// any potential linker. They live here for now to document their structure. + +function gc_scan_fn(control: Control, header: ObjectHeader | null): void { + if (!header) { + // visit all global vars referencing managed objects + } else { + // visit all referenced objects using the compiler's knowledge of this + // object's layout + var classId = load(changetype(header) + ObjectHeader.SIZE); + // switch (classId) { + // arrays + // strings + // user-defined + // } + } +} + +function gc_free_fn(control: Control, header: ObjectHeader): void { + // finalize the given object using the compiler's knowledge of its layout + var classId = load(changetype(header) + ObjectHeader.SIZE); + // switch (classId) { + // array, string: free their data segments + // TODO: might make sense to provide @finalize or similar + // } + free_memory(changetype(header)); +} diff --git a/examples/ugc/package.json b/examples/ugc/package.json new file mode 100644 index 00000000..138aef2a --- /dev/null +++ b/examples/ugc/package.json @@ -0,0 +1,11 @@ +{ + "name": "@assemblyscript/ugc", + "version": "1.0.0", + "private": true, + "scripts": { + "build": "npm run build:untouched && npm run build:optimized", + "build:untouched": "asc assembly/ugc.ts -t ugc.untouched.wast -b ugc.untouched.wasm --validate", + "build:optimized": "asc -O assembly/ugc.ts -b ugc.optimized.wasm -t ugc.optimized.wast --validate --noAssert --runPasses inlining", + "test": "node tests" + } +} diff --git a/examples/ugc/tests/index.js b/examples/ugc/tests/index.js new file mode 100644 index 00000000..ddecb3d0 --- /dev/null +++ b/examples/ugc/tests/index.js @@ -0,0 +1,26 @@ +var fs = require("fs"); + +// NOTE that this doesn't do anything useful, yet + +var ugc = new WebAssembly.Instance(new WebAssembly.Module(fs.readFileSync(__dirname + "/../ugc.untouched.wasm"))).exports; + +function mem(memory, offset, count) { + if (!offset) offset = 0; + if (!count) count = 1024; + var mem = new Uint8Array(memory.buffer, offset); + var stackTop = new Uint32Array(memory.buffer, 4, 1)[0]; + var hex = []; + for (var i = 0; i < count; ++i) { + var o = (offset + i).toString(16); + while (o.length < 3) o = "0" + o; + if ((i & 15) === 0) { + hex.push("\n" + o + ":"); + } + var h = mem[i].toString(16); + if (h.length < 2) h = "0" + h; + hex.push(h); + } + console.log(hex.join(" ") + " ..."); +} + +mem(ugc.memory, 0, 1024); diff --git a/src/ast.ts b/src/ast.ts index cec50177..239d93ff 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1050,12 +1050,60 @@ export abstract class DeclarationStatement extends Statement { /** Array of decorators. */ decorators: Decorator[] | null = null; - protected _cachedInternalName: string | null = null; + protected cachedProgramLevelInternalName: string | null = null; + protected cachedFileLevelInternalName: string | null = null; - /** Gets the mangled internal name of this declaration. */ - get internalName(): string { return this._cachedInternalName === null ? this._cachedInternalName = mangleInternalName(this) : this._cachedInternalName; } - /** Tests if this is a top-level declaration. */ - get isTopLevel(): bool { return this.parent != null && this.parent.kind == NodeKind.SOURCE; } + /** Gets the mangled program-level internal name of this declaration. */ + get programLevelInternalName(): string { + if (!this.cachedProgramLevelInternalName) + this.cachedProgramLevelInternalName = mangleInternalName(this, true); + return this.cachedProgramLevelInternalName; + } + + /** Gets the mangled file-level internal name of this declaration. */ + get fileLevelInternalName(): string { + if (!this.cachedFileLevelInternalName) + this.cachedFileLevelInternalName = mangleInternalName(this, false); + return this.cachedFileLevelInternalName; + } + + /** Tests if this is a top-level declaration within its source file. */ + get isTopLevel(): bool { + var parent = this.parent; + if (!parent) + return false; + if (parent.kind == NodeKind.VARIABLE) + if (!(parent = parent.parent)) + return false; + return parent.kind == NodeKind.SOURCE; + } + + /** Tests if this declaration is a top-level export within its source file. */ + get isTopLevelExport(): bool { + var parent = this.parent; + if (!parent) + return false; + if (parent.kind == NodeKind.VARIABLE) + if (!(parent = parent.parent)) + return false; + if (parent.kind == NodeKind.NAMESPACEDECLARATION) + return hasModifier(ModifierKind.EXPORT, this.modifiers) && (parent).isTopLevelExport; + if (parent.kind == NodeKind.CLASSDECLARATION) + return hasModifier(ModifierKind.STATIC, this.modifiers) && (parent).isTopLevelExport; + return parent.kind == NodeKind.SOURCE && hasModifier(ModifierKind.EXPORT, this.modifiers); + } + + /** Tests if this declaration exported by the given member needs an explicit export. */ + needsExplicitExport(member: ExportMember): bool { + // This is necessary because module-level exports are automatically created for + // exported top level declarations of all sorts. In other words this function + // tests that this condition doesn't apply so the export isn't a duplicate. + return ( + member.identifier.name != member.externalIdentifier.name || // if aliased + this.range.source != member.range.source || // if a re-export + !this.isTopLevelExport // if not top-level + ); + } } /** Base class of all variable-like declaration statements with a type and initializer. */ @@ -1470,7 +1518,7 @@ export function mangleInternalPath(path: string): string { } /** Mangles a declaration's name to an internal name. */ -export function mangleInternalName(declaration: DeclarationStatement): string { +export function mangleInternalName(declaration: DeclarationStatement, asGlobal: bool = false): string { var name = declaration.name.name; var parent = declaration.parent; if (!parent) @@ -1479,8 +1527,10 @@ export function mangleInternalName(declaration: DeclarationStatement): string { if (!(parent = parent.parent)) return name; if (parent.kind == NodeKind.CLASSDECLARATION) - return (parent).internalName + (hasModifier(ModifierKind.STATIC, declaration.modifiers) ? STATIC_DELIMITER : INSTANCE_DELIMITER) + name; + return mangleInternalName(parent, asGlobal) + (hasModifier(ModifierKind.STATIC, declaration.modifiers) ? STATIC_DELIMITER : INSTANCE_DELIMITER) + name; if (parent.kind == NodeKind.NAMESPACEDECLARATION || parent.kind == NodeKind.ENUMDECLARATION) - return (parent).internalName + STATIC_DELIMITER + name; + return mangleInternalName(parent, asGlobal) + STATIC_DELIMITER + name; + if (asGlobal) + return name; return declaration.range.source.internalPath + PATH_DELIMITER + name; } diff --git a/src/compiler.ts b/src/compiler.ts index cba39226..74e371de 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -326,17 +326,11 @@ export class Compiler extends DiagnosticEmitter { // globals compileGlobalDeclaration(declaration: VariableDeclaration, isConst: bool): Global | null { - var element = this.program.elements.get(declaration.internalName); + var element = this.program.elements.get(declaration.fileLevelInternalName); if (!element || element.kind != ElementKind.GLOBAL) throw new Error("global expected"); if (!this.compileGlobal(element)) // reports return null; - if (isModuleExport(element, declaration)) { - if ((element).hasConstantValue) - this.module.addGlobalExport(element.internalName, declaration.name.name); - else - this.warning(DiagnosticCode.Cannot_export_a_mutable_global, declaration.range); - } return element; } @@ -416,7 +410,6 @@ export class Compiler extends DiagnosticEmitter { if (!this.module.noEmit) this.startFunctionBody.push(setExpr); } else { - // TODO: not necessary to create a global if constant and not a file-level export anyway if (!global.isMutable) { if (!this.module.noEmit) { var exprType = _BinaryenExpressionGetType(initExpr); @@ -441,11 +434,14 @@ export class Compiler extends DiagnosticEmitter { default: throw new Error("concrete type expected"); } - global.hasConstantValue = true; - if (!declaration || isModuleExport(global, declaration)) - this.module.addGlobal(internalName, nativeType, global.isMutable, initExpr); } - } else if (!this.module.noEmit) + global.hasConstantValue = true; + if (!declaration || declaration.isTopLevel) { // might be re-exported + this.module.addGlobal(internalName, nativeType, global.isMutable, initExpr); + } + if (declaration && declaration.range.source.isEntry && declaration.isTopLevelExport) + this.module.addGlobalExport(global.internalName, declaration.programLevelInternalName); + } else this.module.addGlobal(internalName, nativeType, global.isMutable, initExpr); } global.isCompiled = true; @@ -454,11 +450,11 @@ export class Compiler extends DiagnosticEmitter { // enums - compileEnumDeclaration(declaration: EnumDeclaration): void { - var element = this.program.elements.get(declaration.internalName); + compileEnumDeclaration(declaration: EnumDeclaration): Enum | null { + var element = this.program.elements.get(declaration.fileLevelInternalName); if (!element || element.kind != ElementKind.ENUM) throw new Error("enum expected"); - this.compileEnum(element); + return this.compileEnum(element) ? element : null; } compileEnum(element: Enum): bool { @@ -473,10 +469,11 @@ export class Compiler extends DiagnosticEmitter { continue; var initInStart = false; var val = member; + var valueDeclaration = val.declaration; if (val.hasConstantValue) { - this.module.addGlobal(val.internalName, NativeType.I32, false, this.module.createI32(val.constantValue)); - } else if (val.declaration) { - var valueDeclaration = val.declaration; + if (!element.declaration || element.declaration.isTopLevelExport) + this.module.addGlobal(val.internalName, NativeType.I32, false, this.module.createI32(val.constantValue)); + } else if (valueDeclaration) { var initExpr: ExpressionRef; if (valueDeclaration.value) { initExpr = this.compileExpression(valueDeclaration.value, Type.i32); @@ -519,25 +516,26 @@ export class Compiler extends DiagnosticEmitter { } } else throw new Error("declaration expected"); - if (element.declaration && isModuleExport(element, element.declaration) && !initInStart) - this.module.addGlobalExport(member.internalName, member.internalName); previousValue = val; + + // export values if the enum is exported + if (element.declaration && element.declaration.range.source.isEntry && element.declaration.isTopLevelExport) { + if (member.hasConstantValue) + this.module.addGlobalExport(member.internalName, member.internalName); + else if (valueDeclaration) + this.warning(DiagnosticCode.Cannot_export_a_mutable_global, valueDeclaration.range); + } } return true; } // functions - compileFunctionDeclaration(declaration: FunctionDeclaration, typeArguments: TypeNode[], contextualTypeArguments: Map | null = null, alternativeReportNode: Node | null = null): void { - var internalName = declaration.internalName; - var element = this.program.elements.get(internalName); + compileFunctionDeclaration(declaration: FunctionDeclaration, typeArguments: TypeNode[], contextualTypeArguments: Map | null = null, alternativeReportNode: Node | null = null): Function | null { + var element = this.program.elements.get(declaration.fileLevelInternalName); if (!element || element.kind != ElementKind.FUNCTION_PROTOTYPE) throw new Error("function expected"); - var instance = this.compileFunctionUsingTypeArguments(element, typeArguments, contextualTypeArguments, alternativeReportNode); // reports - if (!instance) - return; - if (isModuleExport(instance, declaration)) - this.module.addFunctionExport(instance.internalName, declaration.name.name); + return this.compileFunctionUsingTypeArguments(element, typeArguments, contextualTypeArguments, alternativeReportNode); // reports } compileFunctionUsingTypeArguments(prototype: FunctionPrototype, typeArguments: TypeNode[], contextualTypeArguments: Map | null = null, alternativeReportNode: Node | null = null): Function | null { @@ -606,6 +604,9 @@ export class Compiler extends DiagnosticEmitter { this.module.addFunction(instance.internalName, typeRef, typesToNativeTypes(instance.additionalLocals), this.module.createBlock(null, stmts, NativeType.None)); } instance.finalize(); + if (declaration.range.source.isEntry && declaration.isTopLevelExport) { + this.module.addFunctionExport(instance.internalName, declaration.name.name); + } return true; } @@ -709,19 +710,25 @@ export class Compiler extends DiagnosticEmitter { break; case ElementKind.FUNCTION_PROTOTYPE: - if (!(element).isGeneric) { + if (!(element).isGeneric && statement.range.source.isEntry) { var functionInstance = this.compileFunctionUsingTypeArguments(element, []); - if (functionInstance && statement.range.source.isEntry) - this.module.addFunctionExport(functionInstance.internalName, member.externalIdentifier.name); + if (functionInstance) { + var functionDeclaration = functionInstance.prototype.declaration; + if (functionDeclaration && functionDeclaration.needsExplicitExport(member)) + this.module.addFunctionExport(functionInstance.internalName, member.externalIdentifier.name); + } } break; case ElementKind.GLOBAL: if (this.compileGlobal(element) && statement.range.source.isEntry) { - if ((element).hasConstantValue) - this.module.addGlobalExport(element.internalName, member.externalIdentifier.name); - else - this.warning(DiagnosticCode.Cannot_export_a_mutable_global, member.range); + var globalDeclaration = (element).declaration; + if (globalDeclaration && globalDeclaration.needsExplicitExport(member)) { + if ((element).hasConstantValue) + this.module.addGlobalExport(element.internalName, member.externalIdentifier.name); + else + this.warning(DiagnosticCode.Cannot_export_a_mutable_global, member.range); + } } break; @@ -735,8 +742,7 @@ export class Compiler extends DiagnosticEmitter { // classes compileClassDeclaration(declaration: ClassDeclaration, typeArguments: TypeNode[], contextualTypeArguments: Map | null = null, alternativeReportNode: Node | null = null): void { - var internalName = declaration.internalName; - var element = this.program.elements.get(internalName); + var element = this.program.elements.get(declaration.fileLevelInternalName); if (!element || element.kind != ElementKind.CLASS_PROTOTYPE) throw new Error("class expected"); this.compileClassUsingTypeArguments(element, typeArguments, contextualTypeArguments, alternativeReportNode); @@ -3063,26 +3069,6 @@ export class Compiler extends DiagnosticEmitter { // helpers -/** Tests whether an element is a module-level export from the entry file. */ -function isModuleExport(element: Element, declaration: DeclarationStatement): bool { - if (!element.isExported) - return false; - var parentNode = declaration.parent; - if (!parentNode) - return false; - if (declaration.range.source.isEntry && parentNode.kind != NodeKind.NAMESPACEDECLARATION) - return true; - if (parentNode.kind == NodeKind.VARIABLE) - if (!(parentNode = parentNode.parent)) - return false; - if (parentNode.kind != NodeKind.NAMESPACEDECLARATION && parentNode.kind != NodeKind.CLASSDECLARATION) - return false; - var parent = element.program.elements.get((parentNode).internalName); - if (!parent) - return false; - return isModuleExport(parent, parentNode); -} - /** Creates an inlined expression of a constant variable-like element. */ function makeInlineConstant(element: VariableLikeElement, module: Module): ExpressionRef { assert(element.hasConstantValue); diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 304d7fa8..cee2717e 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -180,7 +180,7 @@ export abstract class DiagnosticEmitter { this.diagnostics.push(message); if (!this.silentDiagnostics) { console.log(formatDiagnosticMessage(message, true, true) + "\n"); // temporary - console.log(new Error("stack").stack); + // console.log(new Error("stack").stack); } } diff --git a/src/program.ts b/src/program.ts index 691b1032..5cc0c421 100644 --- a/src/program.ts +++ b/src/program.ts @@ -110,7 +110,7 @@ export class Program extends DiagnosticEmitter { types: Map = noTypesYet; /** Declared type aliases. */ typeAliases: Map = new Map(); - /** Exports of individual files by internal name. Not global exports. */ + /** Exports of individual files by exported internal name. Not global exports. */ exports: Map = new Map(); /** Constructs a new program, optionally inheriting parser diagnostics. */ @@ -283,7 +283,7 @@ export class Program extends DiagnosticEmitter { } private initializeClass(declaration: ClassDeclaration, queuedDerivedClasses: ClassPrototype[], namespace: Element | null = null): void { - var internalName = declaration.internalName; + var internalName = declaration.fileLevelInternalName; if (this.elements.has(internalName)) { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName); return; @@ -351,17 +351,17 @@ export class Program extends DiagnosticEmitter { private initializeField(declaration: FieldDeclaration, classPrototype: ClassPrototype): void { var name = declaration.name.name; - var internalName = declaration.internalName; + var internalName = declaration.fileLevelInternalName; // static fields become global variables if (hasModifier(ModifierKind.STATIC, declaration.modifiers)) { if (this.elements.has(internalName)) { - this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, declaration.internalName); + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName); return; } if (classPrototype.members) { if (classPrototype.members.has(name)) { - this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, declaration.internalName); + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName); return; } } else @@ -374,7 +374,7 @@ export class Program extends DiagnosticEmitter { } else { if (classPrototype.instanceMembers) { if (classPrototype.instanceMembers.has(name)) { - this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, declaration.internalName); + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName); return; } } else @@ -386,19 +386,19 @@ export class Program extends DiagnosticEmitter { private initializeMethod(declaration: MethodDeclaration, classPrototype: ClassPrototype): void { var name = declaration.name.name; - var internalName = declaration.internalName; + var internalName = declaration.fileLevelInternalName; var instancePrototype: FunctionPrototype | null = null; // static methods become global functions if (hasModifier(ModifierKind.STATIC, declaration.modifiers)) { if (this.elements.has(internalName)) { - this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, declaration.internalName); + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName); return; } if (classPrototype.members) { if (classPrototype.members.has(name)) { - this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, declaration.internalName); + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName); return; } } else @@ -411,7 +411,7 @@ export class Program extends DiagnosticEmitter { } else { if (classPrototype.instanceMembers) { if (classPrototype.instanceMembers.has(name)) { - this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, declaration.internalName); + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName); return; } } else @@ -470,7 +470,7 @@ export class Program extends DiagnosticEmitter { private initializeAccessor(declaration: MethodDeclaration, classPrototype: ClassPrototype, isGetter: bool): void { var propertyName = declaration.name.name; - var internalPropertyName = declaration.internalName; + var internalPropertyName = declaration.fileLevelInternalName; var propertyElement = this.elements.get(internalPropertyName); if (propertyElement) { @@ -505,7 +505,7 @@ export class Program extends DiagnosticEmitter { var internalInstanceName = classPrototype.internalName + INSTANCE_DELIMITER + name; if (classPrototype.instanceMembers) { if (classPrototype.instanceMembers.has(name)) { - this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, declaration.internalName); + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalPropertyName); return; } } else @@ -521,7 +521,7 @@ export class Program extends DiagnosticEmitter { } private initializeEnum(declaration: EnumDeclaration, namespace: Element | null = null): void { - var internalName = declaration.internalName; + var internalName = declaration.fileLevelInternalName; if (this.elements.has(internalName)) { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName); return; @@ -556,7 +556,7 @@ export class Program extends DiagnosticEmitter { private initializeEnumValue(declaration: EnumValueDeclaration, enm: Enum): void { var name = declaration.name.name; - var internalName = declaration.internalName; + var internalName = declaration.fileLevelInternalName; if (enm.members) { if (enm.members.has(name)) { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName); @@ -649,7 +649,7 @@ export class Program extends DiagnosticEmitter { } private initializeFunction(declaration: FunctionDeclaration, namespace: Element | null = null): void { - var internalName = declaration.internalName; + var internalName = declaration.fileLevelInternalName; if (this.elements.has(internalName)) { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName); return; @@ -694,7 +694,7 @@ export class Program extends DiagnosticEmitter { } private initializeImport(declaration: ImportDeclaration, internalPath: string, queuedExports: Map, queuedImports: QueuedImport[]): void { - var internalName = declaration.internalName; + var internalName = declaration.fileLevelInternalName; if (this.elements.has(internalName)) { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName); return; @@ -739,7 +739,7 @@ export class Program extends DiagnosticEmitter { } private initializeInterface(declaration: InterfaceDeclaration, namespace: Element | null = null): void { - var internalName = declaration.internalName; + var internalName = declaration.fileLevelInternalName; if (this.elements.has(internalName)) { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName); return; @@ -791,7 +791,7 @@ export class Program extends DiagnosticEmitter { } private initializeNamespace(declaration: NamespaceDeclaration, queuedExtendingClasses: ClassPrototype[], parentNamespace: Element | null = null): void { - var internalName = declaration.internalName; + var internalName = declaration.fileLevelInternalName; var namespace = this.elements.get(internalName); if (!namespace) { @@ -873,7 +873,7 @@ export class Program extends DiagnosticEmitter { var declarations = statement.declarations; for (var i = 0, k = declarations.length; i < k; ++i) { var declaration = declarations[i]; - var internalName = declaration.internalName; + var internalName = declaration.fileLevelInternalName; if (this.elements.has(internalName)) { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName); continue; diff --git a/tests/compiler/export.optimized.wast b/tests/compiler/export.optimized.wast index 285e3c9f..2ca90ee7 100644 --- a/tests/compiler/export.optimized.wast +++ b/tests/compiler/export.optimized.wast @@ -3,14 +3,16 @@ (type $v (func)) (global $export/a i32 (i32.const 1)) (global $export/b i32 (i32.const 2)) + (global $export/c i32 (i32.const 3)) (memory $0 1) (export "add" (func $export/add)) - (export "renamed_sub" (func $export/sub)) + (export "sub" (func $export/sub)) + (export "renamed_mul" (func $export/mul)) (export "a" (global $export/a)) - (export "renamed_b" (global $export/b)) + (export "b" (global $export/b)) + (export "renamed_c" (global $export/c)) (export "two" (func $export/ns.two)) (export "memory" (memory $0)) - (start $export/ns.two) (func $export/add (; 0 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) (i32.add (get_local $0) @@ -23,7 +25,13 @@ (get_local $1) ) ) - (func $export/ns.two (; 2 ;) (type $v) + (func $export/mul (; 2 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (i32.mul + (get_local $0) + (get_local $1) + ) + ) + (func $export/ns.two (; 3 ;) (type $v) (nop) ) ) diff --git a/tests/compiler/export.ts b/tests/compiler/export.ts index e7857e2f..ef50645f 100644 --- a/tests/compiler/export.ts +++ b/tests/compiler/export.ts @@ -6,14 +6,23 @@ function sub(a: i32, b: i32): i32 { return a - b; } -export { sub as renamed_sub }; +export { sub }; + +function mul(a: i32, b: i32): i32 { // not exported as "mul" + return a * b; +} + +export { mul as renamed_mul }; export const a: i32 = 1; const b: i32 = 2; -b; -export { b as renamed_b }; +export { b }; + +const c: i32 = 3; // not exported as "c" + +export { c as renamed_c }; export namespace ns { function one(): void {} diff --git a/tests/compiler/export.wast b/tests/compiler/export.wast index 55cc6737..96d443f1 100644 --- a/tests/compiler/export.wast +++ b/tests/compiler/export.wast @@ -3,15 +3,17 @@ (type $v (func)) (global $export/a i32 (i32.const 1)) (global $export/b i32 (i32.const 2)) + (global $export/c i32 (i32.const 3)) (global $HEAP_BASE i32 (i32.const 4)) (memory $0 1) (export "add" (func $export/add)) - (export "renamed_sub" (func $export/sub)) + (export "sub" (func $export/sub)) + (export "renamed_mul" (func $export/mul)) (export "a" (global $export/a)) - (export "renamed_b" (global $export/b)) + (export "b" (global $export/b)) + (export "renamed_c" (global $export/c)) (export "two" (func $export/ns.two)) (export "memory" (memory $0)) - (start $start) (func $export/add (; 0 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) (return (i32.add @@ -28,13 +30,16 @@ ) ) ) - (func $export/ns.two (; 2 ;) (type $v) - ) - (func $start (; 3 ;) (type $v) - (drop - (i32.const 2) + (func $export/mul (; 2 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (return + (i32.mul + (get_local $0) + (get_local $1) + ) ) ) + (func $export/ns.two (; 3 ;) (type $v) + ) ) (; [program.elements] @@ -82,15 +87,19 @@ GLOBAL: HEAP_BASE FUNCTION_PROTOTYPE: export/add FUNCTION_PROTOTYPE: export/sub + FUNCTION_PROTOTYPE: export/mul GLOBAL: export/a GLOBAL: export/b + GLOBAL: export/c NAMESPACE: export/ns FUNCTION_PROTOTYPE: export/ns.one FUNCTION_PROTOTYPE: export/ns.two [program.exports] FUNCTION_PROTOTYPE: export/add - FUNCTION_PROTOTYPE: export/renamed_sub + FUNCTION_PROTOTYPE: export/sub + FUNCTION_PROTOTYPE: export/renamed_mul GLOBAL: export/a - GLOBAL: export/renamed_b + GLOBAL: export/b + GLOBAL: export/renamed_c NAMESPACE: export/ns ;) diff --git a/tests/compiler/import.optimized-inlined.wast b/tests/compiler/import.optimized-inlined.wast index abbb867f..bae846e6 100644 --- a/tests/compiler/import.optimized-inlined.wast +++ b/tests/compiler/import.optimized-inlined.wast @@ -9,33 +9,51 @@ (local $1 i32) (local $2 i32) (local $3 i32) + (local $4 i32) + (local $5 i32) (drop (i32.add - (block (result i32) - (block $__inlined_func$export/add (result i32) - (set_local $0 - (i32.const 1) + (i32.add + (block (result i32) + (block $__inlined_func$export/add (result i32) + (set_local $0 + (i32.const 1) + ) + (set_local $1 + (i32.const 2) + ) + (i32.add + (get_local $0) + (get_local $1) + ) ) - (set_local $1 - (i32.const 2) - ) - (i32.add - (get_local $0) - (get_local $1) + ) + (block (result i32) + (block $__inlined_func$export/sub (result i32) + (set_local $2 + (i32.const 2) + ) + (set_local $3 + (i32.const 3) + ) + (i32.sub + (get_local $2) + (get_local $3) + ) ) ) ) (block (result i32) - (block $__inlined_func$export/sub (result i32) - (set_local $2 - (i32.const 2) + (block $__inlined_func$export/mul (result i32) + (set_local $4 + (i32.const 3) ) - (set_local $3 + (set_local $5 (i32.const 1) ) - (i32.sub - (get_local $2) - (get_local $3) + (i32.mul + (get_local $4) + (get_local $5) ) ) ) diff --git a/tests/compiler/import.optimized.wast b/tests/compiler/import.optimized.wast index 678d3079..39a84b87 100644 --- a/tests/compiler/import.optimized.wast +++ b/tests/compiler/import.optimized.wast @@ -16,18 +16,30 @@ (get_local $1) ) ) - (func $export/ns.two (; 2 ;) (type $v) + (func $export/mul (; 2 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (i32.mul + (get_local $0) + (get_local $1) + ) + ) + (func $export/ns.two (; 3 ;) (type $v) (nop) ) - (func $start (; 3 ;) (type $v) + (func $start (; 4 ;) (type $v) (drop (i32.add - (call $export/add - (i32.const 1) - (i32.const 2) + (i32.add + (call $export/add + (i32.const 1) + (i32.const 2) + ) + (call $export/sub + (i32.const 2) + (i32.const 3) + ) ) - (call $export/sub - (i32.const 2) + (call $export/mul + (i32.const 3) (i32.const 1) ) ) diff --git a/tests/compiler/import.ts b/tests/compiler/import.ts index b6ef62ff..fcbaa9cc 100644 --- a/tests/compiler/import.ts +++ b/tests/compiler/import.ts @@ -1,5 +1,13 @@ -import { add, renamed_sub as sub, a, renamed_b as b, ns as renamed_ns } from "./export"; +import { + add, + sub as sub, + renamed_mul as mul, + a, + b as b, + renamed_c as c, + ns as renamed_ns +} from "./export"; -add(a, b) + sub(b, a); +add(a, b) + sub(b, c) + mul(c, a); renamed_ns.two(); diff --git a/tests/compiler/import.wast b/tests/compiler/import.wast index 8a9b4f46..b9d50e52 100644 --- a/tests/compiler/import.wast +++ b/tests/compiler/import.wast @@ -3,6 +3,7 @@ (type $v (func)) (global $export/a i32 (i32.const 1)) (global $export/b i32 (i32.const 2)) + (global $export/c i32 (i32.const 3)) (global $HEAP_BASE i32 (i32.const 4)) (memory $0 1) (export "memory" (memory $0)) @@ -23,20 +24,31 @@ ) ) ) - (func $export/ns.two (; 2 ;) (type $v) - ) - (func $start (; 3 ;) (type $v) - (drop - (i32.const 2) + (func $export/mul (; 2 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (return + (i32.mul + (get_local $0) + (get_local $1) + ) ) + ) + (func $export/ns.two (; 3 ;) (type $v) + ) + (func $start (; 4 ;) (type $v) (drop (i32.add - (call $export/add - (i32.const 1) - (i32.const 2) + (i32.add + (call $export/add + (i32.const 1) + (i32.const 2) + ) + (call $export/sub + (i32.const 2) + (i32.const 3) + ) ) - (call $export/sub - (i32.const 2) + (call $export/mul + (i32.const 3) (i32.const 1) ) ) @@ -90,20 +102,26 @@ GLOBAL: HEAP_BASE FUNCTION_PROTOTYPE: export/add FUNCTION_PROTOTYPE: export/sub + FUNCTION_PROTOTYPE: export/mul GLOBAL: export/a GLOBAL: export/b + GLOBAL: export/c NAMESPACE: export/ns FUNCTION_PROTOTYPE: export/ns.one FUNCTION_PROTOTYPE: export/ns.two FUNCTION_PROTOTYPE: import/add FUNCTION_PROTOTYPE: import/sub + FUNCTION_PROTOTYPE: import/mul GLOBAL: import/a GLOBAL: import/b + GLOBAL: import/c NAMESPACE: import/renamed_ns [program.exports] FUNCTION_PROTOTYPE: export/add - FUNCTION_PROTOTYPE: export/renamed_sub + FUNCTION_PROTOTYPE: export/sub + FUNCTION_PROTOTYPE: export/renamed_mul GLOBAL: export/a - GLOBAL: export/renamed_b + GLOBAL: export/b + GLOBAL: export/renamed_c NAMESPACE: export/ns ;) diff --git a/tests/compiler/reexport.optimized.wast b/tests/compiler/reexport.optimized.wast index f3fc9b36..5ef0b907 100644 --- a/tests/compiler/reexport.optimized.wast +++ b/tests/compiler/reexport.optimized.wast @@ -3,13 +3,18 @@ (type $v (func)) (global $export/a i32 (i32.const 1)) (global $export/b i32 (i32.const 2)) + (global $export/c i32 (i32.const 3)) (memory $0 1) (export "add" (func $export/add)) (export "renamed_sub" (func $export/sub)) - (export "renamed_a" (global $export/a)) - (export "rerenamed_b" (global $export/b)) + (export "renamed_mul" (func $export/mul)) + (export "rerenamed_mul" (func $export/mul)) + (export "a" (global $export/a)) + (export "renamed_b" (global $export/b)) + (export "renamed_c" (global $export/c)) + (export "rerenamed_c" (global $export/c)) (export "renamed_add" (func $export/add)) - (export "rerenamed_sub" (func $export/sub)) + (export "rerenamed_sub" (func $export/mul)) (export "memory" (memory $0)) (start $start) (func $export/add (; 0 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) @@ -24,14 +29,20 @@ (get_local $1) ) ) - (func $start (; 2 ;) (type $v) + (func $export/mul (; 2 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (i32.mul + (get_local $0) + (get_local $1) + ) + ) + (func $start (; 3 ;) (type $v) (drop (i32.add (call $export/add (i32.const 1) (i32.const 2) ) - (call $export/sub + (call $export/mul (i32.const 3) (i32.const 4) ) diff --git a/tests/compiler/reexport.ts b/tests/compiler/reexport.ts index 495c7c84..c48dbf71 100644 --- a/tests/compiler/reexport.ts +++ b/tests/compiler/reexport.ts @@ -1,8 +1,25 @@ -export { add, renamed_sub, a as renamed_a, renamed_b as rerenamed_b } from "./export"; +export { + add, + sub as renamed_sub, + renamed_mul, + renamed_mul as rerenamed_mul, -import { add as imported_add, renamed_sub as imported_sub, ns as imported_ns } from "./export"; + a, + b as renamed_b, + renamed_c, + renamed_c as rerenamed_c +} from "./export"; -export { imported_add as renamed_add, imported_sub as rerenamed_sub }; +import { + add as imported_add, + renamed_mul as imported_sub, + ns as imported_ns +} from "./export"; + +export { + imported_add as renamed_add, + imported_sub as rerenamed_sub +}; imported_add(1, 2) + imported_sub(3, 4); diff --git a/tests/compiler/reexport.wast b/tests/compiler/reexport.wast index fecce9bd..b8511b89 100644 --- a/tests/compiler/reexport.wast +++ b/tests/compiler/reexport.wast @@ -3,14 +3,19 @@ (type $v (func)) (global $export/a i32 (i32.const 1)) (global $export/b i32 (i32.const 2)) + (global $export/c i32 (i32.const 3)) (global $HEAP_BASE i32 (i32.const 4)) (memory $0 1) (export "add" (func $export/add)) (export "renamed_sub" (func $export/sub)) - (export "renamed_a" (global $export/a)) - (export "rerenamed_b" (global $export/b)) + (export "renamed_mul" (func $export/mul)) + (export "rerenamed_mul" (func $export/mul)) + (export "a" (global $export/a)) + (export "renamed_b" (global $export/b)) + (export "renamed_c" (global $export/c)) + (export "rerenamed_c" (global $export/c)) (export "renamed_add" (func $export/add)) - (export "rerenamed_sub" (func $export/sub)) + (export "rerenamed_sub" (func $export/mul)) (export "memory" (memory $0)) (start $start) (func $export/add (; 0 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) @@ -29,19 +34,24 @@ ) ) ) - (func $export/ns.two (; 2 ;) (type $v) - ) - (func $start (; 3 ;) (type $v) - (drop - (i32.const 2) + (func $export/mul (; 2 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (return + (i32.mul + (get_local $0) + (get_local $1) + ) ) + ) + (func $export/ns.two (; 3 ;) (type $v) + ) + (func $start (; 4 ;) (type $v) (drop (i32.add (call $export/add (i32.const 1) (i32.const 2) ) - (call $export/sub + (call $export/mul (i32.const 3) (i32.const 4) ) @@ -95,8 +105,10 @@ GLOBAL: HEAP_BASE FUNCTION_PROTOTYPE: export/add FUNCTION_PROTOTYPE: export/sub + FUNCTION_PROTOTYPE: export/mul GLOBAL: export/a GLOBAL: export/b + GLOBAL: export/c NAMESPACE: export/ns FUNCTION_PROTOTYPE: export/ns.one FUNCTION_PROTOTYPE: export/ns.two @@ -105,14 +117,20 @@ NAMESPACE: reexport/imported_ns [program.exports] FUNCTION_PROTOTYPE: export/add - FUNCTION_PROTOTYPE: export/renamed_sub + FUNCTION_PROTOTYPE: export/sub + FUNCTION_PROTOTYPE: export/renamed_mul GLOBAL: export/a - GLOBAL: export/renamed_b + GLOBAL: export/b + GLOBAL: export/renamed_c NAMESPACE: export/ns FUNCTION_PROTOTYPE: reexport/add FUNCTION_PROTOTYPE: reexport/renamed_sub - GLOBAL: reexport/renamed_a - GLOBAL: reexport/rerenamed_b + FUNCTION_PROTOTYPE: reexport/renamed_mul + FUNCTION_PROTOTYPE: reexport/rerenamed_mul + GLOBAL: reexport/a + GLOBAL: reexport/renamed_b + GLOBAL: reexport/renamed_c + GLOBAL: reexport/rerenamed_c FUNCTION_PROTOTYPE: reexport/renamed_add FUNCTION_PROTOTYPE: reexport/rerenamed_sub NAMESPACE: reexport/renamed_ns