diff --git a/README.md b/README.md index 1a38ac83..989911d7 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,11 @@ Side effects: - Good benchmark when comparing both versions - Benefits standard library design ideas +How does it work? +----------------- + +AssemblyScript NEXT compiles a subset (or variant) of TypeScript to Binaryen IR. The resulting module can then be optimized, emitted in text or binary format, or even be converted to asm.js as a polyfill. + Getting started --------------- @@ -38,7 +43,7 @@ $> npm install $> node bin\asc yourModule.ts ``` -Building an UMD bundle to `dist/assemblyscript.js` (does not bundle [binaryen.js](https://github.com/AssemblyScript/binaryen.js): +Building an UMD bundle to `dist/assemblyscript.js` (does not bundle [binaryen.js](https://github.com/AssemblyScript/binaryen.js)): ``` $> npm run build diff --git a/assembly.d.ts b/assembly.d.ts index a951c326..60c2df7e 100644 --- a/assembly.d.ts +++ b/assembly.d.ts @@ -87,17 +87,10 @@ declare function assert(isTrue: bool): void; // internal decorators -declare function global(name?: string): any; -declare function struct(): any +declare function global(): any; declare function inline(): any; -declare function allocates(): any; -declare function operator(token: string, fn: any): any; // standard library -/// -/// -/// -/// -/// -/// +/// +/// diff --git a/src/compiler.ts b/src/compiler.ts index b1e293ed..9ddf4b11 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -1624,59 +1624,54 @@ export class Compiler extends DiagnosticEmitter { } compileIdentifierExpression(expression: IdentifierExpression, contextualType: Type): ExpressionRef { + switch (expression.kind) { - // null - if (expression.kind == NodeKind.NULL) { - if (contextualType.classType) // keep contextualType - return this.options.target == Target.WASM64 ? this.module.createI64(0, 0) : this.module.createI32(0); - if (this.options.target == Target.WASM64) { - this.currentType = Type.u64; - return this.module.createI64(0, 0); - } else { - this.currentType = Type.u32; + case NodeKind.NULL: + if (this.options.target == Target.WASM64) { + if (!contextualType.classType) { + assert(contextualType.kind == TypeKind.USIZE); + this.currentType = Type.usize64; + } + return this.module.createI64(0, 0); + } + if (!contextualType.classType) { + assert(contextualType.kind == TypeKind.USIZE); + this.currentType = Type.usize32; + } return this.module.createI32(0); - } - // true - } else if (expression.kind == NodeKind.TRUE) { - this.currentType = Type.bool; - return this.module.createI32(1); + case NodeKind.TRUE: + this.currentType = Type.bool; + return this.module.createI32(1); - // false - } else if (expression.kind == NodeKind.FALSE) { - this.currentType = Type.bool; - return this.module.createI32(0); + case NodeKind.FALSE: + this.currentType = Type.bool; + return this.module.createI32(0); - // this - } else if (expression.kind == NodeKind.THIS) { - if (this.currentFunction.instanceMethodOf) { - this.currentType = this.currentFunction.instanceMethodOf.type; - return this.module.createGetLocal(0, this.options.target == Target.WASM64 ? NativeType.I64 : NativeType.I32); - } - this.error(DiagnosticCode._this_cannot_be_referenced_in_current_location, expression.range); - this.currentType = this.options.target == Target.WASM64 ? Type.u64 : Type.u32; - return this.module.createUnreachable(); - } + case NodeKind.THIS: + if (this.currentFunction.instanceMethodOf) { + this.currentType = this.currentFunction.instanceMethodOf.type; + return this.module.createGetLocal(0, this.options.target == Target.WASM64 ? NativeType.I64 : NativeType.I32); + } + this.error(DiagnosticCode._this_cannot_be_referenced_in_current_location, expression.range); + this.currentType = this.options.target == Target.WASM64 ? Type.u64 : Type.u32; + return this.module.createUnreachable(); - if (expression.kind == NodeKind.IDENTIFIER) { - - // NaN - if ((expression).name == "NaN") - if (this.currentType.kind == TypeKind.F32) - return this.module.createF32(NaN); - else { + case NodeKind.IDENTIFIER: + // TODO: some sort of resolveIdentifier maybe + if ((expression).name == "NaN") { + if (this.currentType == Type.f32) + return this.module.createF32(NaN); this.currentType = Type.f64; return this.module.createF64(NaN); } - - // Infinity - if ((expression).name == "Infinity") - if (this.currentType.kind == TypeKind.F32) - return this.module.createF32(Infinity); - else { + if ((expression).name == "Infinity") { + if (this.currentType == Type.f32) + return this.module.createF32(Infinity); this.currentType = Type.f64; return this.module.createF64(Infinity); } + break; } const element: Element | null = this.program.resolveElement(expression, this.currentFunction); // reports diff --git a/src/glue/js.d.ts b/src/glue/js.d.ts index 1a70fb15..7fb052f5 100644 --- a/src/glue/js.d.ts +++ b/src/glue/js.d.ts @@ -14,6 +14,7 @@ declare type bool = boolean; // Raw memory access (here: Binaryen memory) declare function store(ptr: usize, val: T): void; declare function load(ptr: usize): T; +declare function assert(isTrue: bool): void; // Other things that might or might not be useful declare function select(ifTrue: T, ifFalse: T, condition: bool): T; diff --git a/std/array.d.ts b/std/array.d.ts deleted file mode 100644 index 0bdc66c2..00000000 --- a/std/array.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/// - -declare class Array { - length: i32; - readonly capacity: i32; - readonly data: usize; - constructor(capacity: i32); -} - -declare class Int8Array extends Array {} -declare class Int16Array extends Array {} -declare class Int32Array extends Array {} -declare class Uint8Array extends Array {} -declare class Uint16Array extends Array {} -declare class Uint32Array extends Array {} -declare class Float32Array extends Array {} -declare class Float64Array extends Array {} diff --git a/std/carray.d.ts b/std/carray.d.ts new file mode 100644 index 00000000..145da600 --- /dev/null +++ b/std/carray.d.ts @@ -0,0 +1,6 @@ +/// + +declare class CArray { + [key: number]: T; + constructor(capacity: usize); +} diff --git a/std/cstring.d.ts b/std/cstring.d.ts new file mode 100644 index 00000000..a0fed1ab --- /dev/null +++ b/std/cstring.d.ts @@ -0,0 +1,5 @@ +/// + +declare class CString extends CArray { + constructor(text: string); +} diff --git a/std/error.d.ts b/std/error.d.ts deleted file mode 100644 index 20055be8..00000000 --- a/std/error.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -/// - -declare class Error { - message: string; - constructor(message: string); -} - -declare class RangeError extends Error {} -declare class ReferenceError extends Error {} -declare class TypeError extends Error {} diff --git a/std/impl/array.ts b/std/impl/array.ts deleted file mode 100644 index f8afbe23..00000000 --- a/std/impl/array.ts +++ /dev/null @@ -1,17 +0,0 @@ -/// - -@global() -class Array { - - length: i32; - readonly capacity: i32; - readonly data: usize; - - constructor(capacity: i32) { - if (capacity < 0) - throw new RangeError("capacity out of bounds"); - this.length = capacity; - this.capacity = capacity; - this.data = Memory.allocate(sizeof() * capacity); - } -} diff --git a/std/impl/carray.ts b/std/impl/carray.ts new file mode 100644 index 00000000..5a3b9d96 --- /dev/null +++ b/std/impl/carray.ts @@ -0,0 +1,29 @@ +/// + +/** A C-compatible Array class. */ +@global() +class CArray { + + /** Constructs a new C-Array of the specified capacity. */ + constructor(capacity: usize) { + return unsafe_cast(Memory.allocate(capacity * sizeof())); + } + + /** Gets the element at the specified index using bracket notation. */ + @inline() + "[]"(index: usize): T { + return load(unsafe_cast(this) + index * sizeof()); + } + + /** Sets the element at the specified index using bracket notation. */ + @inline() + "[]="(index: usize, value: T): T { + store(unsafe_cast(this) + index * sizeof(), value); + return value; + } + + /** Disposes this instance and the memory associated with it. */ + dispose(): void { + Memory.dispose(unsafe_cast(this)); + } +} diff --git a/std/impl/cstring.ts b/std/impl/cstring.ts new file mode 100644 index 00000000..b8d7675a --- /dev/null +++ b/std/impl/cstring.ts @@ -0,0 +1,47 @@ +/// + +/** A C-compatible string class. */ +@global() +class CString extends CArray { + + /** Constructs a new C-String from a String. */ + constructor(text: string) { + super(text.length * 2 + 1); + let idx: usize = unsafe_cast(this); + for (let i: usize = 0, k: usize = (str).length; i < k; ++i) { + let u: i32 = text.charCodeAt(i); + if (u >= 0xD800 && u <= 0xDFFF && i + 1 < k) + u = 0x10000 + ((u & 0x3FF) << 10) | (text.charCodeAt(++i) & 0x3FF); + if (u <= 0x7F) + store(idx++, u as u8); + else if (u <= 0x7FF) { + // TODO: maybe combine multiple stores into the next larger one + store(idx++, (0xC0 | (u >>> 6) ) as u8); + store(idx++, (0x80 | ( u & 63)) as u8); + } else if (u <= 0xFFFF) { + store(idx++, (0xE0 | (u >>> 12) ) as u8); + store(idx++, (0x80 | ((u >>> 6) & 63)) as u8); + store(idx++, (0x80 | ( u & 63)) as u8); + } else if (u <= 0x1FFFFF) { + store(idx++, (0xF0 | (u >>> 18) ) as u8); + store(idx++, (0x80 | ((u >>> 12) & 63)) as u8); + store(idx++, (0x80 | ((u >>> 6) & 63)) as u8); + store(idx++, (0x80 | ( u & 63)) as u8); + } else if (u <= 0x3FFFFFF) { + store(idx++, (0xF8 | (u >>> 24) ) as u8); + store(idx++, (0x80 | ((u >>> 18) & 63)) as u8); + store(idx++, (0x80 | ((u >>> 12) & 63)) as u8); + store(idx++, (0x80 | ((u >>> 6) & 63)) as u8); + store(idx++, (0x80 | ( u & 63)) as u8); + } else { + store(idx++, (0xFC | (u >>> 30) ) as u8); + store(idx++, (0x80 | ((u >>> 24) & 63)) as u8); + store(idx++, (0x80 | ((u >>> 18) & 63)) as u8); + store(idx++, (0x80 | ((u >>> 12) & 63)) as u8); + store(idx++, (0x80 | ((u >>> 6) & 63)) as u8); + store(idx++, (0x80 | ( u & 63)) as u8); + } + } + store(idx, 0); + } +} diff --git a/std/impl/error.ts b/std/impl/error.ts deleted file mode 100644 index ac40b47d..00000000 --- a/std/impl/error.ts +++ /dev/null @@ -1,20 +0,0 @@ -/// - -@global() -class Error { - - message: string; - - constructor(message: string) { - this.message = message; - } -} - -@global() -class RangeError extends Error {} - -@global() -class ReferenceError extends Error {} - -@global() -class TypeError extends Error {} diff --git a/std/impl/map.ts b/std/impl/map.ts deleted file mode 100644 index 41081f01..00000000 --- a/std/impl/map.ts +++ /dev/null @@ -1,22 +0,0 @@ -/// - -@global() -class Map { - private keys: K[]; - private values: V[]; - - constructor() { - this.keys = []; - this.values = []; - } - - has(key: K): bool { - return false; - } - - set(key: K, value: V): void { - } - - clear(): void { - } -} diff --git a/std/impl/math.ts b/std/impl/math.ts deleted file mode 100644 index 70631e72..00000000 --- a/std/impl/math.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// - -@global() -class Math { -} diff --git a/std/impl/memory.ts b/std/impl/memory.ts index 5309cf5a..7ad2e8d7 100644 --- a/std/impl/memory.ts +++ b/std/impl/memory.ts @@ -1,27 +1,21 @@ /// +const MEMORY_ALIGN_LOG2: usize = 3; +const MEMORY_ALIGN_SIZE: usize = 1 << MEMORY_ALIGN_LOG2; +const MEMORY_ALIGN_MASK: usize = MEMORY_ALIGN_SIZE - 1; + @global() class Memory { static allocate(size: usize): usize { - const ptr: usize = load(sizeof()); - store(sizeof(), ptr + size); + const ptr: usize = HEAP_OFFSET; + HEAP_OFFSET += size; + if ((HEAP_OFFSET & MEMORY_ALIGN_MASK) != 0) + HEAP_OFFSET = (HEAP_OFFSET | MEMORY_ALIGN_MASK) + 1; return ptr; } - static free(ptr: usize): void { - } - - static copy(src: usize, dst: usize, count: usize): void { - for (let i: usize = 0; i < count; ++i) - store(dst + i, load(src + i)); - } - - static compare(src: usize, dst: usize, count: usize): i32 { - for (let i: usize = 0; i < count; ++i) { - const d: i32 = (load(src + i) as i32) - (load(dst + i) as i32); - if (d) return d; - } - return 0; + static dispose(ptr: usize): void { + // just a big chunk of non-disposable memory for now } } diff --git a/std/impl/set.ts b/std/impl/set.ts deleted file mode 100644 index 0da35c34..00000000 --- a/std/impl/set.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// - -@global() -class Set { -} diff --git a/std/impl/string.ts b/std/impl/string.ts deleted file mode 100644 index 10643885..00000000 --- a/std/impl/string.ts +++ /dev/null @@ -1,58 +0,0 @@ -/// - -@global() -@allocates() -@operator("==", String.equals) -@operator("!=", String.notEquals) -@operator("+", String.concat) -class String { - - readonly length: i32; - - constructor(length: i32) { - if (length < 0) - throw new RangeError("invalid length"); - const data: usize = Memory.allocate(4 + length); - store(data, length); - return classof(data); - } - - static fromCharCode(c1: i32 /* sic */, c2: i32 = -1): String { - throw new Error("not implemented"); - } - - static equals(a: String, b: String): bool { - const aLength: i32 = a.length; - return aLength == b.length && !Memory.compare(pointerof(a) + 4, pointerof(b) + 4, aLength << 1); - } - - static notEquals(a: String, b: String): bool { - const aLength: i32 = a.length; - return aLength != b.length || Memory.compare(pointerof(a) + 4, pointerof(b) + 4, aLength << 1); - } - - static concat(a: String, b: String): String { - const aLength: i32 = a.length; - const bLength: i32 = b.length; - const combinedLength: i32 = aLength + bLength; - if (combinedLength < 0) - throw new RangeError("invalid length"); - const aByteLength: i32 = aLength << 1; - const bByteLength: i32 = bLength << 1; - const data: usize = Memory.allocate(4 + combinedLength); - store(data, combinedLength); - Memory.copy(pointerof(a) + 4, data + 4, aByteLength); - Memory.copy(pointerof(b) + 4, data + 4 + aByteLength, bByteLength); - return classof(data); - } - - charCodeAt(index: i32): u16 { - if (index < 0 || index > this.length) - throw new RangeError("index out of bounds"); - return load(pointerof(this) + 4 + index << 1); - } - - concat(other: String): String { - return String.concat(this, other); - } -} diff --git a/std/impl/tsconfig.json b/std/impl/tsconfig.json index 0809326f..6241f0cf 100644 --- a/std/impl/tsconfig.json +++ b/std/impl/tsconfig.json @@ -4,12 +4,8 @@ "experimentalDecorators": true }, "files": [ - "array.ts", - "error.ts", - "map.ts", - "math.ts", - "memory.ts", - "set.ts", - "string.ts" + "carray.ts", + "cstring.ts", + "memory.ts" ] } \ No newline at end of file diff --git a/std/map.d.ts b/std/map.d.ts deleted file mode 100644 index 790b8184..00000000 --- a/std/map.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/std/math.d.ts b/std/math.d.ts deleted file mode 100644 index 78794f4f..00000000 --- a/std/math.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -/// - -declare class Math { -} diff --git a/std/memory.d.ts b/std/memory.d.ts index 9354cec6..cdd44d71 100644 --- a/std/memory.d.ts +++ b/std/memory.d.ts @@ -2,7 +2,5 @@ declare class Memory { static allocate(size: usize): usize; - static free(ptr: usize): void; - static copy(src: usize, dst: usize, count: usize): void; - static compare(src: usize, dst: usize, count: usize): i32; + static dispose(ptr: usize): void; } diff --git a/std/set.d.ts b/std/set.d.ts deleted file mode 100644 index 428f9efe..00000000 --- a/std/set.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -/// - -declare class Set { -} diff --git a/std/string.d.ts b/std/string.d.ts deleted file mode 100644 index d3b07f84..00000000 --- a/std/string.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/// - -declare class String { - readonly length: i32; - constructor(length: i32); - static fromCharCode(c1: i32, c2?: i32); - static equals(a: string, b: string): bool; - static concat(a: string, b: string): string; - charCodeAt(index: i32): u16; - concat(other: string): string; -} diff --git a/std/tsconfig.json b/std/tsconfig.json index 2516bc29..6963be52 100644 --- a/std/tsconfig.json +++ b/std/tsconfig.json @@ -4,12 +4,8 @@ "experimentalDecorators": true }, "files": [ - "array.d.ts", - "error.d.ts", - "map.d.ts", - "math.d.ts", - "memory.d.ts", - "set.d.ts", - "string.d.ts" + "carray.d.ts", + "cstring.d.ts", + "memory.d.ts" ] } \ No newline at end of file diff --git a/tests/compiler/game-of-life.html b/tests/compiler/game-of-life.html index 31f791bc..12ff57b0 100644 --- a/tests/compiler/game-of-life.html +++ b/tests/compiler/game-of-life.html @@ -1,36 +1,61 @@ - -