diff --git a/src/compiler.ts b/src/compiler.ts index a840fc0a..00a9b1e5 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -6602,7 +6602,7 @@ export class Compiler extends DiagnosticEmitter { } // make a static array if possible - if (isStatic) return this.ensureStaticArray(elementType, constantValues); + // if (isStatic) return this.ensureStaticArray(elementType, constantValues); // TODO // otherwise obtain the array type var arrayPrototype = assert(this.program.arrayPrototype); diff --git a/std/assembly/array.ts b/std/assembly/array.ts index 749bcdaa..76029910 100644 --- a/std/assembly/array.ts +++ b/std/assembly/array.ts @@ -33,7 +33,7 @@ export class Array extends ArrayBufferView { var oldData = this.data; var oldCapacity = oldData.byteLength >>> alignof(); if (length > oldCapacity) { - const MAX_LENGTH = ArrayBuffer.MAX_BYTELENGTH >>> alignof(); + const MAX_LENGTH = ArrayBufferView.MAX_BYTELENGTH >>> alignof(); if (length > MAX_LENGTH) throw new RangeError("Invalid array length"); let newCapacity = length << alignof(); let newData = REALLOC(changetype(oldData), newCapacity); // registers on move @@ -373,140 +373,150 @@ export class Array extends ArrayBufferView { return this; } - // FIXME: refactor into multiple functions? join(separator: string = ","): string { + if (isInteger()) { + if (value instanceof bool) return this.join_bool(separator); + return this.join_int(separator); + } + if (isFloat()) return this.join_flt(separator); + if (isString()) return this.join_str(separator); + if (isArray()) return this.join_arr(separator); + if (isReference()) return this.join_ref(separator); + ERROR("unspported element type"); + return unreachable(); + } + + private join_bool(separator: string = ","): string { var lastIndex = this.length_ - 1; if (lastIndex < 0) return ""; - var result = ""; - var value: T; - var base = this.dataStart; - // var buffer = this.buffer_; - var sepLen = separator.length; - var hasSeparator = sepLen != 0; - if (value instanceof bool) { - if (!lastIndex) return select("true", "false", load(base)); + var dataStart = this.dataStart; + if (!lastIndex) return select("true", "false", load(dataStart)); - let valueLen = 5; // max possible length of element len("false") - let estLen = (valueLen + sepLen) * lastIndex + valueLen; - let result = ALLOC(estLen << 1); - let offset = 0; - for (let i = 0; i < lastIndex; ++i) { - value = load(base + i); - valueLen = 4 + (!value); - memory.copy( - result + (offset << 1), - changetype(select("true", "false", value)), - valueLen << 1 - ); - offset += valueLen; - if (hasSeparator) { - memory.copy( - result + (offset << 1), - changetype(separator), - sepLen << 1 - ); - offset += sepLen; - } - } - value = load(base + lastIndex); + var sepLen = separator.length; + var valueLen = 5; // max possible length of element len("false") + var estLen = (valueLen + sepLen) * lastIndex + valueLen; + var result = ALLOC(estLen << 1); + var offset = 0; + var value: bool; + for (let i = 0; i < lastIndex; ++i) { + value = load(dataStart + i); valueLen = 4 + (!value); memory.copy( result + (offset << 1), changetype(select("true", "false", value)), - valueLen << 1 + valueLen << 1 ); offset += valueLen; - - if (estLen > offset) { - let trimmed = changetype(result).substring(0, offset); - FREE(result); - return trimmed; // registered in .substring + if (sepLen) { + memory.copy( + result + (offset << 1), + changetype(separator), + sepLen << 1 + ); + offset += sepLen; } - return REGISTER(result); - } else if (isInteger()) { - if (!lastIndex) return changetype(itoa(load(base))); + } + value = load(dataStart + lastIndex); + valueLen = 4 + (!value); + memory.copy( + result + (offset << 1), + changetype(select("true", "false", value)), + valueLen << 1 + ); + offset += valueLen; - const valueLen = (sizeof() <= 4 ? 10 : 20) + isSigned(); - let estLen = (valueLen + sepLen) * lastIndex + valueLen; - let result = ALLOC(estLen << 1); - let offset = 0; - for (let i = 0; i < lastIndex; ++i) { - value = load(base + (i << alignof())); - offset += itoa_stream(result, offset, value); - if (hasSeparator) { - memory.copy( - result + (offset << 1), - changetype(separator), - sepLen << 1 - ); - offset += sepLen; - } - } - value = load(base + (lastIndex << alignof())); + if (estLen > offset) { + let trimmed = changetype(result).substring(0, offset); + FREE(result); + return trimmed; // registered in .substring + } + return REGISTER(result); + } + + private join_int(separator: string = ","): string { + var lastIndex = this.length_ - 1; + if (lastIndex < 0) return ""; + var dataStart = this.dataStart; + if (!lastIndex) return changetype(itoa(load(dataStart))); + + var sepLen = separator.length; + const valueLen = (sizeof() <= 4 ? 10 : 20) + isSigned(); + var estLen = (valueLen + sepLen) * lastIndex + valueLen; + var result = ALLOC(estLen << 1); + var offset = 0; + var value: T; + for (let i = 0; i < lastIndex; ++i) { + value = load(dataStart + (i << alignof())); offset += itoa_stream(result, offset, value); - if (estLen > offset) { - let trimmed = changetype(result).substring(0, offset); - FREE(result); - return trimmed; // registered in .substring + if (sepLen) { + memory.copy( + result + (offset << 1), + changetype(separator), + sepLen << 1 + ); + offset += sepLen; } - return REGISTER(result); - } else if (isFloat()) { - if (!lastIndex) return changetype(dtoa(load(base))); + } + value = load(dataStart + (lastIndex << alignof())); + offset += itoa_stream(result, offset, value); + if (estLen > offset) { + let trimmed = changetype(result).substring(0, offset); + FREE(result); + return trimmed; // registered in .substring + } + return REGISTER(result); + } - const valueLen = MAX_DOUBLE_LENGTH; - let estLen = (valueLen + sepLen) * lastIndex + valueLen; - let result = ALLOC(estLen << 1); - let offset = 0; - for (let i = 0; i < lastIndex; ++i) { - value = load(base + (i << alignof())); - offset += dtoa_stream(result, offset, value); - if (hasSeparator) { - memory.copy( - result + (offset << 1), - changetype(separator), - sepLen << 1 - ); - offset += sepLen; - } - } - value = load(base + (lastIndex << alignof())); + private join_flt(separator: string = ","): string { + var lastIndex = this.length_ - 1; + if (lastIndex < 0) return ""; + var dataStart = this.dataStart; + if (!lastIndex) return changetype(dtoa(load(dataStart))); + + const valueLen = MAX_DOUBLE_LENGTH; + var sepLen = separator.length; + var estLen = (valueLen + sepLen) * lastIndex + valueLen; + var result = ALLOC(estLen << 1); + var offset = 0; + var value: T; + for (let i = 0; i < lastIndex; ++i) { + value = load(dataStart + (i << alignof())); offset += dtoa_stream(result, offset, value); - if (estLen > offset) { - let trimmed = changetype(result).substring(0, offset); - FREE(result); - return trimmed; // registered in .substring + if (sepLen) { + memory.copy( + result + (offset << 1), + changetype(separator), + sepLen << 1 + ); + offset += sepLen; } - return REGISTER(result); - } else if (isString()) { - if (!lastIndex) return load(base); + } + value = load(dataStart + (lastIndex << alignof())); + offset += dtoa_stream(result, offset, value); + if (estLen > offset) { + let trimmed = changetype(result).substring(0, offset); + FREE(result); + return trimmed; // registered in .substring + } + return REGISTER(result); + } - let estLen = 0; - for (let i = 0, len = lastIndex + 1; i < len; ++i) { - estLen += load(base + (i << alignof())).length; - } - let offset = 0; - let result = ALLOC((estLen + sepLen * lastIndex) << 1); - for (let i = 0; i < lastIndex; ++i) { - value = load(base + (i << alignof())); - if (value) { - let valueLen = changetype(value).length; - memory.copy( - result + (offset << 1), - changetype(value), - valueLen << 1 - ); - offset += valueLen; - } - if (hasSeparator) { - memory.copy( - result + (offset << 1), - changetype(separator), - sepLen << 1 - ); - offset += sepLen; - } - } - value = load(base + (lastIndex << alignof())); + private join_str(separator: string = ","): string { + var lastIndex = this.length_ - 1; + if (lastIndex < 0) return ""; + var dataStart = this.dataStart; + if (!lastIndex) return load(dataStart); + + var sepLen = separator.length; + var estLen = 0; + for (let i = 0, len = lastIndex + 1; i < len; ++i) { + estLen += load(dataStart + (i << alignof())).length; + } + var offset = 0; + var result = ALLOC((estLen + sepLen * lastIndex) << 1); + var value: String; + for (let i = 0; i < lastIndex; ++i) { + value = load(dataStart + (i << alignof())); if (value) { let valueLen = changetype(value).length; memory.copy( @@ -514,47 +524,66 @@ export class Array extends ArrayBufferView { changetype(value), valueLen << 1 ); + offset += valueLen; } - return REGISTER(result); - } else if (isArray()) { - if (!lastIndex) { - value = load(base); - return value ? value.join(separator) : ""; + if (sepLen) { + memory.copy( + result + (offset << 1), + changetype(separator), + sepLen << 1 + ); + offset += sepLen; } - for (let i = 0; i < lastIndex; ++i) { - value = load(base + (i << alignof())); - if (value) result += value.join(separator); - if (hasSeparator) result += separator; - } - value = load(base + (lastIndex << alignof())); + } + value = load(dataStart + (lastIndex << alignof())); + if (value) { + let valueLen = changetype(value).length; + memory.copy( + result + (offset << 1), + changetype(value), + valueLen << 1 + ); + } + return REGISTER(result); + } + + private join_arr(separator: string = ","): string { + var lastIndex = this.length_ - 1; + if (lastIndex < 0) return ""; + + var result = ""; + var sepLen = separator.length; + var base = this.dataStart; + var value: T; + if (!lastIndex) { + value = load(base); + return value ? value.join(separator) : ""; + } + for (let i = 0; i < lastIndex; ++i) { + value = load(base + (i << alignof())); if (value) result += value.join(separator); - return result; // registered by concatenation (FIXME: lots of garbage) - } else if (isReference()) { // References - if (!lastIndex) return "[object Object]"; - const valueLen = 15; // max possible length of element len("[object Object]") - let estLen = (valueLen + sepLen) * lastIndex + valueLen; - let result = ALLOC(estLen << 1); - let offset = 0; - for (let i = 0; i < lastIndex; ++i) { - value = load(base + (i << alignof())); - if (value) { - memory.copy( - result + (offset << 1), - changetype("[object Object]"), - valueLen << 1 - ); - offset += valueLen; - } - if (hasSeparator) { - memory.copy( - result + (offset << 1), - changetype(separator), - sepLen << 1 - ); - offset += sepLen; - } - } - if (load(base + (lastIndex << alignof()))) { + if (sepLen) result += separator; + } + value = load(base + (lastIndex << alignof())); + if (value) result += value.join(separator); + return result; // registered by concatenation (FIXME: lots of garbage) + } + + private join_ref(separator: string = ","): string { + var lastIndex = this.length_ - 1; + if (lastIndex < 0) return ""; + var base = this.dataStart; + if (!lastIndex) return "[object Object]"; + + const valueLen = 15; // max possible length of element len("[object Object]") + var sepLen = separator.length; + var estLen = (valueLen + sepLen) * lastIndex + valueLen; + var result = ALLOC(estLen << 1); + var offset = 0; + var value: T; + for (let i = 0; i < lastIndex; ++i) { + value = load(base + (i << alignof())); + if (value) { memory.copy( result + (offset << 1), changetype("[object Object]"), @@ -562,16 +591,29 @@ export class Array extends ArrayBufferView { ); offset += valueLen; } - if (estLen > offset) { - let out = changetype(result).substring(0, offset); - FREE(result); - return out; // registered in .substring + if (sepLen) { + memory.copy( + result + (offset << 1), + changetype(separator), + sepLen << 1 + ); + offset += sepLen; } - return REGISTER(result); - } else { - ERROR("unspported type"); - assert(false); } + if (load(base + (lastIndex << alignof()))) { + memory.copy( + result + (offset << 1), + changetype("[object Object]"), + valueLen << 1 + ); + offset += valueLen; + } + if (estLen > offset) { + let out = changetype(result).substring(0, offset); + FREE(result); + return out; // registered in .substring + } + return REGISTER(result); } @inline diff --git a/std/assembly/arraybuffer.ts b/std/assembly/arraybuffer.ts index 2baa2bf3..03e02f95 100644 --- a/std/assembly/arraybuffer.ts +++ b/std/assembly/arraybuffer.ts @@ -1,6 +1,6 @@ -import { ALLOC_RAW, REGISTER, ArrayBufferBase } from "./runtime"; +import { HEADER, ALLOC_RAW, ALLOC, REGISTER, ArrayBufferView } from "./runtime"; -@sealed export class ArrayBuffer extends ArrayBufferBase { +@sealed export class ArrayBuffer { @inline static isView(value: T): bool { if (value) { @@ -20,7 +20,16 @@ import { ALLOC_RAW, REGISTER, ArrayBufferBase } from "./runtime"; return false; } - slice(begin: i32 = 0, end: i32 = ArrayBuffer.MAX_BYTELENGTH): ArrayBuffer { + constructor(length: i32) { + if (length > ArrayBufferView.MAX_BYTELENGTH) throw new RangeError("Invalid array buffer length"); + return REGISTER(ALLOC(length)); + } + + get byteLength(): i32 { + return changetype
(changetype(this) - HEADER_SIZE).payloadSize; + } + + slice(begin: i32 = 0, end: i32 = ArrayBufferView.MAX_BYTELENGTH): ArrayBuffer { var length = this.byteLength; begin = begin < 0 ? max(length + begin, 0) : min(begin, length); end = end < 0 ? max(length + end , 0) : min(end , length); diff --git a/std/assembly/builtins.ts b/std/assembly/builtins.ts index df7f0acb..13fa6245 100644 --- a/std/assembly/builtins.ts +++ b/std/assembly/builtins.ts @@ -500,5 +500,3 @@ export namespace f64x2 { export namespace v8x16 { @builtin export declare function shuffle(a: v128, b: v128, l0: u8, l1: u8, l2: u8, l3: u8, l4: u8, l5: u8, l6: u8, l7: u8, l8: u8, l9: u8, l10: u8, l11: u8, l12: u8, l13: u8, l14: u8, l15: u8): v128; } - -@builtin export declare function start(): void; diff --git a/std/assembly/gc.ts b/std/assembly/gc.ts new file mode 100644 index 00000000..36a06745 --- /dev/null +++ b/std/assembly/gc.ts @@ -0,0 +1,31 @@ +/** Garbage collector interface. */ +export namespace gc { + + /** Gets the computed unique class id of a class type. */ + @builtin @unsafe export declare function classId(): u32; + + /** Iterates reference root objects. */ + @builtin @unsafe export declare function iterateRoots(fn: (ref: usize) => void): void; + + /** Registers a managed object to be tracked by the garbage collector. */ + @stub @unsafe export function register(ref: usize): void { + ERROR("stub: missing garbage collector"); + } + + /** Links a registered object with the registered object now referencing it. */ + @stub @unsafe export function link(ref: usize, parentRef: usize): void { + ERROR("stub: missing garbage collector"); + } + + /** Marks an object as being reachable. */ + @stub @unsafe export function mark(ref: usize): void { + ERROR("stub: missing garbage collector"); + } + + /** Performs a full garbage collection cycle. */ + @stub export function collect(): void { + WARNING("stub: missing garbage collector"); + } +} + +// TODO: move marking into userspace using builtins like iterateFields? diff --git a/std/assembly/memory.ts b/std/assembly/memory.ts new file mode 100644 index 00000000..c37ad9b5 --- /dev/null +++ b/std/assembly/memory.ts @@ -0,0 +1,62 @@ +import { memcmp, memmove, memset } from "./util/memory"; + +/** Memory manager interface. */ +export namespace memory { + + /** Gets the size of the memory in pages. */ + @builtin export declare function size(): i32; + + /** Grows the memory by the given size in pages and returns the previous size in pages. */ + @builtin @unsafe export declare function grow(pages: i32): i32; + + /** Fills a section in memory with the specified byte value. */ + @builtin @unsafe @inline export function fill(dst: usize, c: u8, n: usize): void { + memset(dst, c, n); // fallback if "bulk-memory" isn't enabled + } + + /** Copies a section of memory to another. Has move semantics. */ + @builtin @unsafe @inline export function copy(dst: usize, src: usize, n: usize): void { + memmove(dst, src, n); // fallback if "bulk-memory" isn't enabled + } + + /** Initializes a memory segment. */ + @unsafe export function init(segmentIndex: u32, srcOffset: usize, dstOffset: usize, n: usize): void { + ERROR("not implemented"); + } + + /** Drops a memory segment. */ + @unsafe export function drop(segmentIndex: u32): void { + ERROR("not implemented"); + } + + /** Dynamically allocates a section of memory and returns its address. */ + @stub @inline export function allocate(size: usize): usize { + ERROR("stub: missing memory manager"); + return unreachable(); + } + + /** Dynamically frees a section of memory by the previously allocated address. */ + @stub @unsafe @inline export function free(ptr: usize): void { + ERROR("stub: missing memory manager"); + } + + /** Resets the memory to its initial state. Arena allocator only. */ + @stub @unsafe @inline export function reset(): void { + ERROR("stub: not supported by memory manager"); + } + + /** Compares a section of memory to another. */ + @inline export function compare(vl: usize, vr: usize, n: usize): i32 { + return memcmp(vl, vr, n); + } + + /** Repeats a section of memory at a specific address. */ + @unsafe export function repeat(dst: usize, src: usize, srcLength: usize, count: usize): void { + var index: usize = 0; + var total = srcLength * count; + while (index < total) { + memory.copy(dst + index, src, srcLength); + index += srcLength; + } + } +} diff --git a/std/assembly/polyfills.ts b/std/assembly/polyfills.ts index 77228ec3..ab09a477 100644 --- a/std/assembly/polyfills.ts +++ b/std/assembly/polyfills.ts @@ -25,8 +25,7 @@ export function bswap(value: T): T { return value; } -@inline -export function bswap16(value: T): T { +@inline export function bswap16(value: T): T { if (isInteger() && sizeof() <= 4) { if (sizeof() == 2) { return ((value << 8) | ((value >> 8) & 0x00FF)); diff --git a/std/assembly/runtime.ts b/std/assembly/runtime.ts index b0edc9d7..4c046e2f 100644 --- a/std/assembly/runtime.ts +++ b/std/assembly/runtime.ts @@ -19,7 +19,7 @@ import { AL_MASK, MAX_SIZE_32 } from "./util/allocator"; // runtime will most likely change significantly once reftypes and WASM GC are a thing. /** Whether a GC is present or not. */ -@inline export const GC = isImplemented(gc.register) && isImplemented(gc.link); +@inline export const GC = isImplemented(gc.register); /** Size of the common runtime header. */ @inline export const HEADER_SIZE: usize = GC @@ -132,20 +132,9 @@ function unref(ref: usize): HEADER { if (GC) gc.link(changetype(ref), changetype(parentRef)); // tslint:disable-line } -export abstract class ArrayBufferBase { +export abstract class ArrayBufferView { @lazy static readonly MAX_BYTELENGTH: i32 = MAX_SIZE_32 - HEADER_SIZE; - constructor(length: i32) { - if (length > ArrayBufferBase.MAX_BYTELENGTH) throw new RangeError("Invalid array buffer length"); - return REGISTER(ALLOC(length)); - } - - get byteLength(): i32 { - return changetype
(changetype(this) - HEADER_SIZE).payloadSize; - } -} - -export abstract class ArrayBufferView { [key: number]: number; @unsafe data: ArrayBuffer; @@ -153,9 +142,8 @@ export abstract class ArrayBufferView { @unsafe dataEnd: usize; constructor(length: i32, alignLog2: i32) { - if (length > ArrayBufferBase.MAX_BYTELENGTH >>> alignLog2) throw new RangeError("Invalid length"); - var byteLength = length << alignLog2; - var buffer = new ArrayBuffer(byteLength); + if (length > ArrayBufferView.MAX_BYTELENGTH >>> alignLog2) throw new RangeError("Invalid length"); + var buffer = new ArrayBuffer(length << alignLog2); this.data = buffer; this.dataStart = changetype(buffer); this.dataEnd = changetype(buffer) + length; @@ -170,83 +158,7 @@ export abstract class ArrayBufferView { } get length(): i32 { - ERROR("not implemented"); + ERROR("concrete implementation must provide this"); return unreachable(); } } - -export abstract class StringBase { - @lazy static readonly MAX_LENGTH: i32 = (MAX_SIZE_32 - HEADER_SIZE) >> 1; - - get length(): i32 { - return changetype
(changetype(this) - HEADER_SIZE).payloadSize >> 1; - } -} - -import { memcmp, memmove, memset } from "./util/memory"; - -export namespace memory { - @builtin export declare function size(): i32; - - @builtin @unsafe export declare function grow(pages: i32): i32; - - @builtin @unsafe @inline export function fill(dst: usize, c: u8, n: usize): void { - memset(dst, c, n); // fallback if "bulk-memory" isn't enabled - } - - @builtin @unsafe @inline export function copy(dst: usize, src: usize, n: usize): void { - memmove(dst, src, n); // fallback if "bulk-memory" isn't enabled - } - - @unsafe export function init(segmentIndex: u32, srcOffset: usize, dstOffset: usize, n: usize): void { - ERROR("not implemented"); - } - - @unsafe export function drop(segmentIndex: u32): void { - ERROR("not implemented"); - } - - @stub @inline export function allocate(size: usize): usize { - ERROR("stub: missing memory manager"); - return unreachable(); - } - - @stub @unsafe @inline export function free(ptr: usize): void { - ERROR("stub: missing memory manager"); - } - - @stub @unsafe @inline export function reset(): void { - ERROR("stub: not supported by memory manager"); - } - - @inline export function compare(vl: usize, vr: usize, n: usize): i32 { - return memcmp(vl, vr, n); - } - - @unsafe export function repeat(dst: usize, src: usize, srcLength: usize, count: usize): void { - var index: usize = 0; - var total = srcLength * count; - while (index < total) { - memory.copy(dst + index, src, srcLength); - index += srcLength; - } - } -} - -export namespace gc { - @builtin @unsafe export declare function classId(): u32; - - @builtin @unsafe export declare function iterateRoots(fn: (ref: usize) => void): void; - - @stub @unsafe export function register(ref: usize): void { - ERROR("stub: missing garbage collector"); - } - - @stub @unsafe export function link(ref: usize, parentRef: usize): void { - ERROR("stub: missing garbage collector"); - } - - @stub export function collect(): void { - WARNING("stub: missing garbage collector"); - } -} diff --git a/std/assembly/string.ts b/std/assembly/string.ts index 4c89dbd6..e0c07daa 100644 --- a/std/assembly/string.ts +++ b/std/assembly/string.ts @@ -1,17 +1,13 @@ -import { - ALLOC, - REGISTER, - StringBase -} from "./runtime"; +import { HEADER, HEADER_SIZE, ALLOC, REGISTER, ArrayBufferView } from "./runtime"; +import { MAX_SIZE_32 } from "./util/allocator"; +import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./util/string"; -import { - compareImpl, - parse, - CharCode, - isWhiteSpaceOrLineTerminator -} from "./util/string"; +@sealed export abstract class String { + @lazy static readonly MAX_LENGTH: i32 = (MAX_SIZE_32 - HEADER_SIZE) >> alignof(); -@sealed export class String extends StringBase { + get length(): i32 { + return changetype
(changetype(this) - HEADER_SIZE).payloadSize >> 1; + } // TODO Add and handle second argument static fromCharCode(code: i32): String { diff --git a/std/assembly/vector.ts b/std/assembly/vector.ts index e39e402f..6aceaf46 100644 --- a/std/assembly/vector.ts +++ b/std/assembly/vector.ts @@ -1,2 +1,3 @@ +/** Vector abstraction. */ @sealed export class V128 { }