Rename memory instructions; Rework constant handling (#177)

* Rename memory instructions as proposed by the bulk-memory-operations spec.
* Rename memory manager functions to memory.* as well
* Remove automatic inlining of constant globals (Binaryen does this now)
* Improve 'const' enum compatibility
* Improve module-level export generation
* Enable the inline decorator for constant variables
* Add ERROR, WARNING and INFO macros that emit a user-defined diagnostic
* Reintroduce builtin decorator so these can appear anywhere in stdlib again
* Inline isNaN and isFinite by default
* Make an interface around gc.* similar to memory.*
* Emit an error when trying to inline a mutable variable
* Slim down CI stages
* Add a more convenient tracing utility for debugging
* Implement some prequesites for an eventual bundled GC
This commit is contained in:
Daniel Wirtz
2018-07-20 22:53:33 +02:00
committed by GitHub
parent 34e8facfdc
commit 39b489bee2
196 changed files with 28714 additions and 6674 deletions

View File

@ -1,7 +1,7 @@
/**
* Arena Memory Allocator
*
* Provides a `reset_memory` function to reset the heap to its initial state. A user has to make
* Provides a `memory.reset` function to reset the heap to its initial state. A user has to make
* sure that there are no more references to cleared memory afterwards. Always aligns to 8 bytes.
*
* @module std/assembly/allocator/arena
@ -12,18 +12,19 @@ import { AL_MASK, MAX_SIZE_32 } from "../internal/allocator";
var startOffset: usize = (HEAP_BASE + AL_MASK) & ~AL_MASK;
var offset: usize = startOffset;
@global
export function allocate_memory(size: usize): usize {
// Memory allocator interface
@global export function __memory_allocate(size: usize): usize {
if (size) {
if (size > MAX_SIZE_32) unreachable();
let ptr = offset;
let newPtr = (ptr + size + AL_MASK) & ~AL_MASK;
let pagesBefore = current_memory();
let pagesBefore = memory.size();
if (newPtr > <usize>pagesBefore << 16) {
let pagesNeeded = ((newPtr - ptr + 0xffff) & ~0xffff) >>> 16;
let pagesWanted = max(pagesBefore, pagesNeeded); // double memory
if (grow_memory(pagesWanted) < 0) {
if (grow_memory(pagesNeeded) < 0) {
if (memory.grow(pagesWanted) < 0) {
if (memory.grow(pagesNeeded) < 0) {
unreachable(); // out of memory
}
}
@ -34,12 +35,8 @@ export function allocate_memory(size: usize): usize {
return 0;
}
@global
export function free_memory(ptr: usize): void {
// nop
}
@global export function __memory_free(ptr: usize): void { /* nop */ }
@global
export function reset_memory(): void {
@global export function __memory_reset(): void {
offset = startOffset;
}

View File

@ -176,10 +176,10 @@ function update_max_ptr(new_value: usize): i32 {
// if (brk(new_value)) {
// return 0;
// }
let oldPages = <u32>current_memory();
let newPages = <u32>(((new_value + 0xffff) & ~0xffff) >> 16);
let oldPages = <i32>memory.size();
let newPages = <i32>(((new_value + 0xffff) & ~0xffff) >>> 16);
assert(newPages > oldPages);
if (grow_memory(newPages - oldPages) < 0) {
if (memory.grow(newPages - oldPages) < 0) {
return 0;
}
// max_ptr = new_value;
@ -338,8 +338,9 @@ function lower_bucket_limit(bucket: usize): u32 {
return 1;
}
@global
export function allocate_memory(request: usize): usize {
// Memory allocator interface
@global export function __memory_allocate(request: usize): usize {
var original_bucket: usize, bucket: usize;
/*
@ -357,7 +358,7 @@ export function allocate_memory(request: usize): usize {
if (base_ptr == 0) {
// base_ptr = max_ptr = (uint8_t *)sbrk(0);
base_ptr = (NODE_IS_SPLIT_END + 7) & ~7; // must be aligned
max_ptr = <usize>current_memory() << 16; // must grow first
max_ptr = <usize>memory.size() << 16; // must grow first
bucket_limit = BUCKET_COUNT - 1;
if (!update_max_ptr(base_ptr + List.SIZE)) {
return 0;
@ -473,8 +474,7 @@ export function allocate_memory(request: usize): usize {
return 0;
}
@global
export function free_memory(ptr: usize): void {
@global export function __memory_free(ptr: usize): void {
var bucket: usize, i: usize;
/*
@ -538,8 +538,3 @@ export function free_memory(ptr: usize): void {
*/
list_push(buckets$get(bucket), changetype<List>(ptr_for_node(i, bucket)));
}
@global
export function reset_memory(): void {
unreachable();
}

View File

@ -11,17 +11,12 @@
declare function _malloc(size: usize): usize;
declare function _free(ptr: usize): void;
@global
export function allocate_memory(size: usize): usize {
// Memory allocator interface
@global export function __memory_allocate(size: usize): usize {
return _malloc(size);
}
@global
export function free_memory(ptr: usize): void {
@global export function __memory_free(ptr: usize): void {
_free(ptr);
}
@global
export function reset_memory(): void {
unreachable();
}

View File

@ -10,17 +10,12 @@
declare function malloc(size: usize): usize;
declare function free(ptr: usize): void;
@global
export function allocate_memory(size: usize): usize {
// Memory allocator interface
@global export function __memory_allocate(size: usize): usize {
return malloc(size);
}
@global
export function free_memory(ptr: usize): void {
@global export function __memory_free(ptr: usize): void {
free(ptr);
}
@global
export function reset_memory(): void {
unreachable();
}

View File

@ -433,19 +433,18 @@ function fls<T>(word: T): T {
/** Reference to the initialized {@link Root} structure, once initialized. */
var ROOT: Root = changetype<Root>(0);
// External interface
// Memory allocator interface
/** Allocates a chunk of memory. */
@global
export function allocate_memory(size: usize): usize {
@global export function __memory_allocate(size: usize): usize {
// initialize if necessary
var root = ROOT;
if (!root) {
let rootOffset = (HEAP_BASE + AL_MASK) & ~AL_MASK;
let pagesBefore = current_memory();
let pagesBefore = memory.size();
let pagesNeeded = <i32>((((rootOffset + Root.SIZE) + 0xffff) & ~0xffff) >>> 16);
if (pagesNeeded > pagesBefore && grow_memory(pagesNeeded - pagesBefore) < 0) unreachable();
if (pagesNeeded > pagesBefore && memory.grow(pagesNeeded - pagesBefore) < 0) unreachable();
ROOT = root = changetype<Root>(rootOffset);
root.tailRef = 0;
root.flMap = 0;
@ -455,7 +454,7 @@ export function allocate_memory(size: usize): usize {
root.setHead(fl, sl, null);
}
}
root.addMemory((rootOffset + Root.SIZE + AL_MASK) & ~AL_MASK, current_memory() << 16);
root.addMemory((rootOffset + Root.SIZE + AL_MASK) & ~AL_MASK, memory.size() << 16);
}
// search for a suitable block
@ -469,15 +468,15 @@ export function allocate_memory(size: usize): usize {
if (!block) {
// request more memory
let pagesBefore = current_memory();
let pagesBefore = memory.size();
let pagesNeeded = <i32>(((size + 0xffff) & ~0xffff) >>> 16);
let pagesWanted = max(pagesBefore, pagesNeeded); // double memory
if (grow_memory(pagesWanted) < 0) {
if (grow_memory(pagesNeeded) < 0) {
if (memory.grow(pagesWanted) < 0) {
if (memory.grow(pagesNeeded) < 0) {
unreachable(); // out of memory
}
}
let pagesAfter = current_memory();
let pagesAfter = memory.size();
root.addMemory(<usize>pagesBefore << 16, <usize>pagesAfter << 16);
block = assert(root.search(size)); // must be found now
}
@ -490,8 +489,7 @@ export function allocate_memory(size: usize): usize {
}
/** Frees the chunk of memory at the specified address. */
@global
export function free_memory(data: usize): void {
@global export function __memory_free(data: usize): void {
if (data) {
let root = ROOT;
if (root) {
@ -504,7 +502,6 @@ export function free_memory(data: usize): void {
}
}
@global
export function reset_memory(): void {
@global export function __memory_reset(): void {
unreachable();
}

View File

@ -25,7 +25,7 @@ export class Array<T> {
var buffer = allocUnsafe(byteLength);
this.buffer_ = buffer;
this.length_ = length;
set_memory(
memory.fill(
changetype<usize>(buffer) + HEADER_SIZE_AB,
0,
<usize>byteLength
@ -216,7 +216,7 @@ export class Array<T> {
var buffer = this.buffer_;
var element = loadUnsafe<T,T>(buffer, 0);
var lastIndex = length - 1;
move_memory(
memory.copy(
changetype<usize>(buffer) + HEADER_SIZE_AB,
changetype<usize>(buffer) + HEADER_SIZE_AB + sizeof<T>(),
<usize>lastIndex << alignof<T>()
@ -246,7 +246,7 @@ export class Array<T> {
capacity = buffer.byteLength >>> alignof<T>();
this.buffer_ = buffer;
}
move_memory(
memory.copy(
changetype<usize>(buffer) + HEADER_SIZE_AB + sizeof<T>(),
changetype<usize>(buffer) + HEADER_SIZE_AB,
<usize>(capacity - 1) << alignof<T>()
@ -267,7 +267,7 @@ export class Array<T> {
assert(newLength >= 0);
var sliced = new Array<T>(newLength);
if (newLength) {
move_memory(
memory.copy(
changetype<usize>(sliced.buffer_) + HEADER_SIZE_AB,
changetype<usize>(this.buffer_) + HEADER_SIZE_AB + (<usize>begin << alignof<T>()),
<usize>newLength << alignof<T>()
@ -283,7 +283,7 @@ export class Array<T> {
if (start >= length) return;
deleteCount = min(deleteCount, length - start);
var buffer = this.buffer_;
move_memory(
memory.copy(
changetype<usize>(buffer) + HEADER_SIZE_AB + (<usize>start << alignof<T>()),
changetype<usize>(buffer) + HEADER_SIZE_AB + (<usize>(start + deleteCount) << alignof<T>()),
<usize>deleteCount << alignof<T>()

View File

@ -12,7 +12,7 @@ export class ArrayBuffer {
constructor(length: i32, unsafe: bool = false) {
if (<u32>length > <u32>MAX_BLENGTH) throw new RangeError("Invalid array buffer length");
var buffer = allocUnsafe(length);
if (!unsafe) set_memory(changetype<usize>(buffer) + HEADER_SIZE, 0, <usize>length);
if (!unsafe) memory.fill(changetype<usize>(buffer) + HEADER_SIZE, 0, <usize>length);
return buffer;
}
@ -24,7 +24,7 @@ export class ArrayBuffer {
else end = min(end, len);
var newLen = max(end - begin, 0);
var buffer = allocUnsafe(newLen);
move_memory(changetype<usize>(buffer) + HEADER_SIZE, changetype<usize>(this) + HEADER_SIZE + begin, newLen);
memory.copy(changetype<usize>(buffer) + HEADER_SIZE, changetype<usize>(this) + HEADER_SIZE + begin, newLen);
return buffer;
}
}

View File

@ -1,147 +1,103 @@
export declare function isInteger<T>(value?: T): bool;
/* tslint:disable */
export declare function isFloat<T>(value?: T): bool;
@builtin @inline export const NaN: f64 = 0 / 0;
@builtin @inline export const Infinity: f64 = 1 / 0;
@builtin export declare const HEAP_BASE: usize;
export declare function isSigned<T>(value?: T): bool;
@builtin export declare function isInteger<T>(value?: T): bool;
@builtin export declare function isFloat<T>(value?: T): bool;
@builtin export declare function isSigned<T>(value?: T): bool;
@builtin export declare function isReference<T>(value?: T): bool;
@builtin export declare function isString<T>(value?: T): bool;
@builtin export declare function isArray<T>(value?: T): bool;
@builtin export declare function isDefined(expression: void): bool;
@builtin export declare function isConstant(expression: void): bool;
@inline export function isNaN<T>(value: T): bool { return value != value; }
@inline export function isFinite<T>(value: T): bool { return value - value == 0; }
export declare function isReference<T>(value?: T): bool;
@builtin export declare function clz<T>(value: T): T;
@builtin export declare function ctz<T>(value: T): T;
@builtin export declare function popcnt<T>(value: T): T;
@builtin export declare function rotl<T>(value: T, shift: T): T;
@builtin export declare function rotr<T>(value: T, shift: T): T;
@builtin export declare function abs<T>(value: T): T;
@builtin export declare function max<T>(left: T, right: T): T;
@builtin export declare function min<T>(left: T, right: T): T;
@builtin export declare function ceil<T>(value: T): T;
@builtin export declare function floor<T>(value: T): T;
@builtin export declare function copysign<T>(left: T, right: T): T;
@builtin export declare function nearest<T>(value: T): T;
@builtin export declare function reinterpret<T>(value: void): T;
@builtin export declare function sqrt<T>(value: T): T;
@builtin export declare function trunc<T>(value: T): T;
@builtin export declare function load<T>(offset: usize, constantOffset?: usize): T;
@builtin export declare function store<T>(offset: usize, value: void, constantOffset?: usize): void;
@builtin export declare function sizeof<T>(): usize; // | u32 / u64
@builtin export declare function alignof<T>(): usize; // | u32 / u64
@builtin export declare function offsetof<T>(fieldName?: string): usize; // | u32 / u64
@builtin export declare function select<T>(ifTrue: T, ifFalse: T, condition: bool): T;
@builtin export declare function unreachable(): void;
@builtin export declare function changetype<T>(value: void): T;
@builtin export declare function assert<T>(isTrueish: T, message?: string): T;
@builtin export declare function unchecked<T>(expr: T): T;
@builtin export declare function call_indirect<T>(target: void, ...args: void[]): T;
export declare function isString<T>(value?: T): bool;
export declare function isArray<T>(value?: T): bool;
export declare function isDefined(expression: void): bool;
export declare function isConstant(expression: void): bool;
export const NaN: f64 = 0 / 0;
export const Infinity: f64 = 1 / 0;
export function isNaN<T>(value: T): bool {
return value != value;
}
export function isFinite<T>(value: T): bool {
return value - value == 0;
}
export declare function clz<T>(value: T): T;
export declare function ctz<T>(value: T): T;
export declare function popcnt<T>(value: T): T;
export declare function rotl<T>(value: T, shift: T): T;
export declare function rotr<T>(value: T, shift: T): T;
export declare function abs<T>(value: T): T;
export declare function max<T>(left: T, right: T): T;
export declare function min<T>(left: T, right: T): T;
export declare function ceil<T>(value: T): T;
export declare function floor<T>(value: T): T;
export declare function copysign<T>(left: T, right: T): T;
export declare function nearest<T>(value: T): T;
export declare function reinterpret<T>(value: void): T;
export declare function sqrt<T>(value: T): T;
export declare function trunc<T>(value: T): T;
export declare function load<T>(offset: usize, constantOffset?: usize): T;
export declare function store<T>(offset: usize, value: void, constantOffset?: usize): void;
export declare function sizeof<T>(): usize; // | u32 / u64
export declare function alignof<T>(): usize; // | u32 / u64
export declare function offsetof<T>(fieldName?: string): usize; // | u32 / u64
export declare function select<T>(ifTrue: T, ifFalse: T, condition: bool): T;
export declare function unreachable(): void;
export declare function current_memory(): i32;
export declare function grow_memory(pages: i32): i32;
// export declare function move_memory(dest: usize, src: usize: n: usize): void;
// export declare function set_memory(dest: usize, value: u32, n: usize): void;
export declare function changetype<T>(value: void): T;
export declare function assert<T>(isTrueish: T, message?: string): T;
export declare function unchecked<T>(expr: T): T;
export declare function call_indirect<T>(target: void, ...args: void[]): T;
export declare function i8(value: void): i8;
@builtin export declare function i8(value: void): i8;
export namespace i8 {
export const MIN_VALUE: i8 = -128;
export const MAX_VALUE: i8 = 127;
}
export declare function i16(value: void): i16;
@builtin export declare function i16(value: void): i16;
export namespace i16 {
export const MIN_VALUE: i16 = -32768;
export const MAX_VALUE: i16 = 32767;
}
export declare function i32(value: void): i32;
@builtin export declare function i32(value: void): i32;
export namespace i32 {
export const MIN_VALUE: i32 = -2147483648;
export const MAX_VALUE: i32 = 2147483647;
export declare function clz(value: i32): i32;
export declare function ctz(value: i32): i32;
export declare function popcnt(value: i32): i32;
export declare function rotl(value: i32, shift: i32): i32;
export declare function rotr(value: i32, shift: i32): i32;
export declare function reinterpret_f32(value: f32): i32;
export declare function load8_s(offset: usize, constantOffset?: usize): i32;
export declare function load8_u(offset: usize, constantOffset?: usize): i32;
export declare function load16_s(offset: usize, constantOffset?: usize): i32;
export declare function load16_u(offset: usize, constantOffset?: usize): i32;
export declare function load(offset: usize, constantOffset?: usize): i32;
export declare function store8(offset: usize, value: i32, constantOffset?: usize): void;
export declare function store16(offset: usize, value: i32, constantOffset?: usize): void;
export declare function store(offset: usize, value: i32, constantOffset?: usize): void;
@builtin export declare function clz(value: i32): i32;
@builtin export declare function ctz(value: i32): i32;
@builtin export declare function popcnt(value: i32): i32;
@builtin export declare function rotl(value: i32, shift: i32): i32;
@builtin export declare function rotr(value: i32, shift: i32): i32;
@builtin export declare function reinterpret_f32(value: f32): i32;
@builtin export declare function load8_s(offset: usize, constantOffset?: usize): i32;
@builtin export declare function load8_u(offset: usize, constantOffset?: usize): i32;
@builtin export declare function load16_s(offset: usize, constantOffset?: usize): i32;
@builtin export declare function load16_u(offset: usize, constantOffset?: usize): i32;
@builtin export declare function load(offset: usize, constantOffset?: usize): i32;
@builtin export declare function store8(offset: usize, value: i32, constantOffset?: usize): void;
@builtin export declare function store16(offset: usize, value: i32, constantOffset?: usize): void;
@builtin export declare function store(offset: usize, value: i32, constantOffset?: usize): void;
}
export declare function i64(value: void): i64;
@builtin export declare function i64(value: void): i64;
export namespace i64 {
export const MIN_VALUE: i64 = -9223372036854775808;
export const MAX_VALUE: i64 = 9223372036854775807;
export declare function clz(value: i64): i64;
export declare function ctz(value: i64): i64;
export declare function load8_s(offset: usize, constantOffset?: usize): i64;
export declare function load8_u(offset: usize, constantOffset?: usize): u64;
export declare function load16_s(offset: usize, constantOffset?: usize): i64;
export declare function load16_u(offset: usize, constantOffset?: usize): u64;
export declare function load32_s(offset: usize, constantOffset?: usize): i64;
export declare function load32_u(offset: usize, constantOffset?: usize): u64;
export declare function load(offset: usize, constantOffset?: usize): i64;
export declare function popcnt(value: i64): i64;
export declare function rotl(value: i64, shift: i64): i64;
export declare function rotr(value: i64, shift: i64): i64;
export declare function reinterpret_f64(value: f64): i64;
export declare function store8(offset: usize, value: i64, constantOffset?: usize): void;
export declare function store16(offset: usize, value: i64, constantOffset?: usize): void;
export declare function store32(offset: usize, value: i64, constantOffset?: usize): void;
export declare function store(offset: usize, value: i64, constantOffset?: usize): void;
@builtin export declare function clz(value: i64): i64;
@builtin export declare function ctz(value: i64): i64;
@builtin export declare function load8_s(offset: usize, constantOffset?: usize): i64;
@builtin export declare function load8_u(offset: usize, constantOffset?: usize): u64;
@builtin export declare function load16_s(offset: usize, constantOffset?: usize): i64;
@builtin export declare function load16_u(offset: usize, constantOffset?: usize): u64;
@builtin export declare function load32_s(offset: usize, constantOffset?: usize): i64;
@builtin export declare function load32_u(offset: usize, constantOffset?: usize): u64;
@builtin export declare function load(offset: usize, constantOffset?: usize): i64;
@builtin export declare function popcnt(value: i64): i64;
@builtin export declare function rotl(value: i64, shift: i64): i64;
@builtin export declare function rotr(value: i64, shift: i64): i64;
@builtin export declare function reinterpret_f64(value: f64): i64;
@builtin export declare function store8(offset: usize, value: i64, constantOffset?: usize): void;
@builtin export declare function store16(offset: usize, value: i64, constantOffset?: usize): void;
@builtin export declare function store32(offset: usize, value: i64, constantOffset?: usize): void;
@builtin export declare function store(offset: usize, value: i64, constantOffset?: usize): void;
}
export declare function isize(value: void): isize;
@builtin export declare function isize(value: void): isize;
export namespace isize {
export const MIN_VALUE: isize = sizeof<i32>() == sizeof<isize>()
? -2147483648
@ -151,31 +107,31 @@ export namespace isize {
: <isize>9223372036854775807;
}
export declare function u8(value: void): u8;
@builtin export declare function u8(value: void): u8;
export namespace u8 {
export const MIN_VALUE: u8 = 0;
export const MAX_VALUE: u8 = 255;
}
export declare function u16(value: void): u16;
@builtin export declare function u16(value: void): u16;
export namespace u16 {
export const MIN_VALUE: u16 = 0;
export const MAX_VALUE: u16 = 65535;
}
export declare function u32(value: void): u32;
@builtin export declare function u32(value: void): u32;
export namespace u32 {
export const MIN_VALUE: u32 = 0;
export const MAX_VALUE: u32 = 4294967295;
}
export declare function u64(value: void): u64;
@builtin export declare function u64(value: void): u64;
export namespace u64 {
export const MIN_VALUE: u64 = 0;
export const MAX_VALUE: u64 = 18446744073709551615;
}
export declare function usize(value: void): usize;
@builtin export declare function usize(value: void): usize;
export namespace usize {
export const MIN_VALUE: usize = 0;
export const MAX_VALUE: usize = sizeof<u32>() == sizeof<usize>()
@ -183,13 +139,13 @@ export namespace usize {
: <usize>18446744073709551615;
}
export declare function bool(value: void): bool;
@builtin export declare function bool(value: void): bool;
export namespace bool {
export const MIN_VALUE: bool = false;
export const MAX_VALUE: bool = true;
}
export declare function f32(value: void): f32;
@builtin export declare function f32(value: void): f32;
export namespace f32 {
export const MIN_VALUE = reinterpret<f32>(0xFF7FFFFF); // -0x1.fffffep+127f
export const MAX_VALUE = reinterpret<f32>(0x7F7FFFFF); // 0x1.fffffep+127f
@ -197,21 +153,21 @@ export namespace f32 {
export const MIN_SAFE_INTEGER: f32 = -16777215;
export const MAX_SAFE_INTEGER: f32 = 16777215;
export const EPSILON = reinterpret<f32>(0x34000000); // 0x1p-23f
export declare function abs(value: f32): f32;
export declare function ceil(value: f32): f32;
export declare function copysign(x: f32, y: f32): f32;
export declare function floor(value: f32): f32;
export declare function load(offset: usize, constantOffset?: usize): f32;
export declare function max(left: f32, right: f32): f32;
export declare function min(left: f32, right: f32): f32;
export declare function nearest(value: f32): f32;
export declare function reinterpret_i32(value: i32): f32;
export declare function sqrt(value: f32): f32;
export declare function store(offset: usize, value: f32, constantOffset?: usize): void;
export declare function trunc(value: f32): f32;
@builtin export declare function abs(value: f32): f32;
@builtin export declare function ceil(value: f32): f32;
@builtin export declare function copysign(x: f32, y: f32): f32;
@builtin export declare function floor(value: f32): f32;
@builtin export declare function load(offset: usize, constantOffset?: usize): f32;
@builtin export declare function max(left: f32, right: f32): f32;
@builtin export declare function min(left: f32, right: f32): f32;
@builtin export declare function nearest(value: f32): f32;
@builtin export declare function reinterpret_i32(value: i32): f32;
@builtin export declare function sqrt(value: f32): f32;
@builtin export declare function store(offset: usize, value: f32, constantOffset?: usize): void;
@builtin export declare function trunc(value: f32): f32;
}
export declare function f64(value: void): f64;
@builtin export declare function f64(value: void): f64;
export namespace f64 {
export const MIN_VALUE = reinterpret<f64>(0xFFEFFFFFFFFFFFFF); // -0x1.fffffffffffffp+1023
export const MAX_VALUE = reinterpret<f64>(0x7FEFFFFFFFFFFFFF); // 0x1.fffffffffffffp+1023
@ -219,20 +175,24 @@ export namespace f64 {
export const MIN_SAFE_INTEGER: f64 = -9007199254740991;
export const MAX_SAFE_INTEGER: f64 = 9007199254740991;
export const EPSILON = reinterpret<f64>(0x3CB0000000000000); // 0x1p-52
export declare function abs(value: f64): f64;
export declare function ceil(value: f64): f64;
export declare function copysign(x: f64, y: f64): f64;
export declare function floor(value: f64): f64;
export declare function load(offset: usize, constantOffset?: usize): f64;
export declare function max(left: f64, right: f64): f64;
export declare function min(left: f64, right: f64): f64;
export declare function nearest(value: f64): f64;
export declare function reinterpret_i64(value: i64): f64;
export declare function sqrt(value: f64): f64;
export declare function store(offset: usize, value: f64, constantOffset?: usize): void;
export declare function trunc(value: f64): f64;
@builtin export declare function abs(value: f64): f64;
@builtin export declare function ceil(value: f64): f64;
@builtin export declare function copysign(x: f64, y: f64): f64;
@builtin export declare function floor(value: f64): f64;
@builtin export declare function load(offset: usize, constantOffset?: usize): f64;
@builtin export declare function max(left: f64, right: f64): f64;
@builtin export declare function min(left: f64, right: f64): f64;
@builtin export declare function nearest(value: f64): f64;
@builtin export declare function reinterpret_i64(value: i64): f64;
@builtin export declare function sqrt(value: f64): f64;
@builtin export declare function store(offset: usize, value: f64, constantOffset?: usize): void;
@builtin export declare function trunc(value: f64): f64;
}
export declare const HEAP_BASE: usize;
@builtin export declare function start(): void;
export declare function start(): void;
@builtin export declare function ERROR(message?: void): void;
@builtin export declare function WARNING(message?: void): void;
@builtin export declare function INFO(message?: void): void;
@builtin export declare function __gc_iterate_roots(fn: (ref: usize) => void): void;

View File

@ -4,116 +4,18 @@
* @module std/assembly/collector/itcm
*//***/
// Based on the concepts of Bach Le's μgc, see: https://github.com/bullno1/ugc
// Largely based on the Bach Le's μgc, see: https://github.com/bullno1/ugc
const TRACE = false;
import {
AL_MASK,
MAX_SIZE_32
} from "../internal/allocator";
// ╒═══════════════ Managed object layout (32-bit) ════════════════╕
// 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
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┼─┴─┴─┤ ┐
// │ next │ F │ ◄─┐ = nextWithFlags
// ├─────────────────────────────────────────────────────────┴─────┤ │ usize
// │ prev │ ◄─┘
// ╞═══════════════════════════════════════════════════════════════╡ SIZE ┘
// │ ... data ... │
// └───────────────────────────────────────────────────────────────┘
// F: flags
/** Managed object flags. */
namespace Flags {
/** Object is unreachable (so far). */
export var WHITE = 0;
/** Object is reachable. */
export var BLACK = 1;
/** Object is reachable but its children have not yet been scanned. */
export const GRAY = 2;
/** Mask to obtain just the flag bits. */
export const MASK = AL_MASK;
}
/** Represents a managed object in memory, consisting of a header followed by the object's data. */
@unmanaged
class ManagedObject {
/** Pointer to the next object with additional flags stored in the alignment bits. */
nextWithFlags: usize;
/** Pointer to the previous object. */
prev: ManagedObject;
/** Visitor function called with the data pointer (excl. header). */
visitFn: (obj: usize) => void;
/** Size of a managed object after alignment. */
static readonly SIZE: usize = (offsetof<ManagedObject>() + AL_MASK) & ~AL_MASK;
/** Gets the pointer to the next object in the list. */
get next(): ManagedObject {
return changetype<ManagedObject>(this.nextWithFlags & ~Flags.MASK);
}
/** Sets the pointer to the next object in the list. */
set next(obj: ManagedObject) {
this.nextWithFlags = changetype<usize>(obj) | (this.nextWithFlags & Flags.MASK);
}
/** Inserts an object to this list. */
insert(obj: ManagedObject): void {
var prev = this.prev;
obj.next = this;
obj.prev = prev;
prev.next = obj;
this.prev = obj;
}
/** Removes this object from its list. */
remove(): void {
var next = this.next;
var prev = this.prev;
next.prev = prev;
prev.next = next;
}
/** Tests if this object is white, that is unreachable (so far). */
get isWhite(): bool {
return (this.nextWithFlags & Flags.MASK) == Flags.WHITE;
}
/** Marks this object as white, that is unreachable (so far). */
makeWhite(): void {
this.nextWithFlags = (this.nextWithFlags & ~Flags.MASK) | Flags.WHITE;
}
/** Tests if this object is black, that is reachable. Root objects are always reachable. */
get isBlack(): bool {
return (this.nextWithFlags & Flags.MASK) == Flags.BLACK;
}
/** Marks this object as black, that is reachable. */
makeBlack(): void {
this.nextWithFlags = (this.nextWithFlags & ~Flags.MASK) | Flags.BLACK;
}
/** Tests if this object is gray, that is reachable with unscanned children. */
get isGray(): bool {
return (this.nextWithFlags & Flags.MASK) == Flags.GRAY;
}
/** Marks this object as gray, that is reachable with unscanned children. */
makeGray(): void {
if (this != iter) {
this.remove();
to.insert(this);
} else {
iter = iter.prev;
}
this.nextWithFlags = (this.nextWithFlags & ~Flags.MASK) | Flags.GRAY;
}
}
import {
iterateRoots
} from "../gc";
/** Collector states. */
const enum State {
@ -129,125 +31,213 @@ const enum State {
/** Current collector state. */
var state = State.INIT;
/** Current white color value. */
var white = 0;
// From and to spaces
var from: ManagedObject;
var to: ManagedObject;
var from: ManagedObjectSet;
var to: ManagedObjectSet;
var iter: ManagedObject;
// ╒═══════════════ Managed object layout (32-bit) ════════════════╕
// 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
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┼─┴─┴─┤ ┐
// │ next │ F │ ◄─┐ = nextWithFlags
// ├─────────────────────────────────────────────────────────┴─────┤ │ usize
// │ prev │ ◄─┘
// ╞═══════════════════════════════════════════════════════════════╡ SIZE ┘
// │ ... data ... │
// └───────────────────────────────────────────────────────────────┘
// F: flags
/** Represents a managed object in memory, consisting of a header followed by the object's data. */
@unmanaged
class ManagedObject {
/** Pointer to the next object with color flags stored in the alignment bits. */
nextWithColor: usize;
/** Pointer to the previous object. */
prev: ManagedObject;
/** Visitor function called with the payload reference. */
visitFn: (ref: usize) => void;
/** Size of a managed object after alignment. */
static readonly SIZE: usize = (offsetof<ManagedObject>() + AL_MASK) & ~AL_MASK;
/** Gets the pointer to the next object in the list. */
get next(): ManagedObject {
return changetype<ManagedObject>(this.nextWithColor & ~3);
}
/** Sets the pointer to the next object in the list. */
set next(obj: ManagedObject) {
this.nextWithColor = changetype<usize>(obj) | (this.nextWithColor & 3);
}
/** Gets this object's color. */
get color(): i32 {
return this.nextWithColor & 3;
}
/** Sets this object's color. */
set color(color: i32) {
this.nextWithColor = (this.nextWithColor & ~3) | color;
}
/** Unlinks this object from its list. */
unlink(): void {
var next = this.next;
var prev = this.prev;
if (TRACE) trace(" unlink", 3, objToRef(prev), objToRef(this), objToRef(next));
next.prev = prev;
prev.next = next;
}
/** Marks this object as gray, that is reachable with unscanned children. */
makeGray(): void {
if (TRACE) trace(" makeGray", 1, objToRef(this));
const gray = 2;
if (this == iter) iter = this.prev;
this.unlink();
to.push(this);
this.nextWithColor = (this.nextWithColor & ~3) | gray;
}
}
/** A set of managed objects. Used for the from and to spaces. */
@unmanaged
class ManagedObjectSet extends ManagedObject {
/** Inserts an object. */
push(obj: ManagedObject): void {
var prev = this.prev;
if (TRACE) trace(" push", 3, objToRef(prev), objToRef(obj), objToRef(this));
obj.next = this;
obj.prev = prev;
prev.next = obj;
this.prev = obj;
}
/** Clears this list. */
clear(): void {
if (TRACE) trace(" clear", 1, objToRef(this));
this.nextWithColor = changetype<usize>(this);
this.prev = this;
}
}
/** Performs a single step according to the current state. */
function gc_step(): void {
function step(): void {
var obj: ManagedObject;
switch (state) {
case State.INIT: {
from = changetype<ManagedObject>(allocate_memory(ManagedObject.SIZE));
from.nextWithFlags = changetype<usize>(from);
from.prev = from;
to = changetype<ManagedObject>(allocate_memory(ManagedObject.SIZE));
to.nextWithFlags = changetype<usize>(to);
to.prev = to;
if (TRACE) trace("gc~step/INIT");
from = changetype<ManagedObjectSet>(memory.allocate(ManagedObject.SIZE));
from.visitFn = changetype<(ref: usize) => void>(<u32>-1); // would error
from.clear();
to = changetype<ManagedObjectSet>(memory.allocate(ManagedObject.SIZE));
to.visitFn = changetype<(ref: usize) => void>(<u32>-1); // would error
to.clear();
iter = to;
state = State.IDLE;
if (TRACE) trace("gc~state = IDLE");
// fall-through
}
case State.IDLE: {
if (TRACE) trace("gc~step/IDLE");
iterateRoots(__gc_mark);
state = State.MARK;
if (TRACE) trace("gc~state = MARK");
break;
}
case State.MARK: {
obj = iter.next;
if (obj != to) {
if (obj !== to) {
if (TRACE) trace("gc~step/MARK iterate", 1, objToRef(obj));
iter = obj;
obj.makeBlack();
obj.visitFn(changetype<usize>(obj) + ManagedObject.SIZE);
obj.color = <i32>!white;
obj.visitFn(objToRef(obj));
} else {
if (TRACE) trace("gc~step/MARK finish");
iterateRoots(__gc_mark);
obj = iter.next;
if (obj == to) {
let temp = from;
if (obj === to) {
let prevFrom = from;
from = to;
to = temp;
Flags.WHITE ^= 1;
Flags.BLACK ^= 1;
iter = from.next;
to = prevFrom;
white = <i32>!white;
iter = prevFrom.next;
state = State.SWEEP;
if (TRACE) trace("gc~state = SWEEP");
}
}
break;
}
case State.SWEEP: {
obj = iter;
if (obj != to) {
if (obj !== to) {
if (TRACE) trace("gc~step/SWEEP free", 1, objToRef(obj));
iter = obj.next;
free_memory(changetype<usize>(obj));
memory.free(changetype<usize>(obj));
} else {
to.nextWithFlags = changetype<usize>(to);
to.prev = to;
if (TRACE) trace("gc~step/SWEEP finish");
to.clear();
state = State.IDLE;
if (TRACE) trace("gc~state = IDLE");
}
break;
}
}
}
/** Allocates a managed object. */
@global
export function gc_allocate(
size: usize,
visitFn: (obj: usize) => void
): usize {
assert(size <= MAX_SIZE_32 - ManagedObject.SIZE);
var obj = changetype<ManagedObject>(allocate_memory(ManagedObject.SIZE + size));
obj.makeWhite();
obj.visitFn = visitFn;
from.insert(obj);
@inline function refToObj(ref: usize): ManagedObject {
return changetype<ManagedObject>(ref - ManagedObject.SIZE);
}
@inline function objToRef(obj: ManagedObject): usize {
return changetype<usize>(obj) + ManagedObject.SIZE;
}
/** Visits a reachable object. Called from the visitFn functions. */
@global
export function gc_visit(obj: ManagedObject): void {
if (state == State.SWEEP) return;
if (obj.isWhite) obj.makeGray();
// Garbage collector interface
@global export function __gc_allocate(
size: usize,
visitFn: (ref: usize) => void
): usize {
if (TRACE) trace("gc.allocate", 1, size);
if (size > MAX_SIZE_32 - ManagedObject.SIZE) unreachable();
step(); // also makes sure it's initialized
var obj = changetype<ManagedObject>(memory.allocate(ManagedObject.SIZE + size));
obj.visitFn = visitFn;
obj.color = white;
from.push(obj);
return objToRef(obj);
}
/** Registers a managed child object with its parent object. */
@global
export function gc_register(parent: ManagedObject, child: ManagedObject): void {
if (parent.isBlack && child.isWhite) parent.makeGray();
@global export function __gc_link(parentRef: usize, childRef: usize): void {
if (TRACE) trace("gc.link", 2, parentRef, childRef);
var parent = refToObj(parentRef);
if (parent.color == <i32>!white && refToObj(childRef).color == white) parent.makeGray();
}
/** Iterates the root set. Provided by the compiler according to the program. */
@global
export declare function gc_roots(): void;
@global export function __gc_mark(ref: usize): void {
if (TRACE) trace("gc.mark", 1, ref);
if (ref) {
let obj = refToObj(ref);
if (obj.color == white) obj.makeGray();
}
}
/** Performs a full garbage collection cycle. */
@global
export function gc_collect(): void {
@global export function __gc_collect(): void {
if (TRACE) trace("gc.collect");
// begin collecting if not yet collecting
switch (state) {
case State.INIT:
case State.IDLE: gc_step();
case State.IDLE: step();
}
// finish the cycle
while (state != State.IDLE) gc_step();
while (state != State.IDLE) step();
}
declare function allocate_memory(size: usize): usize;
declare function free_memory(ptr: usize): void;
// Considerations
//
// - An API that consists mostly of just replacing `allocate_memory` would be ideal, possibly taking
// any additional number of parameters that are necessary, like the parent and the visitor.
//
// - Not having to generate a helper function for iterating globals but instead marking specific
// nodes as roots could simplify the embedding, but whether this is feasible or not depends on its
// performance characteristics and the possibility of tracking root status accross assignments.
// For example, root status could be implemented as some sort of referenced-by-globals counting
// and a dedicated list of root objects.
//
// - In 32-bit specifically, there is some free space in TLSF object headers due to alignment that
// could be repurposed to store some GC information, like a class id. Certainly, this somewhat
// depends on the efficiency of the used mechanism to detect this at compile time, including when
// a different allocator is used.
//
// - Think about generations.

View File

@ -1,7 +1,16 @@
/** Environment abort function called where assertions evaluate to false / on throw. */
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;

28
std/assembly/gc.ts Normal file
View File

@ -0,0 +1,28 @@
@builtin export declare function iterateRoots(fn: (ref: usize) => void): void; // tslint:disable-line
export namespace gc {
export function allocate(size: usize, visitFn: (ref: usize) => void): usize {
if (isDefined(__gc_allocate)) return __gc_allocate(size, visitFn); // tslint:disable-line
WARNING("Calling 'gc.allocate' requires a garbage collector to be present.");
return <usize>unreachable();
}
export function collect(): void {
if (isDefined(__gc_collect)) { __gc_collect(); return; } // tslint:disable-line
WARNING("Calling 'gc.collect' requires a garbage collector to be present.");
unreachable();
}
export function link(parentRef: usize, childRef: usize): void {
if (isDefined(__gc_link)) { __gc_link(parentRef, childRef); return; } // tslint:disable-line
WARNING("Calling 'gc.link' requires a garbage collector to be present.");
unreachable();
}
export function mark(ref: usize): void {
if (isDefined(__gc_mark)) { __gc_mark(ref); return; } // tslint:disable-line
WARNING("Calling 'gc.mark' requires a garbage collector to be present.");
unreachable();
}
}

View File

@ -34,6 +34,8 @@ declare type f32 = number;
/** A 64-bit float. */
declare type f64 = number;
// Compiler hints
/** Compiler target. 0 = JS, 1 = WASM32, 2 = WASM64. */
declare const ASC_TARGET: i32;
/** Provided noTreeshaking option. */
@ -51,6 +53,100 @@ declare const ASC_FEATURE_MUTABLE_GLOBAL: bool;
/** Whether the sign extension feature is enabled. */
declare const ASC_FEATURE_SIGN_EXTENSION: bool;
// Builtins
/** Performs the sign-agnostic count leading zero bits operation on a 32-bit or 64-bit integer. All zero bits are considered leading if the value is zero. */
declare function clz<T = i32 | i64>(value: T): T;
/** Performs the sign-agnostic count tailing zero bits operation on a 32-bit or 64-bit integer. All zero bits are considered trailing if the value is zero. */
declare function ctz<T = i32 | i64>(value: T): T;
/** Performs the sign-agnostic count number of one bits operation on a 32-bit or 64-bit integer. */
declare function popcnt<T = i32 | i64>(value: T): T;
/** Performs the sign-agnostic rotate left operation on a 32-bit or 64-bit integer. */
declare function rotl<T = i32 | i64>(value: T, shift: T): T;
/** Performs the sign-agnostic rotate right operation on a 32-bit or 64-bit integer. */
declare function rotr<T = i32 | i64>(value: T, shift: T): T;
/** Computes the absolute value of an integer or float. */
declare function abs<T = i32 | i64 | f32 | f64>(value: T): T;
/** Determines the maximum of two integers or floats. If either operand is `NaN`, returns `NaN`. */
declare function max<T = i32 | i64 | f32 | f64>(left: T, right: T): T;
/** Determines the minimum of two integers or floats. If either operand is `NaN`, returns `NaN`. */
declare function min<T = i32 | i64 | f32 | f64>(left: T, right: T): T;
/** Performs the ceiling operation on a 32-bit or 64-bit float. */
declare function ceil<T = f32 | f64>(value: T): T;
/** Composes a 32-bit or 64-bit float from the magnitude of `x` and the sign of `y`. */
declare function copysign<T = f32 | f64>(x: T, y: T): T;
/** Performs the floor operation on a 32-bit or 64-bit float. */
declare function floor<T = f32 | f64>(value: T): T;
/** Rounds to the nearest integer tied to even of a 32-bit or 64-bit float. */
declare function nearest<T = f32 | f64>(value: T): T;
/** Reinterprets the bits of the specified value as type `T`. Valid reinterpretations are u32/i32 to/from f32 and u64/i64 to/from f64. */
declare function reinterpret<T = i32 | i64 | f32 | f64>(value: number): T;
/** Selects one of two pre-evaluated values depending on the condition. */
declare function select<T>(ifTrue: T, ifFalse: T, condition: bool): T;
/** Calculates the square root of a 32-bit or 64-bit float. */
declare function sqrt<T = f32 | f64>(value: T): T;
/** Rounds to the nearest integer towards zero of a 32-bit or 64-bit float. */
declare function trunc<T = f32 | f64>(value: T): T;
/** Loads a value of the specified type from memory. Equivalent to dereferncing a pointer in other languages. */
declare function load<T>(ptr: usize, constantOffset?: usize): T;
/** Stores a value of the specified type to memory. Equivalent to dereferencing a pointer in other languages when assigning a value. */
declare function store<T>(ptr: usize, value: any, constantOffset?: usize): void;
/** Emits an unreachable operation that results in a runtime error when executed. Both a statement and an expression of any type. */
declare function unreachable(): any; // sic
/** NaN (not a number) as a 32-bit or 64-bit float depending on context. */
declare const NaN: f32 | f64;
/** Positive infinity as a 32-bit or 64-bit float depending on context. */
declare const Infinity: f32 | f64;
/** Heap base offset. */
declare const HEAP_BASE: usize;
/** Determines the byte size of the specified underlying core type. Compiles to a constant. */
declare function sizeof<T>(): usize;
/** Determines the alignment (log2) of the specified underlying core type. Compiles to a constant. */
declare function alignof<T>(): usize;
/** Determines the offset of the specified field within the given class type. Returns the class type's end offset if field name has been omitted. Compiles to a constant. */
declare function offsetof<T>(fieldName?: string): usize;
/** Changes the type of any value of `usize` kind to another one of `usize` kind. Useful for casting class instances to their pointer values and vice-versa. Beware that this is unsafe.*/
declare function changetype<T>(value: any): T;
/** Explicitly requests no bounds checks on the provided expression. Useful for array accesses. */
declare function unchecked<T>(value: T): T;
/** Emits a `call_indirect` instruction, calling the specified function in the function table by index with the specified arguments. Does result in a runtime error if the arguments do not match the called function. */
declare function call_indirect<T>(target: Function | u32, ...args: any[]): T;
/** Tests if a 32-bit or 64-bit float is `NaN`. */
declare function isNaN<T = f32 | f64>(value: T): bool;
/** Tests if a 32-bit or 64-bit float is finite, that is not `NaN` or +/-`Infinity`. */
declare function isFinite<T = f32 | f64>(value: T): bool;
/** Tests if the specified type *or* expression is of an integer type and not a reference. Compiles to a constant. */
declare function isInteger<T>(value?: any): value is number;
/** Tests if the specified type *or* expression is of a float type. Compiles to a constant. */
declare function isFloat<T>(value?: any): value is number;
/** Tests if the specified type *or* expression can represent negative numbers. Compiles to a constant. */
declare function isSigned<T>(value?: any): value is number;
/** Tests if the specified type *or* expression is of a reference type. Compiles to a constant. */
declare function isReference<T>(value?: any): value is object | string;
/** Tests if the specified type *or* expression can be used as a string. Compiles to a constant. */
declare function isString<T>(value?: any): value is string | String;
/** Tests if the specified type *or* expression can be used as an array. Compiles to a constant. */
declare function isArray<T>(value?: any): value is Array<any>;
/** Tests if the specified expression resolves to a defined element. Compiles to a constant. */
declare function isDefined(expression: any): bool;
/** Tests if the specified expression evaluates to a constant value. Compiles to a constant. */
declare function isConstant(expression: any): bool;
/** Traps if the specified value is not true-ish, otherwise returns the (non-nullable) value. */
declare function assert<T>(isTrueish: T, message?: string): T & object; // any better way to model `: T != null`?
/** Parses an integer string to a 64-bit float. */
declare function parseInt(str: string, radix?: i32): f64;
/** Parses an integer string to a 32-bit integer. */
declare function parseI32(str: string, radix?: i32): i32;
/** Parses an integer string to a 64-bit integer. */
declare function parseI64(str: string, radix?: i32): i64;
/** Parses a string to a 64-bit float. */
declare function parseFloat(str: string): f64;
/** Returns the 64-bit floating-point remainder of `x/y`. */
declare function fmod(x: f64, y: f64): f64;
/** Returns the 32-bit floating-point remainder of `x/y`. */
declare function fmodf(x: f32, y: f32): f32;
/** Converts any other numeric value to an 8-bit signed integer. */
declare function i8(value: i8 | i16 | i32 | i64 | isize | u8 | u16 | u32 | u64 | usize | bool | f32 | f64): i8;
declare namespace i8 {
@ -210,121 +306,70 @@ declare namespace f64 {
export function store(offset: usize, value: f64, constantOffset?: usize): void;
}
// Built-ins
// User-defined diagnostic macros
/** Performs the sign-agnostic count leading zero bits operation on a 32-bit or 64-bit integer. All zero bits are considered leading if the value is zero. */
declare function clz<T = i32 | i64>(value: T): T;
/** Performs the sign-agnostic count tailing zero bits operation on a 32-bit or 64-bit integer. All zero bits are considered trailing if the value is zero. */
declare function ctz<T = i32 | i64>(value: T): T;
/** Performs the sign-agnostic count number of one bits operation on a 32-bit or 64-bit integer. */
declare function popcnt<T = i32 | i64>(value: T): T;
/** Performs the sign-agnostic rotate left operation on a 32-bit or 64-bit integer. */
declare function rotl<T = i32 | i64>(value: T, shift: T): T;
/** Performs the sign-agnostic rotate right operation on a 32-bit or 64-bit integer. */
declare function rotr<T = i32 | i64>(value: T, shift: T): T;
/** Computes the absolute value of an integer or float. */
declare function abs<T = i32 | i64 | f32 | f64>(value: T): T;
/** Determines the maximum of two integers or floats. If either operand is `NaN`, returns `NaN`. */
declare function max<T = i32 | i64 | f32 | f64>(left: T, right: T): T;
/** Determines the minimum of two integers or floats. If either operand is `NaN`, returns `NaN`. */
declare function min<T = i32 | i64 | f32 | f64>(left: T, right: T): T;
/** Performs the ceiling operation on a 32-bit or 64-bit float. */
declare function ceil<T = f32 | f64>(value: T): T;
/** Composes a 32-bit or 64-bit float from the magnitude of `x` and the sign of `y`. */
declare function copysign<T = f32 | f64>(x: T, y: T): T;
/** Performs the floor operation on a 32-bit or 64-bit float. */
declare function floor<T = f32 | f64>(value: T): T;
/** Rounds to the nearest integer tied to even of a 32-bit or 64-bit float. */
declare function nearest<T = f32 | f64>(value: T): T;
/** Reinterprets the bits of the specified value as type `T`. Valid reinterpretations are u32/i32 to/from f32 and u64/i64 to/from f64. */
declare function reinterpret<T = i32 | i64 | f32 | f64>(value: number): T;
/** Selects one of two pre-evaluated values depending on the condition. */
declare function select<T>(ifTrue: T, ifFalse: T, condition: bool): T;
/** Calculates the square root of a 32-bit or 64-bit float. */
declare function sqrt<T = f32 | f64>(value: T): T;
/** Rounds to the nearest integer towards zero of a 32-bit or 64-bit float. */
declare function trunc<T = f32 | f64>(value: T): T;
/** Loads a value of the specified type from memory. Equivalent to dereferncing a pointer in other languages. */
declare function load<T>(ptr: usize, constantOffset?: usize): T;
/** Stores a value of the specified type to memory. Equivalent to dereferencing a pointer in other languages when assigning a value. */
declare function store<T>(ptr: usize, value: any, constantOffset?: usize): void;
/** Returns the current memory size in units of pages. One page is 64kb. */
declare function current_memory(): i32;
/** Grows linear memory by a given unsigned delta of pages. One page is 64kb. Returns the previous memory size in units of pages or `-1` on failure. */
declare function grow_memory(value: i32): i32;
/** Copies n bytes from the specified source to the specified destination in memory. These regions may overlap. */
declare function move_memory(destination: usize, source: usize, n: usize): void;
/** Sets n bytes beginning at the specified destination in memory to the specified byte value. */
declare function set_memory(destination: usize, value: u8, count: usize): void;
/** Compares two chunks of memory. Returns `0` if equal, otherwise the difference of the first differing bytes. */
declare function compare_memory(vl: usize, vr: usize, n: usize): i32;
/** Allocates a chunk of memory of the specified size and returns a pointer to it. */
declare function allocate_memory(size: usize): usize;
/** Disposes a chunk of memory by its pointer. */
declare function free_memory(ptr: usize): void;
/** Emits an unreachable operation that results in a runtime error when executed. Both a statement and an expression of any type. */
declare function unreachable(): any; // sic
/** Emits a user-defined diagnostic error when encountered. */
declare function ERROR(message?: any): void;
/** Emits a user-defined diagnostic warning when encountered. */
declare function WARNING(message?: any): void;
/** Emits a user-defined diagnostic info when encountered. */
declare function INFO(message?: any): void;
/** [Polyfill] Performs the sign-agnostic reverse bytes **/
// Polyfills
/** Performs the sign-agnostic reverse bytes **/
declare function bswap<T = i8 | u8 | i16 | u16 | i32 | u32 | i64 | u64 | isize | usize>(value: T): T;
/** [Polyfill] Performs the sign-agnostic reverse bytes only for last 16-bit **/
/** Performs the sign-agnostic reverse bytes only for last 16-bit **/
declare function bswap16<T = i8 | u8 | i16 | u16 | i32 | u32>(value: T): T;
/** NaN (not a number) as a 32-bit or 64-bit float depending on context. */
declare const NaN: f32 | f64;
/** Positive infinity as a 32-bit or 64-bit float depending on context. */
declare const Infinity: f32 | f64;
/** Heap base offset. */
declare const HEAP_BASE: usize;
/** Determines the byte size of the specified underlying core type. Compiles to a constant. */
declare function sizeof<T>(): usize;
/** Determines the alignment (log2) of the specified underlying core type. Compiles to a constant. */
declare function alignof<T>(): usize;
/** Determines the offset of the specified field within the given class type. Returns the class type's end offset if field name has been omitted. Compiles to a constant. */
declare function offsetof<T>(fieldName?: string): usize;
/** Changes the type of any value of `usize` kind to another one of `usize` kind. Useful for casting class instances to their pointer values and vice-versa. Beware that this is unsafe.*/
declare function changetype<T>(value: any): T;
/** Explicitly requests no bounds checks on the provided expression. Useful for array accesses. */
declare function unchecked<T>(value: T): T;
/** Emits a `call_indirect` instruction, calling the specified function in the function table by index with the specified arguments. Does result in a runtime error if the arguments do not match the called function. */
declare function call_indirect<T>(target: Function | u32, ...args: any[]): T;
/** Tests if a 32-bit or 64-bit float is `NaN`. */
declare function isNaN<T = f32 | f64>(value: T): bool;
/** Tests if a 32-bit or 64-bit float is finite, that is not `NaN` or +/-`Infinity`. */
declare function isFinite<T = f32 | f64>(value: T): bool;
/** Tests if the specified type *or* expression is of an integer type and not a reference. Compiles to a constant. */
declare function isInteger<T>(value?: any): value is number;
/** Tests if the specified type *or* expression is of a float type. Compiles to a constant. */
declare function isFloat<T>(value?: any): value is number;
/** Tests if the specified type *or* expression can represent negative numbers. Compiles to a constant. */
declare function isSigned<T>(value?: any): value is number;
/** Tests if the specified type *or* expression is of a reference type. Compiles to a constant. */
declare function isReference<T>(value?: any): value is object | string;
/** Tests if the specified type *or* expression can be used as a string. Compiles to a constant. */
declare function isString<T>(value?: any): value is string | String;
/** Tests if the specified type *or* expression can be used as an array. Compiles to a constant. */
declare function isArray<T>(value?: any): value is Array<any>;
/** Tests if the specified expression resolves to a defined element. Compiles to a constant. */
declare function isDefined(expression: any): bool;
/** Tests if the specified expression evaluates to a constant value. Compiles to a constant. */
declare function isConstant(expression: any): bool;
/** Traps if the specified value is not true-ish, otherwise returns the (non-nullable) value. */
declare function assert<T>(isTrueish: T, message?: string): T & object; // any better way to model `: T != null`?
/** Parses an integer string to a 64-bit float. */
declare function parseInt(str: string, radix?: i32): f64;
/** Parses an integer string to a 32-bit integer. */
declare function parseI32(str: string, radix?: i32): i32;
/** Parses an integer string to a 64-bit integer. */
declare function parseI64(str: string, radix?: i32): i64;
/** Parses a string to a 64-bit float. */
declare function parseFloat(str: string): f64;
/** Returns the 64-bit floating-point remainder of `x/y`. */
declare function fmod(x: f64, y: f64): f64;
/** Returns the 32-bit floating-point remainder of `x/y`. */
declare function fmodf(x: f32, y: f32): f32;
// Standard library
/** Memory operations. */
declare namespace memory {
/** Returns the current memory size in units of pages. One page is 64kb. */
export function size(): i32;
/** Grows linear memory by a given unsigned delta of pages. One page is 64kb. Returns the previous memory size in units of pages or `-1` on failure. */
export function grow(value: i32): i32;
/** Sets n bytes beginning at the specified destination in memory to the specified byte value. */
export function fill(dst: usize, value: u8, count: usize): void;
/** Copies n bytes from the specified source to the specified destination in memory. These regions may overlap. */
export function copy(dst: usize, src: usize, n: usize): void;
/** Copies elements from a passive element segment to a table. */
// export function init(segmentIndex: u32, srcOffset: usize, dstOffset: usize, n: usize): void;
/** Prevents further use of a passive element segment. */
// export function drop(segmentIndex: u32): void;
/** Copies elements from one region of a table to another region. */
export function allocate(size: usize): usize;
/** Disposes a chunk of memory by its pointer. */
export function free(ptr: usize): void;
/** Compares two chunks of memory. Returns `0` if equal, otherwise the difference of the first differing bytes. */
export function compare(vl: usize, vr: usize, n: usize): i32;
/** Resets the allocator to its initial state, if supported. */
export function reset(): void;
}
/** Garbage collector operations. */
declare namespace gc {
/** Allocates a managed object identified by its visitor function. */
export function allocate(size: usize, visitFn: (ref: usize) => void): usize;
/** Performs a full garbage collection cycle. */
export function collect(): void;
/** Must be called when a managed object becomes a child of another one. */
export function link(parentRef: usize, childRef: usize): void;
/** Must be called when a managed object is found reachable. */
export function mark(ref: usize): void;
}
/** Table operations. */
declare namespace table {
/** Copies elements from a passive element segment to a table. */
// export function init(elementIndex: u32, srcOffset: u32, dstOffset: u32, n: u32): void;
/** Prevents further use of a passive element segment. */
// export function drop(elementIndex: u32): void;
/** Copies elements from one region of a table to another region. */
// export function copy(dest: u32, src: u32, n: u32): void;
}
/** Class representing a generic, fixed-length raw binary data buffer. */
declare class ArrayBuffer {
/** The size, in bytes, of the array. */
@ -607,7 +652,10 @@ declare const Math: IMath<f64>;
/** Alias of {@link NativeMathf} or {@link JSMath} respectively. Defaults to `NativeMathf`. */
declare const Mathf: IMath<f32>;
// Internal decorators
/** Environmental tracing function for debugging purposes. */
declare function trace(msg: string, n?: i32, a0?: f64, a1?: f64, a2?: f64, a3?: f64, a4?: f64): void;
// Decorators
/** Annotates an element as a program global. */
declare function global(target: Function, propertyKey: string, descriptor: any): void;

View File

@ -36,8 +36,8 @@ export function weakHeapSort<T>(arr: Array<T>, comparator: (a: T, b: T) => i32):
var length = arr.length;
var bitsetSize = (length + 31) >> 5 << shift32;
var bitset = allocate_memory(bitsetSize); // indexed in 32-bit chunks below
set_memory(bitset, 0, bitsetSize);
var bitset = memory.allocate(bitsetSize); // indexed in 32-bit chunks below
memory.fill(bitset, 0, bitsetSize);
// see: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.21.1863&rep=rep1&type=pdf
@ -83,7 +83,7 @@ export function weakHeapSort<T>(arr: Array<T>, comparator: (a: T, b: T) => i32):
}
}
free_memory(bitset);
memory.free(bitset);
var t = loadUnsafe<T,T>(buffer, 1); // t = arr[1]
storeUnsafe<T,T>(buffer, 1, loadUnsafe<T,T>(buffer, 0)); // arr[1] = arr[0]

View File

@ -20,7 +20,7 @@ export function computeSize(byteLength: i32): usize {
/** Allocates a raw ArrayBuffer. Contents remain uninitialized. */
export function allocUnsafe(byteLength: i32): ArrayBuffer {
assert(<u32>byteLength <= <u32>MAX_BLENGTH);
var buffer = allocate_memory(computeSize(byteLength));
var buffer = memory.allocate(computeSize(byteLength));
store<i32>(buffer, byteLength, offsetof<ArrayBuffer>("byteLength"));
return changetype<ArrayBuffer>(buffer);
}
@ -32,19 +32,19 @@ export function reallocUnsafe(buffer: ArrayBuffer, newByteLength: i32): ArrayBuf
assert(newByteLength <= MAX_BLENGTH);
if (newByteLength <= <i32>(computeSize(oldByteLength) - HEADER_SIZE)) { // fast path: zero out additional space
store<i32>(changetype<usize>(buffer), newByteLength, offsetof<ArrayBuffer>("byteLength"));
set_memory(
memory.fill(
changetype<usize>(buffer) + HEADER_SIZE + <usize>oldByteLength,
0,
<usize>(newByteLength - oldByteLength)
);
} else { // slow path: copy to new buffer
let newBuffer = allocUnsafe(newByteLength);
move_memory(
memory.copy(
changetype<usize>(newBuffer) + HEADER_SIZE,
changetype<usize>(buffer) + HEADER_SIZE,
<usize>oldByteLength
);
set_memory(
memory.fill(
changetype<usize>(newBuffer) + HEADER_SIZE + <usize>oldByteLength,
0,
<usize>(newByteLength - oldByteLength)

View File

@ -23,7 +23,7 @@ export function clamp<T>(val: T, lo: T, hi: T): T {
/** Allocates a raw String with uninitialized contents. */
export function allocate(length: i32): String {
assert(length > 0 && length <= MAX_LENGTH);
var buffer = allocate_memory(HEADER_SIZE + (<usize>length << 1));
var buffer = memory.allocate(HEADER_SIZE + (<usize>length << 1));
store<i32>(buffer, length);
return changetype<String>(buffer);
}

View File

@ -18,7 +18,7 @@ export abstract class TypedArray<T,V> {
if (<u32>length > MAX_LENGTH) throw new RangeError("Invalid typed array length");
var byteLength = length << alignof<T>();
var buffer = allocUnsafe(byteLength);
set_memory(changetype<usize>(buffer) + HEADER_SIZE_AB, 0, <usize>byteLength);
memory.fill(changetype<usize>(buffer) + HEADER_SIZE_AB, 0, <usize>byteLength);
this.buffer = buffer;
this.byteOffset = 0;
this.byteLength = byteLength;
@ -64,7 +64,7 @@ export abstract class TypedArray<T,V> {
else begin = min(begin, length);
if (end < 0) end = max(length + end, begin);
else end = max(min(end, length), begin);
var slice = allocate_memory(offsetof<this>());
var slice = memory.allocate(offsetof<this>());
store<usize>(slice, this.buffer, offsetof<this>("buffer"));
store<i32>(slice, begin << alignof<T>(), offsetof<this>("byteOffset"));
store<i32>(slice, end << alignof<T>(), offsetof<this>("byteLength"));

View File

@ -1,7 +1,56 @@
function copy_memory(dest: usize, src: usize, n: usize): void {
// based on musl's implementation of memcpy
// not a future instruction and sufficiently covered by the upcoming move_memory intrinsic
export namespace memory {
@builtin export declare function size(): i32; // tslint:disable-line
@builtin export declare function grow(pages: i32): i32; // tslint:disable-line
export function fill(dest: usize, c: u8, n: usize): void { // see: musl/src/string/memset
if (isDefined(__memory_fill)) { __memory_fill(dest, c, n); return; } // tslint:disable-line
memset(dest, c, n);
}
export function copy(dest: usize, src: usize, n: usize): void { // see: musl/src/string/memmove.c
if (isDefined(__memory_copy)) { __memory_copy(dest, src, n); return; } // tslint:disable-line
memmove(dest, src, n);
}
export function compare(vl: usize, vr: usize, n: usize): i32 { // see: musl/src/string/memcmp.c
if (isDefined(__memory_compare)) return __memory_compare(vl, vr, n); // tslint:disable-line
return memcmp(vl, vr, n);
}
// Passive segments
// export function init(segmentIndex: u32, srcOffset: usize, dstOffset: usize, n: usize): void {
// __memory_init(segmentIndex, srcOffset, dstOffset);
// }
// export function drop(segmentIndex: u32): void {
// __memory_drop(segmentIndex);
// }
// Allocator
export function allocate(size: usize): usize {
if (isDefined(__memory_allocate)) return __memory_allocate(size); // tslint:disable-line
WARNING("Calling 'memory.allocate' requires a memory manager to be present.");
return <usize>unreachable();
}
export function free(ptr: usize): void {
if (isDefined(__memory_free)) { __memory_free(ptr); return; } // tslint:disable-line
WARNING("Calling 'memory.free' requires a memory manager to be present.");
unreachable();
}
export function reset(): void {
if (isDefined(__memory_reset)) { __memory_reset(); return; } // tslint:disable-line
unreachable();
}
}
// this function will go away once `memory.copy` becomes an intrinsic
function memcpy(dest: usize, src: usize, n: usize): void { // see: musl/src/string/memcpy.c
var w: u32, x: u32;
// copy 1 byte each until src is aligned to 4 bytes
@ -144,13 +193,11 @@ function copy_memory(dest: usize, src: usize, n: usize): void {
}
}
export function move_memory(dest: usize, src: usize, n: usize): void {
// based on musl's implementation of memmove
// becomes obsolete once https://github.com/WebAssembly/bulk-memory-operations lands
// this function will go away once `memory.copy` becomes an intrinsic
function memmove(dest: usize, src: usize, n: usize): void { // see: musl/src/string/memmove.c
if (dest == src) return;
if (src + n <= dest || dest + n <= src) {
copy_memory(dest, src, n);
memcpy(dest, src, n);
return;
}
if (dest < src) {
@ -188,9 +235,8 @@ export function move_memory(dest: usize, src: usize, n: usize): void {
}
}
export function set_memory(dest: usize, c: u8, n: usize): void {
// based on musl's implementation of memset
// becomes obsolete once https://github.com/WebAssembly/bulk-memory-operations lands
// this function will go away once `memory.fill` becomes an intrinsic
function memset(dest: usize, c: u8, n: usize): void { // see: musl/src/string/memset
// fill head and tail with minimal branching
if (!n) return;
@ -250,14 +296,10 @@ export function set_memory(dest: usize, c: u8, n: usize): void {
}
}
export function compare_memory(vl: usize, vr: usize, n: usize): i32 {
// based on musl's implementation of memcmp
// provided because there's no proposed alternative
function memcmp(vl: usize, vr: usize, n: usize): i32 { // see: musl/src/string/memcmp.c
if (vl == vr) return 0;
while (n && load<u8>(vl) == load<u8>(vr)) {
n--;
vl++;
vr++;
while (n != 0 && load<u8>(vl) == load<u8>(vr)) {
n--; vl++; vr++;
}
return n ? <i32>load<u8>(vl) - <i32>load<u8>(vr) : 0;
}

View File

@ -90,13 +90,13 @@ export class String {
if (outLen == 0) return EMPTY;
var out = allocate(outLen);
move_memory(
memory.copy(
changetype<usize>(out) + HEADER_SIZE,
changetype<usize>(this) + HEADER_SIZE,
thisLen << 1
);
move_memory(
memory.copy(
changetype<usize>(out) + HEADER_SIZE + (thisLen << 1),
changetype<usize>(other) + HEADER_SIZE,
otherLen << 1
@ -112,7 +112,7 @@ export class String {
var searchLength: isize = searchString.length;
var start: isize = end - searchLength;
if (start < 0) return false;
return !compare_memory(
return !memory.compare(
changetype<usize>(this) + HEADER_SIZE + (start << 1),
changetype<usize>(searchString) + HEADER_SIZE,
searchLength << 1
@ -127,7 +127,7 @@ export class String {
var leftLength = left.length;
if (leftLength != right.length) return false;
return !compare_memory(
return !memory.compare(
changetype<usize>(left) + HEADER_SIZE,
changetype<usize>(right) + HEADER_SIZE,
(<usize>leftLength << 1)
@ -150,7 +150,7 @@ export class String {
if (!rightLength) return true;
var length = <usize>min<i32>(leftLength, rightLength);
return compare_memory(
return memory.compare(
changetype<usize>(left) + HEADER_SIZE,
changetype<usize>(right) + HEADER_SIZE,
length << 1
@ -169,7 +169,7 @@ export class String {
if (!rightLength) return true;
var length = <usize>min<i32>(leftLength, rightLength);
return compare_memory(
return memory.compare(
changetype<usize>(left) + HEADER_SIZE,
changetype<usize>(right) + HEADER_SIZE,
length << 1
@ -187,7 +187,7 @@ export class String {
if (!leftLength) return true;
var length = <usize>min<i32>(leftLength, rightLength);
return compare_memory(
return memory.compare(
changetype<usize>(left) + HEADER_SIZE,
changetype<usize>(right) + HEADER_SIZE,
length << 1
@ -206,7 +206,7 @@ export class String {
if (!leftLength) return true;
var length = <usize>min<i32>(leftLength, rightLength);
return compare_memory(
return memory.compare(
changetype<usize>(left) + HEADER_SIZE,
changetype<usize>(right) + HEADER_SIZE,
length << 1
@ -228,7 +228,7 @@ export class String {
len -= searchLen;
// TODO: multiple char codes
for (let k: isize = start; k <= len; ++k) {
if (!compare_memory(
if (!memory.compare(
changetype<usize>(this) + HEADER_SIZE + (k << 1),
changetype<usize>(searchString) + HEADER_SIZE,
searchLen << 1
@ -250,7 +250,7 @@ export class String {
// TODO: multiple char codes
for (let k = start; k >= 0; --k) {
if (!compare_memory(
if (!memory.compare(
changetype<usize>(this) + HEADER_SIZE + (k << 1),
changetype<usize>(searchString) + HEADER_SIZE,
searchLen << 1
@ -272,7 +272,7 @@ export class String {
if (searchLength + start > len) {
return false;
}
return !compare_memory(
return !memory.compare(
changetype<usize>(this) + HEADER_SIZE + (start << 1),
changetype<usize>(searchString) + HEADER_SIZE,
searchLength << 1
@ -292,7 +292,7 @@ export class String {
return EMPTY;
}
var out = allocate(resultLength);
move_memory(
memory.copy(
changetype<usize>(out) + HEADER_SIZE,
changetype<usize>(this) + HEADER_SIZE + (intStart << 1),
<usize>resultLength << 1
@ -315,7 +315,7 @@ export class String {
return this;
}
var out = allocate(len);
move_memory(
memory.copy(
changetype<usize>(out) + HEADER_SIZE,
changetype<usize>(this) + HEADER_SIZE + (from << 1),
len << 1
@ -351,7 +351,7 @@ export class String {
return this;
}
var out = allocate(length);
move_memory(
memory.copy(
changetype<usize>(out) + HEADER_SIZE,
changetype<usize>(this) + HEADER_SIZE + (start << 1),
length << 1
@ -379,7 +379,7 @@ export class String {
return EMPTY;
}
var out = allocate(outLen);
move_memory(
memory.copy(
changetype<usize>(out) + HEADER_SIZE,
changetype<usize>(this) + HEADER_SIZE + (start << 1),
outLen << 1
@ -405,7 +405,7 @@ export class String {
return this;
}
var out = allocate(len);
move_memory(
memory.copy(
changetype<usize>(out) + HEADER_SIZE,
changetype<usize>(this) + HEADER_SIZE,
len << 1
@ -433,7 +433,7 @@ export class String {
* 'a' + 'a' => 'aa' + 'aa' => 'aaaa' + 'aaaa' etc
*/
for (let offset = 0, len = strLen * count; offset < len; offset += strLen) {
move_memory(
memory.copy(
changetype<usize>(result) + HEADER_SIZE + offset,
changetype<usize>(this) + HEADER_SIZE,
strLen
@ -472,7 +472,7 @@ export class String {
}
toUTF8(): usize {
var buf = allocate_memory(<usize>this.lengthUTF8);
var buf = memory.allocate(<usize>this.lengthUTF8);
var pos: usize = 0;
var end = <usize>this.length;
var off: usize = 0;

16
std/assembly/table.ts Normal file
View File

@ -0,0 +1,16 @@
export namespace table {
// export function copy(dst: u32, src: u32, n: u32): void {
// __table_copy(dst, src, n);
// }
// Passive elements
// export function init(elementIndex: u32, srcOffset: u32, dstOffset: u32, n: u32): void {
// __table_init(elementIndex, srcOffset, dstOffset, n);
// }
// export function drop(elementIndex: u32): void {
// __table_drop(elementIndex);
// }
}

View File

@ -14,7 +14,7 @@
/// <reference no-default-lib="true"/>
// Portable types
// Types
declare type i8 = number;
declare type i16 = number;
@ -28,9 +28,85 @@ declare type usize = number;
declare type f32 = number;
declare type f64 = number;
// Compiler hints
/** Compiler target. 0 = JS, 1 = WASM32, 2 = WASM64. */
declare const ASC_TARGET: i32;
// Builtins
/** Performs the sign-agnostic count leading zero bits operation on a 32-bit integer. All zero bits are considered leading if the value is zero. */
declare function clz<T = i32>(value: T): T;
/** Performs the sign-agnostic count tailing zero bits operation on a 32-bit integer. All zero bits are considered trailing if the value is zero. */
declare function ctz<T = i32>(value: T): T;
/** Performs the sign-agnostic count number of one bits operation on a 32-bit integer. */
declare function popcnt<T = i32>(value: T): T;
/** Performs the sign-agnostic rotate left operation on a 32-bit integer. */
declare function rotl<T = i32>(value: T, shift: T): T;
/** Performs the sign-agnostic rotate right operation on a 32-bit integer. */
declare function rotr<T = i32>(value: T, shift: T): T;
/** Computes the absolute value of an integer or float. */
declare function abs<T = i32 | f32 | f64>(value: T): T;
/** Determines the maximum of two integers or floats. If either operand is `NaN`, returns `NaN`. */
declare function max<T = i32 | f32 | f64>(left: T, right: T): T;
/** Determines the minimum of two integers or floats. If either operand is `NaN`, returns `NaN`. */
declare function min<T = i32 | f32 | f64>(left: T, right: T): T;
/** Composes a 32-bit or 64-bit float from the magnitude of `x` and the sign of `y`. */
declare function copysign<T = f32 | f64>(x: T, y: T): T;
/** Performs the ceiling operation on a 32-bit or 64-bit float. */
declare function ceil<T = f32 | f64>(value: T): T;
/** Performs the floor operation on a 32-bit or 64-bit float. */
declare function floor<T = f32 | f64>(value: T): T;
/** Rounds to the nearest integer tied to even of a 32-bit or 64-bit float. */
declare function nearest<T = f32 | f64>(value: T): T;
/** Selects one of two pre-evaluated values depending on the condition. */
declare function select<T>(ifTrue: T, ifFalse: T, condition: bool): T;
/** Calculates the square root of a 32-bit or 64-bit float. */
declare function sqrt<T = f32 | f64>(value: T): T;
/** Rounds to the nearest integer towards zero of a 32-bit or 64-bit float. */
declare function trunc<T = f32 | f64>(value: T): T;
/** Loads a value of the specified type from memory. Type must be `u8`. */
declare function load<T = u8>(ptr: usize, constantOffset?: usize): T;
/** Stores a value of the specified type to memory. Type must be `u8`. */
declare function store<T = u8>(ptr: usize, value: T, constantOffset?: usize): void;
/** Emits an unreachable operation that results in a runtime error when executed. */
declare function unreachable(): any; // sic
/** NaN (not a number) as a 32-bit or 64-bit float depending on context. */
declare const NaN: f32 | f64;
/** Positive infinity as a 32-bit or 64-bit float depending on context. */
declare const Infinity: f32 | f64;
/** Changes the type of any value of `usize` kind to another one of `usize` kind. Useful for casting class instances to their pointer values and vice-versa. Beware that this is unsafe.*/
declare function changetype<T>(value: any): T;
/** Explicitly requests no bounds checks on the provided expression. Useful for array accesses. */
declare function unchecked<T>(value: T): T;
/** Tests if a 32-bit or 64-bit float is `NaN`. */
declare function isNaN<T = f32 | f64>(value: T): bool;
/** Tests if a 32-bit or 64-bit float is finite, that is not `NaN` or +/-`Infinity`. */
declare function isFinite<T = f32 | f64>(value: T): bool;
/** Tests if the specified value is a valid integer. Can't distinguish an integer from an integral float. */
declare function isInteger(value: any): value is number;
/** Tests if the specified value is a valid float. Can't distinguish a float from an integer. */
declare function isFloat(value: any): value is number;
/** Tests if the specified value is of a reference type. */
declare function isReference(value: any): value is object | string;
/** Tests if the specified value can be used as a string. */
declare function isString(value: any): value is string | String;
/** Tests if the specified value can be used as an array. */
declare function isArray(value: any): value is Array<any>;
/** Traps if the specified value is not true-ish, otherwise returns the value. */
declare function assert<T>(isTrueish: T | null, message?: string): T;
/** Parses an integer string to a 64-bit float. */
declare function parseInt(str: string, radix?: i32): f64;
/** Parses an integer string to a 32-bit integer. */
declare function parseI32(str: string, radix?: i32): i32;
/** Parses a floating point string to a 64-bit float. */
declare function parseFloat(str: string): f64;
/** Returns the 64-bit floating-point remainder of `x/y`. */
declare function fmod(x: f64, y: f64): f64;
/** Returns the 32-bit floating-point remainder of `x/y`. */
declare function fmodf(x: f32, y: f32): f32;
/** Converts any other numeric value to an 8-bit signed integer. */
declare function i8(value: i8 | i16 | i32 | isize | u8 | u16 | u32 | usize | bool | f32 | f64): i8;
declare namespace i8 {
@ -136,92 +212,26 @@ declare namespace f64 {
export const EPSILON: f64;
}
// Portable built-ins
/** Performs the sign-agnostic count leading zero bits operation on a 32-bit integer. All zero bits are considered leading if the value is zero. */
declare function clz<T = i32>(value: T): T;
/** Performs the sign-agnostic count tailing zero bits operation on a 32-bit integer. All zero bits are considered trailing if the value is zero. */
declare function ctz<T = i32>(value: T): T;
/** Performs the sign-agnostic count number of one bits operation on a 32-bit integer. */
declare function popcnt<T = i32>(value: T): T;
/** Performs the sign-agnostic rotate left operation on a 32-bit integer. */
declare function rotl<T = i32>(value: T, shift: T): T;
/** Performs the sign-agnostic rotate right operation on a 32-bit integer. */
declare function rotr<T = i32>(value: T, shift: T): T;
/** Computes the absolute value of an integer or float. */
declare function abs<T = i32 | f32 | f64>(value: T): T;
/** Determines the maximum of two integers or floats. If either operand is `NaN`, returns `NaN`. */
declare function max<T = i32 | f32 | f64>(left: T, right: T): T;
/** Determines the minimum of two integers or floats. If either operand is `NaN`, returns `NaN`. */
declare function min<T = i32 | f32 | f64>(left: T, right: T): T;
/** Composes a 32-bit or 64-bit float from the magnitude of `x` and the sign of `y`. */
declare function copysign<T = f32 | f64>(x: T, y: T): T;
/** Performs the ceiling operation on a 32-bit or 64-bit float. */
declare function ceil<T = f32 | f64>(value: T): T;
/** Performs the floor operation on a 32-bit or 64-bit float. */
declare function floor<T = f32 | f64>(value: T): T;
/** Rounds to the nearest integer tied to even of a 32-bit or 64-bit float. */
declare function nearest<T = f32 | f64>(value: T): T;
/** Selects one of two pre-evaluated values depending on the condition. */
declare function select<T>(ifTrue: T, ifFalse: T, condition: bool): T;
/** Calculates the square root of a 32-bit or 64-bit float. */
declare function sqrt<T = f32 | f64>(value: T): T;
/** Rounds to the nearest integer towards zero of a 32-bit or 64-bit float. */
declare function trunc<T = f32 | f64>(value: T): T;
/** Allocates a chunk of memory of the specified size and returns a pointer to it. */
declare function allocate_memory(size: usize): usize;
/** Disposes a chunk of memory by its pointer. */
declare function free_memory(ptr: usize): void;
/** Copies n bytes from the specified source to the specified destination in memory. These regions may overlap. */
declare function move_memory(destination: usize, source: usize, n: usize): void;
/** Loads a value of the specified type from memory. Type must be `u8`. */
declare function load<T = u8>(ptr: usize, constantOffset?: usize): T;
/** Stores a value of the specified type to memory. Type must be `u8`. */
declare function store<T = u8>(ptr: usize, value: T, constantOffset?: usize): void;
/** Emits an unreachable operation that results in a runtime error when executed. */
declare function unreachable(): any; // sic
// Polyfills
/** [Polyfill] Performs the sign-agnostic reverse bytes **/
declare function bswap<T = i32 | u32 | isize | usize>(value: T): T;
/** [Polyfill] Performs the sign-agnostic reverse bytes only for last 16-bit **/
declare function bswap16<T = i16 | u16 | i32 | u32>(value: T): T;
/** Changes the type of any value of `usize` kind to another one of `usize` kind. Useful for casting class instances to their pointer values and vice-versa. Beware that this is unsafe.*/
declare function changetype<T>(value: any): T;
/** Explicitly requests no bounds checks on the provided expression. Useful for array accesses. */
declare function unchecked<T>(value: T): T;
/** Tests if a 32-bit or 64-bit float is `NaN`. */
declare function isNaN<T = f32 | f64>(value: T): bool;
/** Tests if a 32-bit or 64-bit float is finite, that is not `NaN` or +/-`Infinity`. */
declare function isFinite<T = f32 | f64>(value: T): bool;
/** Tests if the specified value is a valid integer. Can't distinguish an integer from an integral float. */
declare function isInteger(value: any): value is number;
/** Tests if the specified value is a valid float. Can't distinguish a float from an integer. */
declare function isFloat(value: any): value is number;
/** Tests if the specified value is of a reference type. */
declare function isReference(value: any): value is object | string;
/** Tests if the specified value can be used as a string. */
declare function isString(value: any): value is string | String;
/** Tests if the specified value can be used as an array. */
declare function isArray(value: any): value is Array<any>;
/** Traps if the specified value is not true-ish, otherwise returns the value. */
declare function assert<T>(isTrueish: T | null, message?: string): T;
/** Parses an integer string to a 64-bit float. */
declare function parseInt(str: string, radix?: i32): f64;
/** Parses an integer string to a 32-bit integer. */
declare function parseI32(str: string, radix?: i32): i32;
/** Parses a floating point string to a 64-bit float. */
declare function parseFloat(str: string): f64;
/** Returns the 64-bit floating-point remainder of `x/y`. */
declare function fmod(x: f64, y: f64): f64;
/** Returns the 32-bit floating-point remainder of `x/y`. */
declare function fmodf(x: f32, y: f32): f32;
// Standard library
// Portable standard library
// Everything marked @deprecated is a temporary filler. Do not use.
declare const NaN: f32 | f64;
declare const Infinity: f32 | f64;
/** Memory operations. */
declare namespace memory {
/** Allocates a chunk of memory of the specified size and returns a pointer to it. */
function allocate(size: usize): usize;
/** Disposes a chunk of memory by its pointer. */
function free(ptr: usize): void;
/** Copies n bytes from the specified source to the specified destination in memory. These regions may overlap. */
function copy(dst: usize, src: usize, n: usize): void;
/** Resets the allocator to its initial state, if supported. */
function reset(): void;
}
/** Class representing a generic, fixed-length raw binary data buffer. */
declare class ArrayBuffer {

View File

@ -218,4 +218,32 @@ globalScope["fmodf"] = function fmodf(x, y) {
globalScope["JSMath"] = Math;
require("./memory")(globalScope);
globalScope["memory"] = (() => {
var HEAP = new Uint8Array(0);
var HEAP_OFFSET = 0;
return {
allocate: globalScope["__memory_allocate"] || function allocate(size) {
if (!(size >>>= 0)) return 0;
if (HEAP_OFFSET + size > HEAP.length) {
var oldHeap = HEAP;
HEAP = new Uint8Array(Math.max(65536, HEAP.length + size, HEAP.length * 2));
HEAP.set(oldHeap);
}
var ptr = HEAP_OFFSET;
if ((HEAP_OFFSET += size) & 7) HEAP_OFFSET = (HEAP_OFFSET | 7) + 1;
return ptr;
},
free: globalScope["__memory_free"] || function free(ptr) { },
copy: globalScope["__memory_copy"] || function copy(dest, src, size) {
HEAP.copyWithin(dest, src, src + size);
}
};
})();
globalScope["store"] = globalScope["__store"] || function store(ptr, value, offset) {
HEAP[ptr + (offset | 0)] = value;
};
globalScope["load"] = globalScope["__load"] || function load(ptr, offset) {
return HEAP[ptr + (offset | 0)];
};

View File

@ -1,36 +0,0 @@
module.exports = globalScope => {
var HEAP = new Uint8Array(0);
var HEAP_OFFSET = 0;
globalScope["allocate_memory"] = function allocate_memory(size) {
if (!(size >>>= 0)) return 0;
if (HEAP_OFFSET + size > HEAP.length) {
var oldHeap = HEAP;
HEAP = new Uint8Array(Math.max(65536, HEAP.length + size, HEAP.length * 2));
HEAP.set(oldHeap);
}
var ptr = HEAP_OFFSET;
if ((HEAP_OFFSET += size) & 7)
HEAP_OFFSET = (HEAP_OFFSET | 7) + 1;
return ptr;
};
globalScope["free_memory"] = function free_memory(ptr) {
// TODO
};
globalScope["move_memory"] = function move_memory(dest, src, size) {
HEAP.copyWithin(dest, src, src + size);
};
globalScope["store"] = function store(ptr, value, offset) {
HEAP[ptr + (offset | 0)] = value;
};
globalScope["load"] = function load(ptr, offset) {
return HEAP[ptr + (offset | 0)];
};
};