rtti & refactoring

This commit is contained in:
dcode
2019-04-06 20:17:48 +02:00
parent a9e4813798
commit e1070cee86
266 changed files with 23295 additions and 33925 deletions

View File

@ -1,8 +1,8 @@
/// <reference path="./collector/index.d.ts" />
import { MAX_BYTELENGTH, allocate, reallocate, discard, register } from "./util/runtime";
import { MAX_BYTELENGTH, allocate, reallocate, discard, register, NEWARRAY } from "./util/runtime";
import { COMPARATOR, SORT } from "./util/sort";
import { runtime, __runtime_id, __gc_mark_members } from "./runtime";
import { __runtime_id, __gc_mark_members } from "./runtime";
import { ArrayBuffer, ArrayBufferView } from "./arraybuffer";
import { itoa, dtoa, itoa_stream, dtoa_stream, MAX_DOUBLE_LENGTH } from "./util/number";
import { isArray as builtin_isArray } from "./builtins";
@ -42,9 +42,9 @@ export class Array<T> extends ArrayBufferView {
static create<T>(capacity: i32 = 0): Array<T> {
if (<u32>capacity > <u32>MAX_BYTELENGTH >>> alignof<T>()) throw new RangeError(E_INVALIDLENGTH);
var array = changetype<Array<T>>(runtime.newArray(capacity, alignof<T>(), __runtime_id<Array<T>>()));
memory.fill(array.dataStart, 0, <usize>array.dataLength);
array.length_ = 0; // !
var array = NEWARRAY<T>(capacity);
array.length_ = 0; // safe even if T is a non-nullable reference
memory.fill(array.dataStart, 0, array.dataLength);
return array;
}
@ -232,7 +232,9 @@ export class Array<T> extends ArrayBufferView {
concat(other: Array<T>): Array<T> {
var thisLen = this.length_;
var otherLen = select(0, other.length_, other === null);
var out = changetype<Array<T>>(runtime.newArray(thisLen + otherLen, alignof<T>(), __runtime_id<Array<T>>()));
var outLen = thisLen + otherLen;
if (<u32>outLen > <u32>MAX_BYTELENGTH >>> alignof<T>()) throw new Error(E_INVALIDLENGTH);
var out = NEWARRAY<T>(outLen);
var outStart = out.dataStart;
var thisSize = <usize>thisLen << alignof<T>();
if (isManaged<T>()) {
@ -320,7 +322,7 @@ export class Array<T> extends ArrayBufferView {
map<U>(callbackfn: (value: T, index: i32, array: Array<T>) => U): Array<U> {
var length = this.length_;
var out = changetype<Array<U>>(runtime.newArray(length, alignof<U>(), __runtime_id<Array<U>>()));
var out = NEWARRAY<U>(length);
var outStart = out.dataStart;
for (let index = 0; index < min(length, this.length_); ++index) {
let value = load<T>(this.dataStart + (<usize>index << alignof<T>()));
@ -346,7 +348,7 @@ export class Array<T> extends ArrayBufferView {
}
filter(callbackfn: (value: T, index: i32, array: Array<T>) => bool): Array<T> {
var result = changetype<Array<T>>(runtime.newArray(0, alignof<T>(), __runtime_id<Array<T>>()));
var result = NEWARRAY<T>(0);
for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) {
let value = load<T>(this.dataStart + (<usize>index << alignof<T>()));
if (callbackfn(value, index, this)) result.push(value);
@ -434,7 +436,7 @@ export class Array<T> extends ArrayBufferView {
begin = begin < 0 ? max(begin + length, 0) : min(begin, length);
end = end < 0 ? max(end + length, 0) : min(end , length);
length = max(end - begin, 0);
var slice = changetype<Array<T>>(runtime.newArray(length, alignof<T>(), __runtime_id<Array<T>>()));
var slice = NEWARRAY<T>(length);
var sliceBase = slice.dataStart;
var thisBase = this.dataStart + (<usize>begin << alignof<T>());
if (isManaged<T>()) {
@ -466,7 +468,7 @@ export class Array<T> extends ArrayBufferView {
var length = this.length_;
start = start < 0 ? max<i32>(length + start, 0) : min<i32>(start, length);
deleteCount = max<i32>(min<i32>(deleteCount, length - start), 0);
var result = changetype<Array<T>>(runtime.newArray(deleteCount, alignof<T>(), __runtime_id<Array<T>>()));
var result = NEWARRAY<T>(deleteCount);
var resultStart = result.dataStart;
var thisStart = this.dataStart;
var thisBase = thisStart + (<usize>start << alignof<T>());

View File

@ -1715,3 +1715,36 @@ export namespace v8x16 {
l8: u8, l9: u8, l10: u8, l11: u8, l12: u8, l13: u8, l14: u8, l15: u8
): v128;
}
// @ts-ignore: decorator
@builtin
export declare function ERROR(message?: string): void;
// @ts-ignore: decorator
@builtin
export declare function WARNING(message?: string): void;
// @ts-ignore: decorator
@builtin
export declare function INFO(message?: string): void;
// @ts-ignore: decorator
@external("env", "abort")
declare function abort(
message?: string | null,
fileName?: string | null,
lineNumber?: u32,
columnNumber?: u32
): void;
// @ts-ignore: decorator
@external("env", "trace")
declare function trace(
message: string,
n?: i32,
a0?: f64,
a1?: f64,
a2?: f64,
a3?: f64,
a4?: f64
): void;

View File

@ -0,0 +1 @@
These source files are used by both the standard library *and* compiler. Must remain portable.

View File

@ -0,0 +1,9 @@
/** Indicates module capabilities. */
export const enum Capability {
/** No specific capabilities. */
NONE = 0,
/** Uses WebAssembly with 64-bit pointers. */
WASM64 = 1 << 0,
/** Garbage collector is present (full runtime header). */
GC = 1 << 1
}

View File

@ -0,0 +1,15 @@
/** Indicates specific features to activate. */
export const enum Feature {
/** No additional features. */
NONE = 0,
/** Sign extension operations. */
SIGN_EXTENSION = 1 << 0, // see: https://github.com/WebAssembly/sign-extension-ops
/** Mutable global imports and exports. */
MUTABLE_GLOBAL = 1 << 1, // see: https://github.com/WebAssembly/mutable-global
/** Bulk memory operations. */
BULK_MEMORY = 1 << 2, // see: https://github.com/WebAssembly/bulk-memory-operations
/** SIMD types and operations. */
SIMD = 1 << 3, // see: https://github.com/WebAssembly/simd
/** Threading and atomic operations. */
THREADS = 1 << 4 // see: https://github.com/WebAssembly/threads
}

View File

@ -0,0 +1,60 @@
// ╒═════════════════════ RTTI interpretation ═════════════════════╕
// 3 2 1
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤ ◄─ RTTI_BASE
// │ count │
// ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
// │ reserved │
// ╞═══════════════════════════════════════════════════════════════╡ ┐
// │ RTTIData#flags [id=1] │ id=1..count
// ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
// │ RTTIData#base [id=1] │
// ├───────────────────────────────────────────────────────────────┤
// │ ... │
/** Runtime type information data structure. */
@unmanaged
export class RTTIData {
flags: RTTIFlags;
base: u32;
}
/** Runtime type information flags. */
export const enum RTTIFlags {
/** No specific flags. */
NONE = 0,
/** Type is an `Array`. */
ARRAY = 1 << 0,
/** Type is a `Set`. */
SET = 1 << 1,
/** Type is a `Map`. */
MAP = 1 << 2,
/** Value alignment of 1 byte. */
VALUE_ALIGN_0 = 1 << 3,
/** Value alignment of 2 bytes. */
VALUE_ALIGN_1 = 1 << 4,
/** Value alignment of 4 bytes. */
VALUE_ALIGN_2 = 1 << 5,
/** Value alignment of 8 bytes. */
VALUE_ALIGN_3 = 1 << 6,
/** Value alignment of 16 bytes. */
VALUE_ALIGN_4 = 1 << 7,
/** Value type is nullable. */
VALUE_NULLABLE = 1 << 8,
/** Value type is managed. */
VALUE_MANAGED = 1 << 9,
/** Key alignment of 1 byte. */
KEY_ALIGN_0 = 1 << 10,
/** Key alignment of 2 bytes. */
KEY_ALIGN_1 = 1 << 11,
/** Key alignment of 4 bytes. */
KEY_ALIGN_2 = 1 << 12,
/** Key alignment of 8 bytes. */
KEY_ALIGN_3 = 1 << 13,
/** Key alignment of 16 bytes. */
KEY_ALIGN_4 = 1 << 14,
/** Key type is nullable. */
KEY_NULLABLE = 1 << 15,
/** Key type is managed. */
KEY_MANAGED = 1 << 16
}

View File

@ -0,0 +1,9 @@
/** Compilation target. */
export enum Target {
/** WebAssembly with 32-bit pointers. */
WASM32,
/** WebAssembly with 64-bit pointers. Experimental and not supported by any runtime yet. */
WASM64,
/** Portable. */
JS
}

View File

@ -1,11 +0,0 @@
// @ts-ignore: decorator
@builtin
export declare function ERROR(message?: string): void;
// @ts-ignore: decorator
@builtin
export declare function WARNING(message?: string): void;
// @ts-ignore: decorator
@builtin
export declare function INFO(message?: string): void;

View File

@ -1,16 +0,0 @@
declare function abort(
message?: string | null,
fileName?: string | null,
lineNumber?: u32,
columnNumber?: u32
): void;
declare function trace(
message: string,
n?: i32,
a0?: f64,
a1?: f64,
a2?: f64,
a3?: f64,
a4?: f64
): void;

View File

@ -1485,13 +1485,6 @@ interface TypedPropertyDescriptor<T> {
set?(value: T): void;
}
/** Annotates an element as a program global. */
declare function global(
target: any,
propertyKey: string,
descriptor: TypedPropertyDescriptor<any>
): TypedPropertyDescriptor<any> | void;
/** Annotates a method as a binary operator overload for the specified `token`. */
declare function operator(token:
"[]" | "[]=" | "{}" | "{}=" | "==" | "!=" | ">" | "<" | "<=" | ">=" |
@ -1526,6 +1519,9 @@ declare namespace operator {
) => TypedPropertyDescriptor<any> | void;
}
/** Annotates an element as a program global. */
declare function global(...args: any[]): any;
/** Annotates a class as being unmanaged with limited capabilities. */
declare function unmanaged(constructor: Function): void;

View File

@ -3,63 +3,18 @@
import { HEADER, HEADER_SIZE, allocate, register } from "./util/runtime";
import { E_NOTIMPLEMENTED } from "./util/error";
import { memory } from "./memory";
import { ArrayBufferView } from "./arraybuffer";
import { RTTIFlags, RTTIData } from "./common/rtti";
// @ts-ignore: decorator
@builtin
export declare const RTTI_BASE: usize;
/** Gets the computed unique id of a class type. */
// @ts-ignore: decorator
@builtin
export declare function __runtime_id<T>(): u32;
/** Tests if a managed class is the same as or a superclass of another. */
// @ts-ignore: decorator
@builtin
export declare function __runtime_instanceof(id: u32, superId: u32): bool;
/** Runtime flags. */
const enum RuntimeFlags { // keep in sync with src/program.ts
NONE = 0,
/** Type is an `Array`. */
ARRAY = 1 << 0,
/** Type is a `Set`. */
SET = 1 << 1,
/** Type is a `Map`. */
MAP = 1 << 2,
/** Value alignment of 1 byte. */
VALUE_ALIGN_0 = 1 << 3,
/** Value alignment of 2 bytes. */
VALUE_ALIGN_1 = 1 << 4,
/** Value alignment of 4 bytes. */
VALUE_ALIGN_2 = 1 << 5,
/** Value alignment of 8 bytes. */
VALUE_ALIGN_3 = 1 << 6,
/** Value alignment of 16 bytes. */
VALUE_ALIGN_4 = 1 << 7,
/** Value type is nullable. */
VALUE_NULLABLE = 1 << 8,
/** Value type is managed. */
VALUE_MANAGED = 1 << 9,
/** Key alignment of 1 byte. */
KEY_ALIGN_0 = 1 << 10,
/** Key alignment of 2 bytes. */
KEY_ALIGN_1 = 1 << 11,
/** Key alignment of 4 bytes. */
KEY_ALIGN_2 = 1 << 12,
/** Key alignment of 8 bytes. */
KEY_ALIGN_3 = 1 << 13,
/** Key alignment of 16 bytes. */
KEY_ALIGN_4 = 1 << 14,
/** Key type is nullable. */
KEY_NULLABLE = 1 << 15,
/** Key type is managed. */
KEY_MANAGED = 1 << 16
}
/** Gets the runtime flags of the managed type represented by the specified runtime id. */
// @ts-ignore: decorator
@builtin
export declare function __runtime_flags(id: u32): RuntimeFlags;
/** Marks root objects when a tracing GC is present. */
// @ts-ignore: decorator
@unsafe @builtin
@ -76,20 +31,24 @@ export class runtime {
private constructor() { return unreachable(); }
/** Determines whether a managed object is considered to be an instance of the class represented by the specified runtime id. */
@unsafe
static instanceof(ref: usize, id: u32): bool { // keyword
return ref
? __runtime_instanceof(
changetype<HEADER>(ref - HEADER_SIZE).classId,
id
)
: false;
static instanceof(ref: usize, superId: u32): bool { // keyword
var id = changetype<HEADER>(ref - HEADER_SIZE).classId;
var ptr = RTTI_BASE;
if (id && id <= load<u32>(ptr)) {
do if (id == superId) return true;
while (id = changetype<RTTIData>(ptr + id * offsetof<RTTIData>()).base);
}
return false;
}
}
export namespace runtime {
export function flags(id: u32): RuntimeFlags {
return __runtime_flags(id);
/** Gets the runtime flags of the managed type represented by the specified runtime id. */
export function flags(id: u32): RTTIFlags {
var ptr = RTTI_BASE;
return !id || id > load<u32>(ptr)
? unreachable()
: changetype<RTTIData>(ptr + id * offsetof<RTTIData>()).flags;
}
/** Allocates and registers, but doesn't initialize the data of, a new managed object of the specified kind. */
@ -113,57 +72,36 @@ export namespace runtime {
return newObject(byteLength, __runtime_id<ArrayBuffer>());
}
/** Allocates and registers, but doesn't initialize the data of, a new `Array` of the specified length and element alignment.*/
/** Allocates and registers a new `Array` of the specified kind using the given backing buffer.*/
// @ts-ignore: decorator
@unsafe
export function newArray(length: i32, alignLog2: usize, id: u32, data: usize = 0): usize {
// TODO: This API isn't great, but the compiler requires it for array literals anyway,
// that is when an array literal is encountered and its data is static, this function is
// called and the static buffer provided as `data`. This function can also be used to
// create typed arrays in that `Array` also implements `ArrayBufferView` but has an
// additional `.length_` property that remains unused overhead for typed arrays.
var array = register(allocate(offsetof<i32[]>()), id);
var bufferSize = <usize>length << alignLog2;
var buffer = register(allocate(bufferSize), __runtime_id<ArrayBuffer>());
export function newArray(id: u32, buffer: usize): usize {
var flags = runtime.flags(id); // traps if invalid
var alignLog2 = (<u32>flags / RTTIFlags.VALUE_ALIGN_0) & 31;
var byteLength: i32;
if (!buffer) buffer = newArrayBuffer(byteLength = 0);
else byteLength = changetype<ArrayBuffer>(buffer).byteLength;
var array = newObject(id, offsetof<i32[]>());
changetype<ArrayBufferView>(array).data = changetype<ArrayBuffer>(buffer); // links
changetype<ArrayBufferView>(array).dataStart = buffer;
changetype<ArrayBufferView>(array).dataLength = bufferSize;
store<i32>(changetype<usize>(array), length, offsetof<i32[]>("length_"));
if (data) memory.copy(buffer, data, bufferSize);
// TODO: a way to determine whether the array has managed elements that must be linked?
changetype<ArrayBufferView>(array).dataLength = byteLength;
store<i32>(changetype<usize>(array), byteLength >>> alignLog2, offsetof<i32[]>("length_"));
if (flags & RTTIFlags.VALUE_MANAGED) {
let cur = buffer;
let end = cur + <usize>byteLength;
while (cur < end) {
let ref = load<usize>(cur);
if (ref) {
if (isDefined(__ref_link)) __ref_link(ref, array);
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
cur += sizeof<usize>();
}
}
return array;
}
// export function newArray2(id: u32, buffer: usize): usize {
// var flags = __runtime_flags(id); // traps if invalid
// var alignLog2 = (flags / RuntimeFlags.VALUE_ALIGN_0) & 31;
// var byteLength: i32;
// if (!buffer) {
// buffer = newArrayBuffer(byteLength = 0);
// } else {
// byteLength = changetype<ArrayBuffer>(buffer).byteLength;
// }
// var array = register(allocate(offsetof<i32[]>()), id);
// changetype<ArrayBufferView>(array).data = changetype<ArrayBuffer>(buffer); // links
// changetype<ArrayBufferView>(array).dataStart = buffer;
// changetype<ArrayBufferView>(array).dataLength = byteLength;
// store<i32>(changetype<usize>(array), byteLength >>> alignLog2, offsetof<i32[]>("length_"));
// if (flags & RuntimeFlags.VALUE_MANAGED) {
// let cur = buffer;
// let end = cur + <usize>byteLength;
// while (cur < end) {
// let ref = load<usize>(cur);
// if (ref) {
// if (isDefined(__ref_link)) __ref_link(ref, array);
// else if (isDefined(__ref_retain)) __ref_retain(ref);
// else assert(false);
// }
// cur += sizeof<usize>();
// }
// }
// return array;
// }
/** Retains a managed object externally, making sure that it doesn't become collected. */
// @ts-ignore: decorator
@unsafe

View File

@ -1 +1,3 @@
import "allocator/arena";
export { runtime as $ };

View File

@ -1,10 +1,10 @@
/// <reference path="./collector/index.d.ts" />
import { MAX_SIZE_32 } from "./util/allocator";
import { HEADER, HEADER_SIZE, allocate, register } from "./util/runtime";
import { HEADER, HEADER_SIZE, allocate, register, NEWARRAY } from "./util/runtime";
import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./util/string";
import { E_INVALIDLENGTH } from "./util/error";
import { runtime, __runtime_id } from "./runtime";
import { __runtime_id } from "./runtime";
import { ArrayBufferView } from "./arraybuffer";
@sealed export abstract class String {
@ -362,16 +362,16 @@ import { ArrayBufferView } from "./arraybuffer";
split(separator: String | null = null, limit: i32 = i32.MAX_VALUE): String[] {
assert(this !== null);
if (!limit) return changetype<String[]>(runtime.newArray(0, alignof<String>(), __runtime_id<String[]>()));
if (!limit) return NEWARRAY<String>(0);
if (separator === null) return <String[]>[this];
var length: isize = this.length;
var sepLen: isize = separator.length;
if (limit < 0) limit = i32.MAX_VALUE;
if (!sepLen) {
if (!length) return changetype<String[]>(runtime.newArray(0, alignof<String>(), __runtime_id<String>()));
if (!length) return NEWARRAY<String>(0);
// split by chars
length = min<isize>(length, <isize>limit);
let result = changetype<String[]>(runtime.newArray(length, alignof<String>(), __runtime_id<String[]>()));
let result = NEWARRAY<String>(length);
let resultStart = changetype<ArrayBufferView>(result).dataStart;
for (let i: isize = 0; i < length; ++i) {
let charStr = allocate(2);
@ -385,11 +385,11 @@ import { ArrayBufferView } from "./arraybuffer";
}
return result;
} else if (!length) {
let result = changetype<String[]>(runtime.newArray(1, alignof<String>(), __runtime_id<String[]>()));
let result = NEWARRAY<String>(1);
store<string>(changetype<ArrayBufferView>(result).dataStart, ""); // no need to register/link
return result;
}
var result = changetype<String[]>(runtime.newArray(0, alignof<String>(), __runtime_id<String[]>()));
var result = NEWARRAY<String>(0);
var end = 0, start = 0, i = 0;
while ((end = this.indexOf(separator!, start)) != -1) {
let len = end - start;
@ -404,7 +404,7 @@ import { ArrayBufferView } from "./arraybuffer";
start = end + sepLen;
}
if (!start) {
let result = changetype<String[]>(runtime.newArray(1, alignof<String>(), __runtime_id<String[]>()));
let result = NEWARRAY<String>(1);
unchecked(result[0] = this);
return result;
}

View File

@ -1,4 +1,7 @@
import { AL_MASK, MAX_SIZE_32 } from "./allocator";
import { __runtime_id } from "../runtime";
import { Array } from "../array";
import { ArrayBufferView } from "../arraybuffer";
/**
* The common runtime object header prepended to all managed objects. Has a size of 16 bytes in
@ -136,22 +139,22 @@ export function register(ref: usize, id: u32): usize {
return ref;
}
/** Allocates and registers, but doesn't initialize the data of, a new `Array` of the specified length and element alignment. */
// @ts-ignore: decorator
@unsafe
export function newArray(length: i32, alignLog2: usize, id: u32, data: usize = 0): usize {
// TODO: This API isn't great, but the compiler requires it for array literals anyway,
// that is when an array literal is encountered and its data is static, this function is
// called and the static buffer provided as `data`. This function can also be used to
// create typed arrays in that `Array` also implements `ArrayBufferView` but has an
// additional `.length_` property that remains unused overhead for typed arrays.
var array = newObject(offsetof<i32[]>(), id);
var bufferSize = <u32>length << alignLog2;
var buffer = newArrayBuffer(bufferSize);
export function makeArray(length: i32, alignLog2: usize, id: u32, data: usize = 0): usize {
var array = register(allocate(offsetof<i32[]>()), id);
var bufferSize = <usize>length << alignLog2;
var buffer = register(allocate(bufferSize), __runtime_id<ArrayBuffer>());
changetype<ArrayBufferView>(array).data = changetype<ArrayBuffer>(buffer); // links
changetype<ArrayBufferView>(array).dataStart = buffer;
changetype<ArrayBufferView>(array).dataLength = bufferSize;
store<i32>(changetype<usize>(array), length, offsetof<i32[]>("length_"));
if (data) memory.copy(buffer, data, <usize>bufferSize);
if (data) memory.copy(buffer, data, bufferSize);
return array;
}
// @ts-ignore: decorator
@inline
export function NEWARRAY<T>(length: i32): Array<T> {
return changetype<Array<T>>(makeArray(length, alignof<T>(), __runtime_id<Array<T>>(), 0));
}