Initial implementation if ugc, see #16; Fix tests

This commit is contained in:
dcodeIO 2018-01-18 01:46:41 +01:00
parent 461daab2a2
commit 9cdfa35938
24 changed files with 715 additions and 173 deletions

View File

@ -20,7 +20,10 @@ A few early examples to get an idea:
A PSON decoder implemented in AssemblyScript.
* **[TLSF memory allocator](./examples/tlsf)**<br />
An early port of TLSF to AssemblyScript.
An port of TLSF to AssemblyScript.
* **[μgc garbage collector](./examples/ugc)**<br />
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).

View File

@ -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.

View File

@ -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

View File

@ -35,7 +35,7 @@ class BlockHeader {
static readonly OVERHEAD: usize = sizeof<usize>();
// User data starts directly after the size field in a used block.
static readonly USERDATA_OFFSET: usize = sizeof<usize>() + sizeof<usize>();
static readonly DATA_OFFSET: usize = sizeof<usize>() + sizeof<usize>();
// 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<BlockHeader>(ptr - BlockHeader.USERDATA_OFFSET);
return changetype<BlockHeader>(ptr - BlockHeader.DATA_OFFSET);
}
/** Returns the address of this block's data. */
toDataPtr(): usize {
return changetype<usize>(this) + BlockHeader.USERDATA_OFFSET;
return changetype<usize>(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;

20
examples/ugc/README.md Normal file
View File

@ -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
```

View File

@ -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.

View File

@ -0,0 +1,6 @@
{
"extends": "../../../std/assembly.json",
"include": [
"./**/*.ts"
]
}

View File

@ -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<usize>();
///////////////////////////////// Fields ////////////////////////////////////
tagged_next: usize;
tagged_prev: usize;
get next(): ObjectHeader {
return changetype<ObjectHeader>(this.tagged_next & ~3);
}
set next(value: ObjectHeader) {
this.tagged_next = changetype<usize>(value) | (this.tagged_next & 3);
}
get prev(): ObjectHeader {
return changetype<ObjectHeader>(this.tagged_prev & ~3);
}
set prev(value: ObjectHeader) {
this.tagged_prev = changetype<usize>(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<usize>() + 2 * sizeof<u8>();
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<Control>(mem);
var set1 = changetype<ObjectHeader>(mem);
var set2 = changetype<ObjectHeader>(mem + 2 * sizeof<usize>());
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<ObjectHeader>(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<u32>(changetype<usize>(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<u32>(changetype<usize>(header) + ObjectHeader.SIZE);
// switch (classId) {
// array, string: free their data segments
// TODO: might make sense to provide @finalize or similar
// }
free_memory(changetype<usize>(header));
}

11
examples/ugc/package.json Normal file
View File

@ -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"
}
}

View File

@ -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);

View File

@ -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) && (<NamespaceDeclaration>parent).isTopLevelExport;
if (parent.kind == NodeKind.CLASSDECLARATION)
return hasModifier(ModifierKind.STATIC, this.modifiers) && (<ClassDeclaration>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 (<ClassDeclaration>parent).internalName + (hasModifier(ModifierKind.STATIC, declaration.modifiers) ? STATIC_DELIMITER : INSTANCE_DELIMITER) + name;
return mangleInternalName(<ClassDeclaration>parent, asGlobal) + (hasModifier(ModifierKind.STATIC, declaration.modifiers) ? STATIC_DELIMITER : INSTANCE_DELIMITER) + name;
if (parent.kind == NodeKind.NAMESPACEDECLARATION || parent.kind == NodeKind.ENUMDECLARATION)
return (<DeclarationStatement>parent).internalName + STATIC_DELIMITER + name;
return mangleInternalName(<DeclarationStatement>parent, asGlobal) + STATIC_DELIMITER + name;
if (asGlobal)
return name;
return declaration.range.source.internalPath + PATH_DELIMITER + name;
}

View File

@ -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(<Global>element)) // reports
return null;
if (isModuleExport(element, declaration)) {
if ((<Global>element).hasConstantValue)
this.module.addGlobalExport(element.internalName, declaration.name.name);
else
this.warning(DiagnosticCode.Cannot_export_a_mutable_global, declaration.range);
}
return <Global>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(<Enum>element);
return this.compileEnum(<Enum>element) ? <Enum>element : null;
}
compileEnum(element: Enum): bool {
@ -473,10 +469,11 @@ export class Compiler extends DiagnosticEmitter {
continue;
var initInStart = false;
var val = <EnumValue>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(<Expression>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 = <EnumValue>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<string,Type> | 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<string,Type> | 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(<FunctionPrototype>element, typeArguments, contextualTypeArguments, alternativeReportNode); // reports
if (!instance)
return;
if (isModuleExport(instance, declaration))
this.module.addFunctionExport(instance.internalName, declaration.name.name);
return this.compileFunctionUsingTypeArguments(<FunctionPrototype>element, typeArguments, contextualTypeArguments, alternativeReportNode); // reports
}
compileFunctionUsingTypeArguments(prototype: FunctionPrototype, typeArguments: TypeNode[], contextualTypeArguments: Map<string,Type> | 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, <ExpressionRef[]>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 (!(<FunctionPrototype>element).isGeneric) {
if (!(<FunctionPrototype>element).isGeneric && statement.range.source.isEntry) {
var functionInstance = this.compileFunctionUsingTypeArguments(<FunctionPrototype>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(<Global>element) && statement.range.source.isEntry) {
if ((<Global>element).hasConstantValue)
this.module.addGlobalExport(element.internalName, member.externalIdentifier.name);
else
this.warning(DiagnosticCode.Cannot_export_a_mutable_global, member.range);
var globalDeclaration = (<Global>element).declaration;
if (globalDeclaration && globalDeclaration.needsExplicitExport(member)) {
if ((<Global>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<string,Type> | 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(<ClassPrototype>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((<DeclarationStatement>parentNode).internalName);
if (!parent)
return false;
return isModuleExport(parent, <DeclarationStatement>parentNode);
}
/** Creates an inlined expression of a constant variable-like element. */
function makeInlineConstant(element: VariableLikeElement, module: Module): ExpressionRef {
assert(element.hasConstantValue);

View File

@ -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(<string>new Error("stack").stack);
// console.log(<string>new Error("stack").stack);
}
}

View File

@ -110,7 +110,7 @@ export class Program extends DiagnosticEmitter {
types: Map<string,Type> = noTypesYet;
/** Declared type aliases. */
typeAliases: Map<string,TypeNode> = 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<string,Element> = 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<string,QueuedExport>, 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;

View File

@ -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)
)
)

View File

@ -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 {}

View File

@ -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
;)

View File

@ -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)
)
)
)

View File

@ -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)
)
)

View File

@ -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();

View File

@ -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
;)

View File

@ -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)
)

View File

@ -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);

View File

@ -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