diff --git a/package.json b/package.json index 668a34ca..c25aa957 100644 --- a/package.json +++ b/package.json @@ -43,10 +43,9 @@ "scripts": { "build": "webpack --mode production --display-modules", "clean": "node scripts/clean", - "check": "npm run check:config && npm run check:compiler && npm run check:library", + "check": "npm run check:config && npm run check:compiler", "check:config": "tsc --noEmit -p src --diagnostics --listFiles", "check:compiler": "tslint -c tslint.json --project src --formatters-dir lib/lint/formatters --format as", - "check:library": "tslint -c tslint.json --project std/assembly --formatters-dir lib/lint/formatters --format as", "test": "npm run test:parser && npm run test:compiler", "test:parser": "node tests/parser", "test:compiler": "node tests/compiler", diff --git a/src/ast.ts b/src/ast.ts index 52bbbb71..b06912c1 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1163,8 +1163,7 @@ export enum DecoratorKind { BUILTIN, LAZY, START, - UNSAFE, - STUB + UNSAFE } /** Returns the kind of the specified decorator. Defaults to {@link DecoratorKind.CUSTOM}. */ @@ -1201,7 +1200,6 @@ export function decoratorNameToKind(name: Expression): DecoratorKind { case CharCode.s: { if (nameStr == "sealed") return DecoratorKind.SEALED; if (nameStr == "start") return DecoratorKind.START; - if (nameStr == "stub") return DecoratorKind.STUB; break; } case CharCode.u: { diff --git a/src/builtins.ts b/src/builtins.ts index 39e7be83..369fd4aa 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -21,8 +21,7 @@ import { LiteralKind, LiteralExpression, StringLiteralExpression, - CallExpression, - ElementAccessExpression + CallExpression } from "./ast"; import { @@ -100,7 +99,6 @@ export namespace BuiltinSymbols { export const isFunction = "~lib/builtins/isFunction"; export const isNullable = "~lib/builtins/isNullable"; export const isDefined = "~lib/builtins/isDefined"; - export const isImplemented = "~lib/builtins/isImplemented"; export const isConstant = "~lib/builtins/isConstant"; export const isManaged = "~lib/builtins/isManaged"; @@ -472,14 +470,16 @@ export namespace BuiltinSymbols { export const WARNING = "~lib/diagnostics/WARNING"; export const INFO = "~lib/diagnostics/INFO"; - // std/runtime.ts - export const HEAP_BASE = "~lib/runtime/HEAP_BASE"; - export const memory_size = "~lib/runtime/memory.size"; - export const memory_grow = "~lib/runtime/memory.grow"; - export const memory_copy = "~lib/runtime/memory.copy"; - export const memory_fill = "~lib/runtime/memory.fill"; - export const gc_classId = "~lib/runtime/gc.classId"; - export const gc_iterateRoots = "~lib/runtime/gc.iterateRoots"; + // std/memory.ts + export const HEAP_BASE = "~lib/memory/HEAP_BASE"; + export const memory_size = "~lib/memory/memory.size"; + export const memory_grow = "~lib/memory/memory.grow"; + export const memory_copy = "~lib/memory/memory.copy"; + export const memory_fill = "~lib/memory/memory.fill"; + + // std/gc.ts + export const gc_classId = "~lib/gc/gc.classId"; + export const gc_iterateRoots = "~lib/gc/gc.iterateRoots"; // std/typedarray.ts export const Int8Array = "~lib/typedarray/Int8Array"; @@ -621,20 +621,6 @@ export function compileCall( ); return module.createI32(element ? 1 : 0); } - case BuiltinSymbols.isImplemented: { // isImplemented(expression) -> bool - compiler.currentType = Type.bool; - if ( - checkTypeAbsent(typeArguments, reportNode, prototype) | - checkArgsRequired(operands, 1, reportNode, compiler) - ) return module.createUnreachable(); - let element = compiler.resolver.resolveExpression( - operands[0], - compiler.currentFlow, - Type.void, - ReportMode.SWALLOW - ); - return module.createI32(element && !element.hasDecorator(DecoratorFlags.STUB) ? 1 : 0); - } case BuiltinSymbols.isConstant: { // isConstant(expression) -> bool compiler.currentType = Type.bool; if ( diff --git a/src/program.ts b/src/program.ts index 81f83c6f..7db97ef6 100644 --- a/src/program.ts +++ b/src/program.ts @@ -930,7 +930,7 @@ export class Program extends DiagnosticEmitter { ensureGlobal(name: string, element: DeclaredElement): DeclaredElement { var elementsByName = this.elementsByName; if (elementsByName.has(name)) { - let actual = elementsByName.get(name); + let actual = elementsByName.get(name)!; // NOTE: this is effectively only performed when merging native types with // their respective namespaces in std/builtins, but can also trigger when a // user has multiple global elements of the same name in different files, @@ -1176,7 +1176,7 @@ export class Program extends DiagnosticEmitter { ): void { var name = declaration.name.text; var isStatic = declaration.is(CommonFlags.STATIC); - var acceptedFlags = DecoratorFlags.INLINE; + var acceptedFlags = DecoratorFlags.INLINE | DecoratorFlags.UNSAFE; if (!declaration.is(CommonFlags.GENERIC)) { acceptedFlags |= DecoratorFlags.OPERATOR_BINARY | DecoratorFlags.OPERATOR_PREFIX @@ -1549,7 +1549,7 @@ export class Program extends DiagnosticEmitter { parent: Element ): void { var name = declaration.name.text; - var validDecorators = DecoratorFlags.UNSAFE | DecoratorFlags.STUB; + var validDecorators = DecoratorFlags.UNSAFE; if (declaration.is(CommonFlags.AMBIENT)) { validDecorators |= DecoratorFlags.EXTERNAL; } else { @@ -1791,9 +1791,7 @@ export enum DecoratorFlags { /** Is the explicit start function. */ START = 1 << 10, /** Is considered unsafe code. */ - UNSAFE = 1 << 11, - /** Is a stub that can be overridden. */ - STUB = 1 << 12 + UNSAFE = 1 << 11 } /** Translates a decorator kind to the respective decorator flag. */ @@ -1812,7 +1810,6 @@ export function decoratorKindToFlag(kind: DecoratorKind): DecoratorFlags { case DecoratorKind.LAZY: return DecoratorFlags.LAZY; case DecoratorKind.START: return DecoratorFlags.START; case DecoratorKind.UNSAFE: return DecoratorFlags.UNSAFE; - case DecoratorKind.STUB: return DecoratorFlags.STUB; default: return DecoratorFlags.NONE; } } @@ -1888,8 +1885,8 @@ export abstract class Element { if (!members) this.members = members = new Map(); else if (members.has(name)) { let actual = members.get(name)!; - if (actual.parent !== this || actual.hasDecorator(DecoratorFlags.STUB)) { - // override non-own or stub element + if (actual.parent !== this) { + // override non-own element } else { let merged = tryMerge(actual, element); if (merged) { diff --git a/std/assembly/allocator/arena.ts b/std/assembly/allocator/arena.ts index dfe694d7..94c2a232 100644 --- a/std/assembly/allocator/arena.ts +++ b/std/assembly/allocator/arena.ts @@ -1,41 +1,30 @@ -/** - * Arena Memory Allocator - * - * 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 - *//***/ - +import { HEAP_BASE, memory } from "../memory"; import { AL_MASK, MAX_SIZE_32 } from "../util/allocator"; -var startOffset: usize = (HEAP_BASE + AL_MASK) & ~AL_MASK; -var offset: usize = startOffset; +@lazy var startOffset: usize = (HEAP_BASE + AL_MASK) & ~AL_MASK; +@lazy var offset: usize = startOffset; -// Memory allocator implementation -@global namespace memory { - - export function allocate(size: usize): usize { - if (size > MAX_SIZE_32) unreachable(); - var ptr = offset; - var newPtr = (ptr + max(size, 1) + AL_MASK) & ~AL_MASK; - var pagesBefore = memory.size(); - if (newPtr > pagesBefore << 16) { - let pagesNeeded = ((newPtr - ptr + 0xffff) & ~0xffff) >>> 16; - let pagesWanted = max(pagesBefore, pagesNeeded); // double memory - if (memory.grow(pagesWanted) < 0) { - if (memory.grow(pagesNeeded) < 0) { - unreachable(); // out of memory - } +@unsafe @global function __memory_allocate(size: usize): usize { + if (size > MAX_SIZE_32) unreachable(); + var ptr = offset; + var newPtr = (ptr + max(size, 1) + AL_MASK) & ~AL_MASK; + var pagesBefore = memory.size(); + if (newPtr > pagesBefore << 16) { + let pagesNeeded = ((newPtr - ptr + 0xffff) & ~0xffff) >>> 16; + let pagesWanted = max(pagesBefore, pagesNeeded); // double memory + if (memory.grow(pagesWanted) < 0) { + if (memory.grow(pagesNeeded) < 0) { + unreachable(); // out of memory } } - offset = newPtr; - return ptr; - } - - export function free(ptr: usize): void { /* nop */ } - - export function reset(): void { - offset = startOffset; } + offset = newPtr; + return ptr; +} + +@unsafe @global function __memory_free(ptr: usize): void { +} + +@unsafe @global function __memory_reset(): void { + offset = startOffset; } diff --git a/std/assembly/allocator/buddy.ts b/std/assembly/allocator/buddy.ts deleted file mode 100644 index cec54d04..00000000 --- a/std/assembly/allocator/buddy.ts +++ /dev/null @@ -1,542 +0,0 @@ -/** - * Buddy Memory Allocator. - * @module std/assembly/allocator/buddy - *//***/ - -/* - Copyright 2018 Evan Wallace - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - -*/// see: https://github.com/evanw/buddy-malloc - -/* - * This file implements a buddy memory allocator, which is an allocator that - * allocates memory within a fixed linear address range. It spans the address - * range with a binary tree that tracks free space. Both "malloc" and "free" - * are O(log N) time where N is the maximum possible number of allocations. - * - * The "buddy" term comes from how the tree is used. When memory is allocated, - * nodes in the tree are split recursively until a node of the appropriate size - * is reached. Every split results in two child nodes, each of which is the - * buddy of the other. When a node is freed, the node and its buddy can be - * merged again if the buddy is also free. This makes the memory available - * for larger allocations again. - */ - -/* - * Every allocation needs an 8-byte header to store the allocation size while - * staying 8-byte aligned. The address returned by "malloc" is the address - * right after this header (i.e. the size occupies the 8 bytes before the - * returned address). - */ -const HEADER_SIZE: usize = 8; - -/* - * The minimum allocation size is 16 bytes because we have an 8-byte header and - * we need to stay 8-byte aligned. - */ -const MIN_ALLOC_LOG2: usize = 4; -const MIN_ALLOC: usize = 1 << MIN_ALLOC_LOG2; - -/* - * The maximum allocation size is currently set to 2gb. This is the total size - * of the heap. It's technically also the maximum allocation size because the - * heap could consist of a single allocation of this size. But of course real - * heaps will have multiple allocations, so the real maximum allocation limit - * is at most 1gb. - */ -const MAX_ALLOC_LOG2: usize = 30; // 31; -const MAX_ALLOC: usize = 1 << MAX_ALLOC_LOG2; - -/* - * Allocations are done in powers of two starting from MIN_ALLOC and ending at - * MAX_ALLOC inclusive. Each allocation size has a bucket that stores the free - * list for that allocation size. - * - * Given a bucket index, the size of the allocations in that bucket can be - * found with "(size_t)1 << (MAX_ALLOC_LOG2 - bucket)". - */ -const BUCKET_COUNT: usize = MAX_ALLOC_LOG2 - MIN_ALLOC_LOG2 + 1; - -/* - * Free lists are stored as circular doubly-linked lists. Every possible - * allocation size has an associated free list that is threaded through all - * currently free blocks of that size. That means MIN_ALLOC must be at least - * "sizeof(list_t)". MIN_ALLOC is currently 16 bytes, so this will be true for - * both 32-bit and 64-bit. - */ -@unmanaged -class List { - prev: List; - next: List; - static readonly SIZE: usize = 2 * sizeof(); -} - -/* - * Each bucket corresponds to a certain allocation size and stores a free list - * for that size. The bucket at index 0 corresponds to an allocation size of - * MAX_ALLOC (i.e. the whole address space). - */ -var BUCKETS_START: usize = HEAP_BASE; -var BUCKETS_END: usize = BUCKETS_START + BUCKET_COUNT * List.SIZE; - -function buckets$get(index: usize): List { - assert(index < BUCKET_COUNT); - return changetype(BUCKETS_START + index * List.SIZE); -} - -/* - * We could initialize the allocator by giving it one free block the size of - * the entire address space. However, this would cause us to instantly reserve - * half of the entire address space on the first allocation, since the first - * split would store a free list entry at the start of the right child of the - * root. Instead, we have the tree start out small and grow the size of the - * tree as we use more memory. The size of the tree is tracked by this value. - */ -var bucket_limit: usize; - -/* - * This array represents a linearized binary tree of bits. Every possible - * allocation larger than MIN_ALLOC has a node in this tree (and therefore a - * bit in this array). - * - * Given the index for a node, lineraized binary trees allow you to traverse to - * the parent node or the child nodes just by doing simple arithmetic on the - * index: - * - * - Move to parent: index = (index - 1) / 2; - * - Move to left child: index = index * 2 + 1; - * - Move to right child: index = index * 2 + 2; - * - Move to sibling: index = ((index - 1) ^ 1) + 1; - * - * Each node in this tree can be in one of several states: - * - * - UNUSED (both children are UNUSED) - * - SPLIT (one child is UNUSED and the other child isn't) - * - USED (neither children are UNUSED) - * - * These states take two bits to store. However, it turns out we have enough - * information to distinguish between UNUSED and USED from context, so we only - * need to store SPLIT or not, which only takes a single bit. - * - * Note that we don't need to store any nodes for allocations of size MIN_ALLOC - * since we only ever care about parent nodes. - */ -const SPLIT_COUNT: usize = (1 << (BUCKET_COUNT - 1)) / 8; -var NODE_IS_SPLIT_START: usize = BUCKETS_END; -var NODE_IS_SPLIT_END: usize = NODE_IS_SPLIT_START + SPLIT_COUNT * sizeof(); - -function node_is_split$get(index: usize): i32 { - assert(index < SPLIT_COUNT); - return load(NODE_IS_SPLIT_START + index); -} - -function node_is_split$set(index: usize, state: i32): void { - assert(index < SPLIT_COUNT); - store(NODE_IS_SPLIT_START + index, state); -} - -/* - * This is the starting address of the address range for this allocator. Every - * returned allocation will be an offset of this pointer from 0 to MAX_ALLOC. - */ -var base_ptr: usize; - -/* - * This is the maximum address that has ever been used by the allocator. It's - * used to know when to call "brk" to request more memory from the kernel. - */ -var max_ptr: usize; - -/* - * Make sure all addresses before "new_value" are valid and can be used. Memory - * is allocated in a 2gb address range but that memory is not reserved up - * front. It's only reserved when it's needed by calling this function. This - * will return false if the memory could not be reserved. - */ -function update_max_ptr(new_value: usize): i32 { - if (new_value > max_ptr) { - // if (brk(new_value)) { - // return 0; - // } - let oldPages = memory.size(); - let newPages = (((new_value + 0xffff) & ~0xffff) >>> 16); - assert(newPages > oldPages); - if (memory.grow(newPages - oldPages) < 0) { - return 0; - } - // max_ptr = new_value; - max_ptr = newPages << 16; - } - return 1; -} - -/* - * Initialize a list to empty. Because these are circular lists, an "empty" - * list is an entry where both links point to itself. This makes insertion - * and removal simpler because they don't need any branches. - */ -function list_init(list: List): void { - list.prev = list; - list.next = list; -} - -/* - * Append the provided entry to the end of the list. This assumes the entry - * isn't in a list already because it overwrites the linked list pointers. - */ -function list_push(list: List, entry: List): void { - var prev = list.prev; - entry.prev = prev; - entry.next = list; - prev.next = entry; - list.prev = entry; -} - -/* - * Remove the provided entry from whichever list it's currently in. This - * assumes that the entry is in a list. You don't need to provide the list - * because the lists are circular, so the list's pointers will automatically - * be updated if the first or last entries are removed. - */ -function list_remove(entry: List): void { - var prev = entry.prev; - var next = entry.next; - prev.next = next; - next.prev = prev; -} - -/* - * Remove and return the first entry in the list or NULL if the list is empty. - */ -function list_pop(list: List): List | null { - var back = list.prev; - if (back == list) return null; - list_remove(back); - return back; -} - -/* - * This maps from the index of a node to the address of memory that node - * represents. The bucket can be derived from the index using a loop but is - * required to be provided here since having them means we can avoid the loop - * and have this function return in constant time. - */ -function ptr_for_node(index: usize, bucket: usize): usize { - return base_ptr + ((index - (1 << bucket) + 1) << (MAX_ALLOC_LOG2 - bucket)); -} - -/* - * This maps from an address of memory to the node that represents that - * address. There are often many nodes that all map to the same address, so - * the bucket is needed to uniquely identify a node. - */ -function node_for_ptr(ptr: usize, bucket: usize): usize { - return ((ptr - base_ptr) >> (MAX_ALLOC_LOG2 - bucket)) + (1 << bucket) - 1; -} - -/* - * Given the index of a node, this returns the "is split" flag of the parent. - */ -function parent_is_split(index: usize): bool { - index = (index - 1) / 2; - return ((node_is_split$get(index / 8) >>> (index % 8)) & 1) == 1; -} - -/* - * Given the index of a node, this flips the "is split" flag of the parent. - */ -function flip_parent_is_split(index: usize): void { - index = (index - 1) / 2; - var indexDiv8 = index / 8; - node_is_split$set(indexDiv8, - node_is_split$get(indexDiv8) ^ (1 << (index % 8)) - ); -} - -/* - * Given the requested size passed to "malloc", this function returns the index - * of the smallest bucket that can fit that size. - */ -function bucket_for_request(request: usize): usize { - var bucket = BUCKET_COUNT - 1; - var size = MIN_ALLOC; - - while (size < request) { - bucket--; - size *= 2; - } - - return bucket; -} - -/* - * The tree is always rooted at the current bucket limit. This call grows the - * tree by repeatedly doubling it in size until the root lies at the provided - * bucket index. Each doubling lowers the bucket limit by 1. - */ -function lower_bucket_limit(bucket: usize): u32 { - while (bucket < bucket_limit) { - let root = node_for_ptr(base_ptr, bucket_limit); - let right_child: usize; - - /* - * If the parent isn't SPLIT, that means the node at the current bucket - * limit is UNUSED and our address space is entirely free. In that case, - * clear the root free list, increase the bucket limit, and add a single - * block with the newly-expanded address space to the new root free list. - */ - if (!parent_is_split(root)) { - list_remove(changetype(base_ptr)); - list_init(buckets$get(--bucket_limit)); - list_push(buckets$get(bucket_limit), changetype(base_ptr)); - continue; - } - - /* - * Otherwise, the tree is currently in use. Create a parent node for the - * current root node in the SPLIT state with a right child on the free - * list. Make sure to reserve the memory for the free list entry before - * writing to it. Note that we do not need to flip the "is split" flag for - * our current parent because it's already on (we know because we just - * checked it above). - */ - right_child = ptr_for_node(root + 1, bucket_limit); - if (!update_max_ptr(right_child + List.SIZE)) { - return 0; - } - list_push(buckets$get(bucket_limit), changetype(right_child)); - list_init(buckets$get(--bucket_limit)); - - /* - * Set the grandparent's SPLIT flag so if we need to lower the bucket limit - * again, we'll know that the new root node we just added is in use. - */ - root = (root - 1) / 2; - if (root != 0) { - flip_parent_is_split(root); - } - } - - return 1; -} - -// Memory allocator implementation -@global namespace memory { - - export function allocate(request: usize): usize { - var original_bucket: usize, bucket: usize; - - /* - * Make sure it's possible for an allocation of this size to succeed. There's - * a hard-coded limit on the maximum allocation size because of the way this - * allocator works. - */ - if (request > MAX_ALLOC - HEADER_SIZE) unreachable(); - - /* - * Initialize our global state if this is the first call to "malloc". At the - * beginning, the tree has a single node that represents the smallest - * possible allocation size. More memory will be reserved later as needed. - */ - 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 = memory.size() << 16; // must grow first - bucket_limit = BUCKET_COUNT - 1; - if (!update_max_ptr(base_ptr + List.SIZE)) { - return 0; - } - list_init(buckets$get(BUCKET_COUNT - 1)); - list_push(buckets$get(BUCKET_COUNT - 1), changetype(base_ptr)); - } - - /* - * Find the smallest bucket that will fit this request. This doesn't check - * that there's space for the request yet. - */ - bucket = bucket_for_request(request + HEADER_SIZE); - original_bucket = bucket; - - /* - * Search for a bucket with a non-empty free list that's as large or larger - * than what we need. If there isn't an exact match, we'll need to split a - * larger one to get a match. - */ - while (bucket + 1 != 0) { - let size: usize, bytes_needed: usize, i: usize; - let ptr: usize; - - /* - * We may need to grow the tree to be able to fit an allocation of this - * size. Try to grow the tree and stop here if we can't. - */ - if (!lower_bucket_limit(bucket)) { - return 0; - } - - /* - * Try to pop a block off the free list for this bucket. If the free list - * is empty, we're going to have to split a larger block instead. - */ - ptr = changetype(list_pop(buckets$get(bucket))); - if (!ptr) { - /* - * If we're not at the root of the tree or it's impossible to grow the - * tree any more, continue on to the next bucket. - */ - if (bucket != bucket_limit || bucket == 0) { - bucket--; - continue; - } - - /* - * Otherwise, grow the tree one more level and then pop a block off the - * free list again. Since we know the root of the tree is used (because - * the free list was empty), this will add a parent above this node in - * the SPLIT state and then add the new right child node to the free list - * for this bucket. Popping the free list will give us this right child. - */ - if (!lower_bucket_limit(bucket - 1)) { - return 0; - } - ptr = changetype(list_pop(buckets$get(bucket))); - } - - /* - * Try to expand the address space first before going any further. If we - * have run out of space, put this block back on the free list and fail. - */ - size = 1 << (MAX_ALLOC_LOG2 - bucket); - bytes_needed = bucket < original_bucket ? size / 2 + List.SIZE : size; - if (!update_max_ptr(ptr + bytes_needed)) { - list_push(buckets$get(bucket), changetype(ptr)); - return 0; - } - - /* - * If we got a node off the free list, change the node from UNUSED to USED. - * This involves flipping our parent's "is split" bit because that bit is - * the exclusive-or of the UNUSED flags of both children, and our UNUSED - * flag (which isn't ever stored explicitly) has just changed. - * - * Note that we shouldn't ever need to flip the "is split" bit of our - * grandparent because we know our buddy is USED so it's impossible for our - * grandparent to be UNUSED (if our buddy chunk was UNUSED, our parent - * wouldn't ever have been split in the first place). - */ - i = node_for_ptr(ptr, bucket); - if (i != 0) { - flip_parent_is_split(i); - } - - /* - * If the node we got is larger than we need, split it down to the correct - * size and put the new unused child nodes on the free list in the - * corresponding bucket. This is done by repeatedly moving to the left - * child, splitting the parent, and then adding the right child to the free - * list. - */ - while (bucket < original_bucket) { - i = i * 2 + 1; - bucket++; - flip_parent_is_split(i); - list_push( - buckets$get(bucket), - changetype(ptr_for_node(i + 1, bucket)) - ); - } - - /* - * Now that we have a memory address, write the block header (just the size - * of the allocation) and return the address immediately after the header. - */ - store(ptr, request); - return ptr + HEADER_SIZE; - } - - return 0; - } - - export function free(ptr: usize): void { - var bucket: usize, i: usize; - - /* - * Ignore any attempts to free a NULL pointer. - */ - if (!ptr) { - return; - } - - /* - * We were given the address returned by "malloc" so get back to the actual - * address of the node by subtracting off the size of the block header. Then - * look up the index of the node corresponding to this address. - */ - ptr = ptr - HEADER_SIZE; - bucket = bucket_for_request(load(ptr) + HEADER_SIZE); - i = node_for_ptr(ptr, bucket); - - /* - * Traverse up to the root node, flipping USED blocks to UNUSED and merging - * UNUSED buddies together into a single UNUSED parent. - */ - while (i != 0) { - /* - * Change this node from UNUSED to USED. This involves flipping our - * parent's "is split" bit because that bit is the exclusive-or of the - * UNUSED flags of both children, and our UNUSED flag (which isn't ever - * stored explicitly) has just changed. - */ - flip_parent_is_split(i); - - /* - * If the parent is now SPLIT, that means our buddy is USED, so don't merge - * with it. Instead, stop the iteration here and add ourselves to the free - * list for our bucket. - * - * Also stop here if we're at the current root node, even if that root node - * is now UNUSED. Root nodes don't have a buddy so we can't merge with one. - */ - if (parent_is_split(i) || bucket == bucket_limit) { - break; - } - - /* - * If we get here, we know our buddy is UNUSED. In this case we should - * merge with that buddy and continue traversing up to the root node. We - * need to remove the buddy from its free list here but we don't need to - * add the merged parent to its free list yet. That will be done once after - * this loop is finished. - */ - list_remove(changetype(ptr_for_node(((i - 1) ^ 1) + 1, bucket))); - i = (i - 1) / 2; - bucket--; - } - - /* - * Add ourselves to the free list for our bucket. We add to the back of the - * list because "malloc" takes from the back of the list and we want a "free" - * followed by a "malloc" of the same size to ideally use the same address - * for better memory locality. - */ - list_push(buckets$get(bucket), changetype(ptr_for_node(i, bucket))); - } -} diff --git a/std/assembly/allocator/emscripten.ts b/std/assembly/allocator/emscripten.ts index e34a4bd9..3ab1d083 100644 --- a/std/assembly/allocator/emscripten.ts +++ b/std/assembly/allocator/emscripten.ts @@ -1,24 +1,10 @@ -/** - * Emscripten Memory Allocator. - * - * Uses Emscripten's exported _malloc and _free implementations, i.e., when linking with - * Emscripten-compiled programs that already provide these. Differs from 'system' in that their - * names are prefixed with an underscore. - * - * @module std/assembly/allocator/emscripten - *//***/ +@unsafe declare function _malloc(size: usize): usize; +@unsafe declare function _free(ptr: usize): void; -declare function _malloc(size: usize): usize; -declare function _free(ptr: usize): void; - -// Memory allocator implementation -@global namespace memory { - - @inline export function allocate(size: usize): usize { - return _malloc(size); - } - - @inline export function free(ptr: usize): void { - _free(ptr); - } +@unsafe @global function __memory_allocate(size: usize): usize { + return _malloc(size); +} + +@unsafe @global function __memory_free(ptr: usize): void { + _free(ptr); } diff --git a/std/assembly/allocator/system.ts b/std/assembly/allocator/system.ts index 46dd3827..1c88cbe2 100644 --- a/std/assembly/allocator/system.ts +++ b/std/assembly/allocator/system.ts @@ -1,23 +1,10 @@ -/** - * System Memory Allocator. - * - * Uses the environment's malloc and free implementations, i.e., when linking with other C-like - * programs that already provide these. - * - * @module std/assembly/allocator/system - *//***/ +@unsafe declare function malloc(size: usize): usize; +@unsafe declare function free(ptr: usize): void; -declare function malloc(size: usize): usize; -declare function free(ptr: usize): void; - -// Memory allocator interface -@global namespace memory { - - @inline export function allocate(size: usize): usize { - return malloc(size); - } - - @inline export function free(ptr: usize): void { - free(ptr); - } +@unsafe @global function __memory_allocate(size: usize): usize { + return malloc(size); +} + +@unsafe @global function __memory_free(ptr: usize): void { + free(ptr); } diff --git a/std/assembly/allocator/tlsf.ts b/std/assembly/allocator/tlsf.ts index 4d2b9faf..ec766ffb 100644 --- a/std/assembly/allocator/tlsf.ts +++ b/std/assembly/allocator/tlsf.ts @@ -1,12 +1,3 @@ -/** - * Two-Level Segregate Fit Memory Allocator. - * - * A general purpose dynamic memory allocator specifically designed to meet real-time requirements. - * Always aligns to 8 bytes. - * - * @module std/assembly/allocator/tlsf - *//***/ - // ╒══════════════ Block size interpretation (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 @@ -16,6 +7,7 @@ // FL: first level, SL: second level, AL: alignment, SB: small block import { AL_BITS, AL_SIZE, AL_MASK } from "../util/allocator"; +import { HEAP_BASE, memory } from "../memory"; const SL_BITS: u32 = 5; const SL_SIZE: usize = 1 << SL_BITS; @@ -429,68 +421,64 @@ function fls(word: T): T { /** Reference to the initialized {@link Root} structure, once initialized. */ var ROOT: Root = changetype(0); -// Memory allocator interface -@global namespace memory { - - /** Allocates a chunk of memory. */ - export function allocate(size: usize): usize { - // initialize if necessary - var root = ROOT; - if (!root) { - let rootOffset = (HEAP_BASE + AL_MASK) & ~AL_MASK; - let pagesBefore = memory.size(); - let pagesNeeded = ((((rootOffset + Root.SIZE) + 0xffff) & ~0xffff) >>> 16); - if (pagesNeeded > pagesBefore && memory.grow(pagesNeeded - pagesBefore) < 0) unreachable(); - ROOT = root = changetype(rootOffset); - root.tailRef = 0; - root.flMap = 0; - for (let fl: usize = 0; fl < FL_BITS; ++fl) { - root.setSLMap(fl, 0); - for (let sl: u32 = 0; sl < SL_SIZE; ++sl) { - root.setHead(fl, sl, null); - } +/** Allocates a chunk of memory. */ +@unsafe @global function __memory_allocate(size: usize): usize { + // initialize if necessary + var root = ROOT; + if (!root) { + let rootOffset = (HEAP_BASE + AL_MASK) & ~AL_MASK; + let pagesBefore = memory.size(); + let pagesNeeded = ((((rootOffset + Root.SIZE) + 0xffff) & ~0xffff) >>> 16); + if (pagesNeeded > pagesBefore && memory.grow(pagesNeeded - pagesBefore) < 0) unreachable(); + ROOT = root = changetype(rootOffset); + root.tailRef = 0; + root.flMap = 0; + for (let fl: usize = 0; fl < FL_BITS; ++fl) { + root.setSLMap(fl, 0); + for (let sl: u32 = 0; sl < SL_SIZE; ++sl) { + root.setHead(fl, sl, null); } - root.addMemory((rootOffset + Root.SIZE + AL_MASK) & ~AL_MASK, memory.size() << 16); } - - // search for a suitable block - if (size > Block.MAX_SIZE) unreachable(); - - // 32-bit MAX_SIZE is 1 << 30 and itself aligned, hence the following can't overflow MAX_SIZE - size = max((size + AL_MASK) & ~AL_MASK, Block.MIN_SIZE); - - var block = root.search(size); - if (!block) { - - // request more memory - let pagesBefore = memory.size(); - let pagesNeeded = (((size + 0xffff) & ~0xffff) >>> 16); - let pagesWanted = max(pagesBefore, pagesNeeded); // double memory - if (memory.grow(pagesWanted) < 0) { - if (memory.grow(pagesNeeded) < 0) { - unreachable(); // out of memory - } - } - let pagesAfter = memory.size(); - root.addMemory(pagesBefore << 16, pagesAfter << 16); - block = assert(root.search(size)); // must be found now - } - - assert((block.info & ~TAGS) >= size); - return root.use(block, size); + root.addMemory((rootOffset + Root.SIZE + AL_MASK) & ~AL_MASK, memory.size() << 16); } - /** Frees the chunk of memory at the specified address. */ - export function free(data: usize): void { - if (data) { - let root = ROOT; - if (root) { - let block = changetype(data - Block.INFO); - let blockInfo = block.info; - assert(!(blockInfo & FREE)); // must be used - block.info = blockInfo | FREE; - root.insert(changetype(data - Block.INFO)); + // search for a suitable block + if (size > Block.MAX_SIZE) unreachable(); + + // 32-bit MAX_SIZE is 1 << 30 and itself aligned, hence the following can't overflow MAX_SIZE + size = max((size + AL_MASK) & ~AL_MASK, Block.MIN_SIZE); + + var block = root.search(size); + if (!block) { + + // request more memory + let pagesBefore = memory.size(); + let pagesNeeded = (((size + 0xffff) & ~0xffff) >>> 16); + let pagesWanted = max(pagesBefore, pagesNeeded); // double memory + if (memory.grow(pagesWanted) < 0) { + if (memory.grow(pagesNeeded) < 0) { + unreachable(); // out of memory } } + let pagesAfter = memory.size(); + root.addMemory(pagesBefore << 16, pagesAfter << 16); + block = assert(root.search(size)); // must be found now + } + + assert((block.info & ~TAGS) >= size); + return root.use(block, size); +} + +/** Frees the chunk of memory at the specified address. */ +@unsafe @global function __memory_free(data: usize): void { + if (data) { + let root = ROOT; + if (root) { + let block = changetype(data - Block.INFO); + let blockInfo = block.info; + assert(!(blockInfo & FREE)); // must be used + block.info = blockInfo | FREE; + root.insert(changetype(data - Block.INFO)); + } } } diff --git a/std/assembly/array.ts b/std/assembly/array.ts index 76029910..b56cad5e 100644 --- a/std/assembly/array.ts +++ b/std/assembly/array.ts @@ -1,4 +1,4 @@ -import { ALLOC, REALLOC, REGISTER, LINK, FREE, ArrayBufferView } from "./runtime"; +import { runtime, ArrayBufferView } from "./runtime"; import { ArrayBuffer } from "./arraybuffer"; import { COMPARATOR, SORT } from "./util/sort"; import { itoa, dtoa, itoa_stream, dtoa_stream, MAX_DOUBLE_LENGTH } from "./util/number"; @@ -36,7 +36,7 @@ export class Array extends ArrayBufferView { const MAX_LENGTH = ArrayBufferView.MAX_BYTELENGTH >>> alignof(); if (length > MAX_LENGTH) throw new RangeError("Invalid array length"); let newCapacity = length << alignof(); - let newData = REALLOC(changetype(oldData), newCapacity); // registers on move + let newData = runtime.realloc(changetype(oldData), newCapacity); // registers on move if (newData !== changetype(oldData)) { this.data = changetype(newData); // links this.dataStart = newData; @@ -76,7 +76,7 @@ export class Array extends ArrayBufferView { private __set(index: i32, value: T): void { this.resize(index + 1); store(this.dataStart + (index << alignof()), value); - if (isManaged()) LINK(changetype(value), changetype(this)); + if (isManaged()) runtime.link(changetype(value), changetype(this)); if (index >= this.length_) this.length_ = index + 1; } @@ -141,7 +141,7 @@ export class Array extends ArrayBufferView { this.resize(newLength); this.length_ = newLength; store(this.dataStart + ((newLength - 1) << alignof()), element); - if (isManaged()) LINK(changetype(element), changetype(this)); + if (isManaged()) runtime.link(changetype(element), changetype(this)); return newLength; } @@ -156,14 +156,14 @@ export class Array extends ArrayBufferView { for (let offset: usize = 0; offset < thisSize; offset += sizeof()) { let element = load(thisStart + offset); store(outStart + offset, element); - LINK(changetype(element), changetype(out)); + runtime.link(changetype(element), changetype(out)); } let otherStart = other.dataStart; let otherSize = otherLen << alignof(); for (let offset: usize = 0; offset < otherSize; offset += sizeof()) { let element = load(otherStart + offset); store(outStart + thisSize + offset, element); - LINK(changetype(element), changetype(out)); + runtime.link(changetype(element), changetype(out)); } } else { memory.copy(outStart, this.dataStart, thisSize); @@ -221,7 +221,7 @@ export class Array extends ArrayBufferView { let value = load(this.dataStart + (index << alignof())); let result = callbackfn(value, index, this); store(outStart + (index << alignof()), result); - if (isManaged()) LINK(changetype(result), changetype(out)); + if (isManaged()) runtime.link(changetype(result), changetype(out)); } return out; } @@ -290,7 +290,7 @@ export class Array extends ArrayBufferView { (newLength - 1) << alignof() ); store(base, element); - if (isManaged()) LINK(changetype(element), changetype(this)); + if (isManaged()) runtime.link(changetype(element), changetype(this)); this.length_ = newLength; return newLength; } @@ -307,7 +307,7 @@ export class Array extends ArrayBufferView { let offset = i << alignof(); let element = load(thisBase + offset); store(sliceBase + offset, element); - if (isManaged()) LINK(changetype(element), changetype(slice)); + if (isManaged()) runtime.link(changetype(element), changetype(slice)); } return slice; } @@ -323,7 +323,7 @@ export class Array extends ArrayBufferView { for (let i = 0; i < deleteCount; ++i) { let element = load(thisBase + (i << alignof())); store(spliceStart + (i << alignof()), element); - if (isManaged()) LINK(changetype(element), changetype(splice)); + if (isManaged()) runtime.link(changetype(element), changetype(splice)); } memory.copy( splice.dataStart, @@ -395,7 +395,7 @@ export class Array extends ArrayBufferView { var sepLen = separator.length; var valueLen = 5; // max possible length of element len("false") var estLen = (valueLen + sepLen) * lastIndex + valueLen; - var result = ALLOC(estLen << 1); + var result = runtime.alloc(estLen << 1); var offset = 0; var value: bool; for (let i = 0; i < lastIndex; ++i) { @@ -427,10 +427,10 @@ export class Array extends ArrayBufferView { if (estLen > offset) { let trimmed = changetype(result).substring(0, offset); - FREE(result); + runtime.free(result); return trimmed; // registered in .substring } - return REGISTER(result); + return runtime.register(result); } private join_int(separator: string = ","): string { @@ -442,7 +442,7 @@ export class Array extends ArrayBufferView { var sepLen = separator.length; const valueLen = (sizeof() <= 4 ? 10 : 20) + isSigned(); var estLen = (valueLen + sepLen) * lastIndex + valueLen; - var result = ALLOC(estLen << 1); + var result = runtime.alloc(estLen << 1); var offset = 0; var value: T; for (let i = 0; i < lastIndex; ++i) { @@ -461,10 +461,10 @@ export class Array extends ArrayBufferView { offset += itoa_stream(result, offset, value); if (estLen > offset) { let trimmed = changetype(result).substring(0, offset); - FREE(result); + runtime.free(result); return trimmed; // registered in .substring } - return REGISTER(result); + return runtime.register(result); } private join_flt(separator: string = ","): string { @@ -476,7 +476,7 @@ export class Array extends ArrayBufferView { const valueLen = MAX_DOUBLE_LENGTH; var sepLen = separator.length; var estLen = (valueLen + sepLen) * lastIndex + valueLen; - var result = ALLOC(estLen << 1); + var result = runtime.alloc(estLen << 1); var offset = 0; var value: T; for (let i = 0; i < lastIndex; ++i) { @@ -495,10 +495,10 @@ export class Array extends ArrayBufferView { offset += dtoa_stream(result, offset, value); if (estLen > offset) { let trimmed = changetype(result).substring(0, offset); - FREE(result); + runtime.free(result); return trimmed; // registered in .substring } - return REGISTER(result); + return runtime.register(result); } private join_str(separator: string = ","): string { @@ -513,7 +513,7 @@ export class Array extends ArrayBufferView { estLen += load(dataStart + (i << alignof())).length; } var offset = 0; - var result = ALLOC((estLen + sepLen * lastIndex) << 1); + var result = runtime.alloc((estLen + sepLen * lastIndex) << 1); var value: String; for (let i = 0; i < lastIndex; ++i) { value = load(dataStart + (i << alignof())); @@ -544,7 +544,7 @@ export class Array extends ArrayBufferView { valueLen << 1 ); } - return REGISTER(result); + return runtime.register(result); } private join_arr(separator: string = ","): string { @@ -578,7 +578,7 @@ export class Array extends ArrayBufferView { const valueLen = 15; // max possible length of element len("[object Object]") var sepLen = separator.length; var estLen = (valueLen + sepLen) * lastIndex + valueLen; - var result = ALLOC(estLen << 1); + var result = runtime.alloc(estLen << 1); var offset = 0; var value: T; for (let i = 0; i < lastIndex; ++i) { @@ -610,10 +610,10 @@ export class Array extends ArrayBufferView { } if (estLen > offset) { let out = changetype(result).substring(0, offset); - FREE(result); + runtime.free(result); return out; // registered in .substring } - return REGISTER(result); + return runtime.register(result); } @inline diff --git a/std/assembly/arraybuffer.ts b/std/assembly/arraybuffer.ts index 03e02f95..0b9af201 100644 --- a/std/assembly/arraybuffer.ts +++ b/std/assembly/arraybuffer.ts @@ -1,4 +1,4 @@ -import { HEADER, ALLOC_RAW, ALLOC, REGISTER, ArrayBufferView } from "./runtime"; +import { runtime, ArrayBufferView } from "./runtime"; @sealed export class ArrayBuffer { @@ -22,11 +22,11 @@ import { HEADER, ALLOC_RAW, ALLOC, REGISTER, ArrayBufferView } from "./runtime"; constructor(length: i32) { if (length > ArrayBufferView.MAX_BYTELENGTH) throw new RangeError("Invalid array buffer length"); - return REGISTER(ALLOC(length)); + return runtime.register(runtime.alloc(length)); } get byteLength(): i32 { - return changetype
(changetype(this) - HEADER_SIZE).payloadSize; + return changetype(changetype(this) - runtime.Header.SIZE).payloadSize; } slice(begin: i32 = 0, end: i32 = ArrayBufferView.MAX_BYTELENGTH): ArrayBuffer { @@ -34,9 +34,9 @@ import { HEADER, ALLOC_RAW, ALLOC, REGISTER, ArrayBufferView } from "./runtime"; begin = begin < 0 ? max(length + begin, 0) : min(begin, length); end = end < 0 ? max(length + end , 0) : min(end , length); var outSize = max(end - begin, 0); - var out = ALLOC_RAW(outSize); + var out = runtime.allocRaw(outSize); memory.copy(out, changetype(this) + begin, outSize); - return REGISTER(out); + return runtime.register(out); } toString(): string { diff --git a/std/assembly/builtins.ts b/std/assembly/builtins.ts index 13fa6245..75190b5a 100644 --- a/std/assembly/builtins.ts +++ b/std/assembly/builtins.ts @@ -13,7 +13,6 @@ @builtin export declare function isFunction(value?: T): bool; @builtin export declare function isNullable(value?: T): bool; @builtin export declare function isDefined(expression: void): bool; -@builtin export declare function isImplemented(expression: void): bool; @builtin export declare function isConstant(expression: void): bool; @builtin export declare function isManaged(value?: T): bool; @inline export function isNaN(value: T): bool { return value != value; } diff --git a/std/assembly/collector/itcm.ts b/std/assembly/collector/itcm.ts index bd1d37f9..27fb146f 100644 --- a/std/assembly/collector/itcm.ts +++ b/std/assembly/collector/itcm.ts @@ -1,9 +1,3 @@ -/** - * Incremental Tri-Color-Marking Garbage Collector. - * - * @module std/assembly/collector/itcm - *//***/ - // Largely based on Bach Le's μgc, see: https://github.com/bullno1/ugc @inline const TRACE = false; @@ -11,8 +5,8 @@ /** Size of a managed object header. */ @inline export const HEADER_SIZE: usize = (offsetof() + AL_MASK) & ~AL_MASK; -import { AL_MASK, MAX_SIZE_32 } from "../internal/allocator"; -import { __rt_iterateroots } from "../builtins"; +import { AL_MASK, MAX_SIZE_32 } from "../util/allocator"; +import { gc } from "../gc"; /** Collector states. */ const enum State { @@ -142,7 +136,7 @@ function step(): void { } case State.IDLE: { if (TRACE) trace("gc~step/IDLE"); - __rt_iterateroots(__gc_mark); + gc.iterateRoots(__gc_mark); state = State.MARK; if (TRACE) trace("gc~state = MARK"); break; @@ -163,7 +157,7 @@ function step(): void { obj.hookFn(objToRef(obj)); } else { if (TRACE) trace("gc~step/MARK finish"); - __rt_iterateroots(__gc_mark); + gc.iterateRoots(__gc_mark); obj = iter.next; if (obj === toSpace) { let from = fromSpace; @@ -202,9 +196,7 @@ function step(): void { return changetype(obj) + HEADER_SIZE; } -// Garbage collector interface - -@global export function __gc_allocate( +@global @unsafe export function __gc_allocate( // TODO: make this register only / reuse header size: usize, markFn: (ref: usize) => void ): usize { @@ -218,13 +210,13 @@ function step(): void { return objToRef(obj); } -@global export function __gc_link(parentRef: usize, childRef: usize): void { +@global @unsafe export function __gc_link(parentRef: usize, childRef: usize): void { if (TRACE) trace("gc.link", 2, parentRef, childRef); var parent = refToObj(parentRef); if (parent.color == !white && refToObj(childRef).color == white) parent.makeGray(); } -@global export function __gc_mark(ref: usize): void { +@global @unsafe export function __gc_mark(ref: usize): void { if (TRACE) trace("gc.mark", 1, ref); if (ref) { let obj = refToObj(ref); @@ -232,7 +224,7 @@ function step(): void { } } -@global export function __gc_collect(): void { +@global @unsafe export function __gc_collect(): void { if (TRACE) trace("gc.collect"); // begin collecting if not yet collecting switch (state) { diff --git a/std/assembly/dataview.ts b/std/assembly/dataview.ts index 12c8817d..92fe52b8 100644 --- a/std/assembly/dataview.ts +++ b/std/assembly/dataview.ts @@ -1,4 +1,5 @@ import { ArrayBuffer } from "./arraybuffer"; +import { ArrayBufferView } from "./runtime"; export class DataView { @@ -12,7 +13,7 @@ export class DataView { byteLength: i32 = i32.MIN_VALUE // FIXME ) { if (byteLength === i32.MIN_VALUE) byteLength = buffer.byteLength - byteOffset; // FIXME - if (byteLength > ArrayBuffer.MAX_BYTELENGTH) throw new RangeError("Invalid byteLength"); + if (byteLength > ArrayBufferView.MAX_BYTELENGTH) throw new RangeError("Invalid byteLength"); if (byteOffset + byteLength > buffer.byteLength) throw new RangeError("Invalid length"); this.data = buffer; // links var dataStart = changetype(buffer) + byteOffset; diff --git a/std/assembly/diagnostics.ts b/std/assembly/diagnostics.ts index 9e797ad1..291f45b4 100644 --- a/std/assembly/diagnostics.ts +++ b/std/assembly/diagnostics.ts @@ -1,5 +1,3 @@ -/* tslint:disable */ - -@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 ERROR(message?: string): void; +@builtin export declare function WARNING(message?: string): void; +@builtin export declare function INFO(message?: string): void; diff --git a/std/assembly/gc.ts b/std/assembly/gc.ts index 36a06745..b226d39d 100644 --- a/std/assembly/gc.ts +++ b/std/assembly/gc.ts @@ -1,30 +1,37 @@ /** Garbage collector interface. */ export namespace gc { + /** Whether the garbage collector interface is implemented. */ + @lazy export const implemented: bool = isDefined(__gc_register); + /** Gets the computed unique class id of a class type. */ - @builtin @unsafe export declare function classId(): u32; + @unsafe @builtin export declare function classId(): u32; /** Iterates reference root objects. */ - @builtin @unsafe export declare function iterateRoots(fn: (ref: usize) => void): void; + @unsafe @builtin export declare function iterateRoots(fn: (ref: usize) => void): void; /** Registers a managed object to be tracked by the garbage collector. */ - @stub @unsafe export function register(ref: usize): void { - ERROR("stub: missing garbage collector"); + @unsafe export function register(ref: usize): void { + if (isDefined(__gc_register)) __gc_register(ref); + else ERROR("missing implementation: gc.register"); } /** Links a registered object with the registered object now referencing it. */ - @stub @unsafe export function link(ref: usize, parentRef: usize): void { - ERROR("stub: missing garbage collector"); + @unsafe export function link(ref: usize, parentRef: usize): void { + if (isDefined(__gc_link)) __gc_link(ref, parentRef); + else ERROR("missing implementation: gc.link"); } /** Marks an object as being reachable. */ - @stub @unsafe export function mark(ref: usize): void { - ERROR("stub: missing garbage collector"); + @unsafe export function mark(ref: usize): void { + if (isDefined(__gc_mark)) __gc_mark(ref); + else ERROR("missing implementation: gc.mark"); } /** Performs a full garbage collection cycle. */ - @stub export function collect(): void { - WARNING("stub: missing garbage collector"); + export function collect(): void { + if (isDefined(__gc_collect)) __gc_collect(); + else WARNING("missing implementation: gc.collect"); } } diff --git a/std/assembly/index.d.ts b/std/assembly/index.d.ts index 27d983fe..bafda538 100644 --- a/std/assembly/index.d.ts +++ b/std/assembly/index.d.ts @@ -144,8 +144,6 @@ declare function isFunction(value?: any): value is (...args: any) => any; declare function isNullable(value?: any): bool; /** 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 resolves to an implemented (non-stub) element. Compiles to a constant. */ -declare function isImplemented(expression: any): bool; /** Tests if the specified expression evaluates to a constant value. Compiles to a constant. */ declare function isConstant(expression: any): bool; /** Tests if the specified type *or* expression is of a managed type. Compiles to a constant. */ @@ -1519,6 +1517,13 @@ declare function inline( descriptor: TypedPropertyDescriptor ): TypedPropertyDescriptor | void; +/** Annotates a method, function or constant global as unsafe. */ +declare function unsafe( + target: any, + propertyKey: string, + descriptor: TypedPropertyDescriptor +): TypedPropertyDescriptor | void; + /** Annotates an explicit external name of a function or global. */ declare function external(namespace: string, name: string): ( target: any, diff --git a/std/assembly/map.ts b/std/assembly/map.ts index 7c549124..2152b929 100644 --- a/std/assembly/map.ts +++ b/std/assembly/map.ts @@ -1,4 +1,4 @@ -import { LINK } from "./runtime"; +import { runtime } from "./runtime"; import { HASH } from "./util/hash"; // A deterministic hash map based on CloseTable from https://github.com/jorendorff/dht @@ -108,8 +108,8 @@ export class Map { let bucketPtrBase = changetype(this.buckets) + (hashCode & this.bucketsMask) * BUCKET_SIZE; entry.taggedNext = load(bucketPtrBase); store(bucketPtrBase, changetype(entry)); - if (isManaged()) LINK(changetype(key), changetype(this)); - if (isManaged()) LINK(changetype(value), changetype(this)); + if (isManaged()) runtime.link(changetype(key), changetype(this)); + if (isManaged()) runtime.link(changetype(value), changetype(this)); } } diff --git a/std/assembly/memory.ts b/std/assembly/memory.ts index c37ad9b5..260bc5e3 100644 --- a/std/assembly/memory.ts +++ b/std/assembly/memory.ts @@ -1,5 +1,7 @@ import { memcmp, memmove, memset } from "./util/memory"; +@builtin export declare const HEAP_BASE: usize; + /** Memory manager interface. */ export namespace memory { @@ -7,15 +9,15 @@ export namespace memory { @builtin export declare function size(): i32; /** Grows the memory by the given size in pages and returns the previous size in pages. */ - @builtin @unsafe export declare function grow(pages: i32): i32; + @unsafe @builtin export declare function grow(pages: i32): i32; /** Fills a section in memory with the specified byte value. */ - @builtin @unsafe @inline export function fill(dst: usize, c: u8, n: usize): void { + @unsafe @builtin export function fill(dst: usize, c: u8, n: usize): void { memset(dst, c, n); // fallback if "bulk-memory" isn't enabled } /** Copies a section of memory to another. Has move semantics. */ - @builtin @unsafe @inline export function copy(dst: usize, src: usize, n: usize): void { + @unsafe @builtin export function copy(dst: usize, src: usize, n: usize): void { memmove(dst, src, n); // fallback if "bulk-memory" isn't enabled } @@ -30,24 +32,22 @@ export namespace memory { } /** Dynamically allocates a section of memory and returns its address. */ - @stub @inline export function allocate(size: usize): usize { - ERROR("stub: missing memory manager"); + @unsafe export function allocate(size: usize): usize { + if (isDefined(__memory_allocate)) return __memory_allocate(size); + else ERROR("missing implementation: memory.allocate"); return unreachable(); } /** Dynamically frees a section of memory by the previously allocated address. */ - @stub @unsafe @inline export function free(ptr: usize): void { - ERROR("stub: missing memory manager"); + @unsafe export function free(ptr: usize): void { + if (isDefined(__memory_free)) __memory_free(ptr); + else ERROR("missing implementation: memory.free"); } /** Resets the memory to its initial state. Arena allocator only. */ - @stub @unsafe @inline export function reset(): void { - ERROR("stub: not supported by memory manager"); - } - - /** Compares a section of memory to another. */ - @inline export function compare(vl: usize, vr: usize, n: usize): i32 { - return memcmp(vl, vr, n); + @unsafe export function reset(): void { + if (isDefined(__memory_reset)) __memory_reset(); + else ERROR("missing implementation: memory.reset"); } /** Repeats a section of memory at a specific address. */ @@ -59,4 +59,9 @@ export namespace memory { index += srcLength; } } + + /** Compares a section of memory to another. */ + @inline export function compare(vl: usize, vr: usize, n: usize): i32 { + return memcmp(vl, vr, n); + } } diff --git a/std/assembly/polyfills.ts b/std/assembly/polyfills.ts index ab09a477..00274f94 100644 --- a/std/assembly/polyfills.ts +++ b/std/assembly/polyfills.ts @@ -1,4 +1,4 @@ -export function bswap(value: T): T { +export function bswap(value: T): T { if (isInteger()) { if (sizeof() == 2) { return ((value << 8) | ((value >> 8) & 0x00FF)); @@ -25,7 +25,7 @@ export function bswap(value: T): T { return value; } -@inline export function bswap16(value: T): T { +@inline export function bswap16(value: T): T { if (isInteger() && sizeof() <= 4) { if (sizeof() == 2) { return ((value << 8) | ((value >> 8) & 0x00FF)); diff --git a/std/assembly/runtime.ts b/std/assembly/runtime.ts index 4c046e2f..d6b5727f 100644 --- a/std/assembly/runtime.ts +++ b/std/assembly/runtime.ts @@ -1,139 +1,143 @@ import { AL_MASK, MAX_SIZE_32 } from "./util/allocator"; +import { HEAP_BASE, memory } from "./memory"; +import { gc } from "./gc"; -@builtin export declare const HEAP_BASE: usize; +/** Common runtime. */ +export namespace runtime { -/** Common runtime header of all objects. */ -@unmanaged export class HEADER { - /** Unique id of the respective class or a magic value if not yet registered.*/ - classId: u32; - /** Size of the allocated payload. */ - payloadSize: u32; - /** Reserved field for use by GC. Only present if GC is. */ - gc1: usize; // itcm: tagged next - /** Reserved field for use by GC. Only present if GC is. */ - gc2: usize; // itcm: prev -} + /** Common runtime header of all objects. */ + @unmanaged export class Header { -// Note that header data and layout isn't quite optimal depending on which allocator one -// decides to use, but it's done this way for maximum flexibility. Also remember that the -// runtime will most likely change significantly once reftypes and WASM GC are a thing. + /** Size of a runtime header. */ + @lazy @inline static readonly SIZE: usize = gc.implemented + ? (offsetof
( ) + AL_MASK) & ~AL_MASK // full header if GC is present + : (offsetof
("gc1") + AL_MASK) & ~AL_MASK; // half header if GC is absent -/** Whether a GC is present or not. */ -@inline export const GC = isImplemented(gc.register); + /** Magic value used to validate runtime headers. */ + @lazy @inline static readonly MAGIC: u32 = 0xA55E4B17; -/** Size of the common runtime header. */ -@inline export const HEADER_SIZE: usize = GC - ? (offsetof
( ) + AL_MASK) & ~AL_MASK // full header if GC is present - : (offsetof
("gc1") + AL_MASK) & ~AL_MASK; // half header if GC is absent - -/** Magic value used to validate common runtime headers. */ -@inline export const HEADER_MAGIC: u32 = 0xA55E4B17; - -/** Adjusts an allocation to actual block size. Primarily targets TLSF. */ -export function ADJUST(payloadSize: usize): usize { - // round up to power of 2, e.g. with HEADER_SIZE=8: - // 0 -> 2^3 = 8 - // 1..8 -> 2^4 = 16 - // 9..24 -> 2^5 = 32 - // ... - // MAX_LENGTH -> 2^30 = 0x40000000 (MAX_SIZE_32) - return 1 << (32 - clz(payloadSize + HEADER_SIZE - 1)); -} - -/** Allocates a new object and returns a pointer to its payload. Does not fill. */ -@unsafe export function ALLOC_RAW(payloadSize: u32): usize { - var header = changetype
(memory.allocate(ADJUST(payloadSize))); - header.classId = HEADER_MAGIC; - header.payloadSize = payloadSize; - if (GC) { - header.gc1 = 0; - header.gc2 = 0; + /** Unique id of the respective class or a magic value if not yet registered.*/ + classId: u32; + /** Size of the allocated payload. */ + payloadSize: u32; + /** Reserved field for use by GC. Only present if GC is. */ + gc1: usize; // itcm: tagged next + /** Reserved field for use by GC. Only present if GC is. */ + gc2: usize; // itcm: prev } - return changetype(header) + HEADER_SIZE; -} -/** Allocates a new object and returns a pointer to its payload. Fills with zeroes.*/ -@unsafe export function ALLOC(payloadSize: u32): usize { - var ref = ALLOC_RAW(payloadSize); - memory.fill(ref, 0, payloadSize); - return ref; -} + // Note that header data and layout isn't quite optimal depending on which allocator one + // decides to use, but it's done this way for maximum flexibility. Also remember that the + // runtime will most likely change significantly once reftypes and WASM GC are a thing. -/** Reallocates an object if necessary. Returns a pointer to its (moved) payload. */ -@unsafe export function REALLOC(ref: usize, newPayloadSize: u32): usize { - // Background: When managed objects are allocated these aren't immediately registered with GC - // but can be used as scratch objects while unregistered. This is useful in situations where - // the object must be reallocated multiple times because its final size isn't known beforehand, - // e.g. in Array#filter, with only the final object making it into GC'ed userland. - var header = changetype
(ref - HEADER_SIZE); - var payloadSize = header.payloadSize; - if (payloadSize < newPayloadSize) { - let newAdjustedSize = ADJUST(newPayloadSize); - if (select(ADJUST(payloadSize), 0, ref > HEAP_BASE) < newAdjustedSize) { - // move if the allocation isn't large enough or not a heap object - let newHeader = changetype
(memory.allocate(newAdjustedSize)); - newHeader.classId = HEADER_MAGIC; - if (GC) { - newHeader.gc1 = 0; - newHeader.gc2 = 0; - } - let newRef = changetype(newHeader) + HEADER_SIZE; - memory.copy(newRef, ref, payloadSize); - memory.fill(newRef + payloadSize, 0, newPayloadSize - payloadSize); - if (header.classId == HEADER_MAGIC) { - // free right away if not registered yet - assert(ref > HEAP_BASE); // static objects aren't scratch objects - memory.free(changetype(header)); - } else if (GC) { - // if previously registered, register again - gc.register(ref); - } - header = newHeader; - ref = newRef; - } else { - // otherwise just clear additional memory within this block - memory.fill(ref + payloadSize, 0, newPayloadSize - payloadSize); + /** Adjusts an allocation to actual block size. Primarily targets TLSF. */ + function adjust(payloadSize: usize): usize { + // round up to power of 2, e.g. with HEADER_SIZE=8: + // 0 -> 2^3 = 8 + // 1..8 -> 2^4 = 16 + // 9..24 -> 2^5 = 32 + // ... + // MAX_LENGTH -> 2^30 = 0x40000000 (MAX_SIZE_32) + return 1 << (32 - clz(payloadSize + Header.SIZE - 1)); + } + + /** Allocates a new object and returns a pointer to its payload. Does not fill. */ + @unsafe export function allocRaw(payloadSize: u32): usize { + var header = changetype
(memory.allocate(adjust(payloadSize))); + header.classId = Header.MAGIC; + header.payloadSize = payloadSize; + if (gc.implemented) { + header.gc1 = 0; + header.gc2 = 0; } - } else { - // if the size is the same or less, just update the header accordingly. - // unused space is cleared when grown, so no need to do this here. + return changetype(header) + Header.SIZE; + } + + /** Allocates a new object and returns a pointer to its payload. Fills with zeroes.*/ + @unsafe export function alloc(payloadSize: u32): usize { + var ref = allocRaw(payloadSize); + memory.fill(ref, 0, payloadSize); + return ref; + } + + /** Reallocates an object if necessary. Returns a pointer to its (moved) payload. */ + @unsafe export function realloc(ref: usize, newPayloadSize: u32): usize { + // Background: When managed objects are allocated these aren't immediately registered with GC + // but can be used as scratch objects while unregistered. This is useful in situations where + // the object must be reallocated multiple times because its final size isn't known beforehand, + // e.g. in Array#filter, with only the final object making it into GC'ed userland. + var header = changetype
(ref - Header.SIZE); + var payloadSize = header.payloadSize; + if (payloadSize < newPayloadSize) { + let newAdjustedSize = adjust(newPayloadSize); + if (select(adjust(payloadSize), 0, ref > HEAP_BASE) < newAdjustedSize) { + // move if the allocation isn't large enough or not a heap object + let newHeader = changetype
(memory.allocate(newAdjustedSize)); + newHeader.classId = Header.MAGIC; + if (gc.implemented) { + newHeader.gc1 = 0; + newHeader.gc2 = 0; + } + let newRef = changetype(newHeader) + Header.SIZE; + memory.copy(newRef, ref, payloadSize); + memory.fill(newRef + payloadSize, 0, newPayloadSize - payloadSize); + if (header.classId == Header.MAGIC) { + // free right away if not registered yet + assert(ref > HEAP_BASE); // static objects aren't scratch objects + memory.free(changetype(header)); + } else if (gc.implemented) { + // if previously registered, register again + gc.register(ref); + } + header = newHeader; + ref = newRef; + } else { + // otherwise just clear additional memory within this block + memory.fill(ref + payloadSize, 0, newPayloadSize - payloadSize); + } + } else { + // if the size is the same or less, just update the header accordingly. + // unused space is cleared when grown, so no need to do this here. + } + header.payloadSize = newPayloadSize; + return ref; + } + + function unref(ref: usize): Header { + assert(ref >= HEAP_BASE + Header.SIZE); // must be a heap object + var header = changetype
(ref - Header.SIZE); + assert(header.classId == Header.MAGIC); // must be unregistered + return header; + } + + /** Frees an object. Must not have been registered with GC yet. */ + @unsafe @inline export function free(ref: T): void { + memory.free(changetype(unref(changetype(ref)))); + } + + /** Registers a managed object. Cannot be free'd anymore afterwards. */ + @unsafe @inline export function register(ref: usize): T { + if (!isReference()) ERROR("reference expected"); + // see comment in REALLOC why this is useful. also inline this because + // it's generic so we don't get a bunch of functions. + unref(ref).classId = gc.classId(); + if (gc.implemented) gc.register(ref); + return changetype(ref); + } + + /** Links a managed object with its managed parent. */ + @unsafe @inline export function link(ref: T, parentRef: TParent): void { + assert(changetype(ref) >= HEAP_BASE + Header.SIZE); // must be a heap object + var header = changetype
(changetype(ref) - Header.SIZE); + assert(header.classId != Header.MAGIC && header.gc1 != 0 && header.gc2 != 0); // must be registered + if (gc.implemented) gc.link(changetype(ref), changetype(parentRef)); // tslint:disable-line } - header.payloadSize = newPayloadSize; - return ref; } -function unref(ref: usize): HEADER { - assert(ref >= HEAP_BASE + HEADER_SIZE); // must be a heap object - var header = changetype
(ref - HEADER_SIZE); - assert(header.classId == HEADER_MAGIC); // must be unregistered - return header; -} - -/** Frees an object. Must not have been registered with GC yet. */ -@unsafe @inline export function FREE(ref: T): void { - memory.free(changetype(unref(changetype(ref)))); -} - -/** Registers a managed object. Cannot be free'd anymore afterwards. */ -@unsafe @inline export function REGISTER(ref: usize): T { - if (!isReference()) ERROR("reference expected"); - // see comment in REALLOC why this is useful. also inline this because - // it's generic so we don't get a bunch of functions. - unref(ref).classId = gc.classId(); - if (GC) gc.register(ref); - return changetype(ref); -} - -/** Links a managed object with its managed parent. */ -@unsafe @inline export function LINK(ref: T, parentRef: TParent): void { - assert(changetype(ref) >= HEAP_BASE + HEADER_SIZE); // must be a heap object - var header = changetype
(changetype(ref) - HEADER_SIZE); - assert(header.classId != HEADER_MAGIC && header.gc1 != 0 && header.gc2 != 0); // must be registered - if (GC) gc.link(changetype(ref), changetype(parentRef)); // tslint:disable-line -} +import { ArrayBuffer } from "./arraybuffer"; export abstract class ArrayBufferView { - @lazy static readonly MAX_BYTELENGTH: i32 = MAX_SIZE_32 - HEADER_SIZE; + @lazy static readonly MAX_BYTELENGTH: i32 = MAX_SIZE_32 - runtime.Header.SIZE; [key: number]: number; @@ -158,7 +162,7 @@ export abstract class ArrayBufferView { } get length(): i32 { - ERROR("concrete implementation must provide this"); + ERROR("missing implementation: [T extends ArrayBufferView]#length"); return unreachable(); } } diff --git a/std/assembly/set.ts b/std/assembly/set.ts index 09e5da5f..bac34da2 100644 --- a/std/assembly/set.ts +++ b/std/assembly/set.ts @@ -1,4 +1,4 @@ -import { LINK } from "./runtime"; +import { runtime } from "./runtime"; import { HASH } from "./util/hash"; // A deterministic hash set based on CloseTable from https://github.com/jorendorff/dht @@ -98,7 +98,7 @@ export class Set { let bucketPtrBase = changetype(this.buckets) + (hashCode & this.bucketsMask) * BUCKET_SIZE; entry.taggedNext = load(bucketPtrBase); store(bucketPtrBase, changetype(entry)); - if (isManaged()) LINK(changetype(key), changetype(this)); // tslint:disable-line + if (isManaged()) runtime.link(changetype(key), changetype(this)); // tslint:disable-line } } diff --git a/std/assembly/string.ts b/std/assembly/string.ts index e0c07daa..2765333b 100644 --- a/std/assembly/string.ts +++ b/std/assembly/string.ts @@ -1,25 +1,25 @@ -import { HEADER, HEADER_SIZE, ALLOC, REGISTER, ArrayBufferView } from "./runtime"; +import { runtime } from "./runtime"; import { MAX_SIZE_32 } from "./util/allocator"; import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./util/string"; @sealed export abstract class String { - @lazy static readonly MAX_LENGTH: i32 = (MAX_SIZE_32 - HEADER_SIZE) >> alignof(); + @lazy static readonly MAX_LENGTH: i32 = (MAX_SIZE_32 - runtime.Header.SIZE) >> alignof(); get length(): i32 { - return changetype
(changetype(this) - HEADER_SIZE).payloadSize >> 1; + return changetype(changetype(this) - runtime.Header.SIZE).payloadSize >> 1; } // TODO Add and handle second argument static fromCharCode(code: i32): String { - var out = ALLOC(2); + var out = runtime.allocRaw(2); store(out, code); - return REGISTER(out); + return runtime.register(out); } static fromCodePoint(code: i32): String { assert(code <= 0x10FFFF); var sur = code > 0xFFFF; - var out = ALLOC((sur + 1) << 1); + var out = runtime.allocRaw((sur + 1) << 1); if (!sur) { store(out, code); } else { @@ -28,15 +28,15 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut let lo: u32 = (code & 0x3FF) + 0xDC00; store(out, (hi << 16) | lo); } - return REGISTER(out); + return runtime.register(out); } @operator("[]") charAt(pos: i32): String { assert(this !== null); if (pos >= this.length) return changetype(""); - var out = ALLOC(2); + var out = runtime.allocRaw(2); store(out, load(changetype(this) + (pos << 1))); - return REGISTER(out); + return runtime.register(out); } charCodeAt(pos: i32): i32 { @@ -67,10 +67,10 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut var otherSize: isize = other.length << 1; var outSize: usize = thisSize + otherSize; if (outSize == 0) return changetype(""); - var out = ALLOC(outSize); + var out = runtime.allocRaw(outSize); memory.copy(out, changetype(this), thisSize); memory.copy(out + thisSize, changetype(other), otherSize); - return REGISTER(out); + return runtime.register(out); } endsWith(searchString: String, endPosition: i32 = String.MAX_LENGTH): bool { @@ -173,9 +173,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut if (intStart < 0) intStart = max(size + intStart, 0); var resultLength = min(max(end, 0), size - intStart); if (resultLength <= 0) return changetype(""); - var out = ALLOC(resultLength << 1); + var out = runtime.allocRaw(resultLength << 1); memory.copy(out, changetype(this) + intStart, resultLength); - return REGISTER(out); + return runtime.register(out); } substring(start: i32, end: i32 = i32.MAX_VALUE): String { @@ -188,9 +188,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut len = toPos - fromPos; if (!len) return changetype(""); if (!fromPos && toPos == this.length << 1) return this; - var out = ALLOC(len); + var out = runtime.allocRaw(len); memory.copy(out, changetype(this) + fromPos, len); - return REGISTER(out); + return runtime.register(out); } trim(): String { @@ -216,9 +216,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut } if (!size) return changetype(""); if (!start && size == length << 1) return this; - var out = ALLOC(size); + var out = runtime.allocRaw(size); memory.copy(out, changetype(this) + offset, size); - return REGISTER(out); + return runtime.register(out); } @inline @@ -246,9 +246,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut if (!offset) return this; size -= offset; if (!size) return changetype(""); - var out = ALLOC(size); + var out = runtime.allocRaw(size); memory.copy(out, changetype(this) + offset, size); - return REGISTER(out); + return runtime.register(out); } trimEnd(): String { @@ -265,9 +265,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut } if (!size) return changetype(""); if (size == originalSize) return this; - var out = ALLOC(size); + var out = runtime.allocRaw(size); memory.copy(out, changetype(this), size); - return REGISTER(out); + return runtime.register(out); } padStart(targetLength: i32, padString: string = " "): String { @@ -277,7 +277,7 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut var padSize = padString.length << 1; if (targetSize < thisSize || !padSize) return this; var prependSize = targetSize - thisSize; - var out = ALLOC(targetSize); + var out = runtime.allocRaw(targetSize); if (prependSize > padSize) { let repeatCount = (prependSize - 2) / padSize; let restBase = repeatCount * padSize; @@ -288,7 +288,7 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut memory.copy(out, changetype(padString), prependSize); } memory.copy(out + prependSize, changetype(this), thisSize); - return REGISTER(out); + return runtime.register(out); } padEnd(targetLength: i32, padString: string = " "): String { @@ -298,7 +298,7 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut var padSize = padString.length << 1; if (targetSize < thisSize || !padSize) return this; var appendSize = targetSize - thisSize; - var out = ALLOC(targetSize); + var out = runtime.allocRaw(targetSize); memory.copy(out, changetype(this), thisSize); if (appendSize > padSize) { let repeatCount = (appendSize - 2) / padSize; @@ -309,7 +309,7 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut } else { memory.copy(out + thisSize, changetype(padString), appendSize); } - return REGISTER(out); + return runtime.register(out); } repeat(count: i32 = 0): String { @@ -323,9 +323,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut if (count == 0 || !length) return changetype(""); if (count == 1) return this; - var out = ALLOC((length * count) << 1); + var out = runtime.allocRaw((length * count) << 1); memory.repeat(out, changetype(this), length << 1, count); - return REGISTER(out); + return runtime.register(out); } slice(beginIndex: i32, endIndex: i32 = i32.MAX_VALUE): String { @@ -334,9 +334,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut var end = endIndex < 0 ? max(endIndex + len, 0) : min(endIndex, len); len = end - begin; if (len <= 0) return changetype(""); - var out = ALLOC(len << 1); + var out = runtime.allocRaw(len << 1); memory.copy(out, changetype(this) + (begin << 1), len << 1); - return REGISTER(out); + return runtime.register(out); } split(separator: String | null = null, limit: i32 = i32.MAX_VALUE): String[] { @@ -354,7 +354,7 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut let buffer = unreachable(); // TODO // let buffer = result.buffer_; for (let i: isize = 0; i < length; ++i) { - let char = ALLOC(2); + let char = runtime.allocRaw(2); store( changetype(char), load( @@ -374,9 +374,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut while ((end = this.indexOf(separator!, start)) != -1) { let len = end - start; if (len > 0) { - let out = ALLOC(len << 1); + let out = runtime.allocRaw(len << 1); memory.copy(out, changetype(this) + (start << 1), len << 1); - result.push(REGISTER(out)); + result.push(runtime.register(out)); } else { result.push(changetype("")); } @@ -390,9 +390,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut } var len = length - start; if (len > 0) { - let out = ALLOC(len << 1); + let out = runtime.allocRaw(len << 1); memory.copy(out, changetype(this) + (start << 1), len << 1); - result.push(REGISTER(out)); + result.push(runtime.register(out)); } else { result.push(changetype("")); } @@ -464,10 +464,10 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut } } assert(ptrPos == len); - var out = ALLOC(bufPos); + var out = runtime.allocRaw(bufPos); memory.copy(changetype(out), buf, bufPos); memory.free(buf); - return REGISTER(out); + return runtime.register(out); } toUTF8(): usize { diff --git a/std/assembly/table.ts b/std/assembly/table.ts index b0f9c1cd..cc312ade 100644 --- a/std/assembly/table.ts +++ b/std/assembly/table.ts @@ -1,16 +1,14 @@ export namespace table { - // export function copy(dst: u32, src: u32, n: u32): void { - // __table_copy(dst, src, n); - // } + export function copy(dst: u32, src: u32, n: u32): void { + ERROR("not implemented: table.copy"); + } - // Passive elements + export function init(elementIndex: u32, srcOffset: u32, dstOffset: u32, n: u32): void { + ERROR("not implemented: table.init"); + } - // 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); - // } + export function drop(elementIndex: u32): void { + ERROR("not implemented: table.drop"); + } } diff --git a/std/assembly/typedarray.ts b/std/assembly/typedarray.ts index e8720c6c..2f1b89b9 100644 --- a/std/assembly/typedarray.ts +++ b/std/assembly/typedarray.ts @@ -1,4 +1,4 @@ -import { ALLOC, REGISTER, LINK, ArrayBufferView } from "./runtime"; +import { runtime, ArrayBufferView } from "./runtime"; import { COMPARATOR, SORT as SORT_IMPL } from "./util/sort"; function clampToByte(value: i32): i32 { @@ -720,11 +720,11 @@ export class Float64Array extends ArrayBufferView { else begin = min(begin, length); if (end < 0) end = max(length + end, begin); else end = max(min(end, length), begin); - var out = ALLOC(offsetof()); + var out = runtime.allocRaw(offsetof()); store(out, buffer, offsetof("buffer")); store(out, array.dataStart + (begin << alignof()) , offsetof("dataStart")); store(out, array.dataEnd + ((end - begin) << alignof()), offsetof("dataEnd")); - LINK(buffer, REGISTER(out)); // register first, then link + runtime.link(buffer, runtime.register(out)); // register first, then link return changetype(out); } diff --git a/std/assembly/util/number.ts b/std/assembly/util/number.ts index 970fe785..45dbefe1 100644 --- a/std/assembly/util/number.ts +++ b/std/assembly/util/number.ts @@ -1,4 +1,4 @@ -import { ALLOC, REGISTER, FREE } from "../runtime"; +import { runtime } from "../runtime"; import { CharCode } from "./string"; @inline export const MAX_DOUBLE_LENGTH = 28; @@ -254,10 +254,10 @@ export function utoa32(value: u32): String { if (!value) return "0"; var decimals = decimalCount32(value); - var out = ALLOC(decimals << 1); + var out = runtime.alloc(decimals << 1); utoa32_core(changetype(out), value, decimals); - return REGISTER(out); + return runtime.register(out); } export function itoa32(value: i32): String { @@ -267,12 +267,12 @@ export function itoa32(value: i32): String { if (sign) value = -value; var decimals = decimalCount32(value) + sign; - var out = ALLOC(decimals << 1); + var out = runtime.alloc(decimals << 1); utoa32_core(changetype(out), value, decimals); if (sign) store(changetype(out), CharCode.MINUS); - return REGISTER(out); + return runtime.register(out); } export function utoa64(value: u64): String { @@ -282,14 +282,14 @@ export function utoa64(value: u64): String { if (value <= u32.MAX_VALUE) { let val32 = value; let decimals = decimalCount32(val32); - out = ALLOC(decimals << 1); + out = runtime.alloc(decimals << 1); utoa32_core(out, val32, decimals); } else { let decimals = decimalCount64(value); - out = ALLOC(decimals << 1); + out = runtime.alloc(decimals << 1); utoa64_core(changetype(out), value, decimals); } - return REGISTER(out); + return runtime.register(out); } export function itoa64(value: i64): String { @@ -302,16 +302,16 @@ export function itoa64(value: i64): String { if (value <= u32.MAX_VALUE) { let val32 = value; let decimals = decimalCount32(val32) + sign; - out = ALLOC(decimals << 1); + out = runtime.alloc(decimals << 1); utoa32_core(changetype(out), val32, decimals); } else { let decimals = decimalCount64(value) + sign; - out = ALLOC(decimals << 1); + out = runtime.alloc(decimals << 1); utoa64_core(changetype(out), value, decimals); } if (sign) store(changetype(out), CharCode.MINUS); - return REGISTER(out); + return runtime.register(out); } export function itoa(value: T): String { @@ -594,10 +594,10 @@ export function dtoa(value: f64): String { if (isNaN(value)) return "NaN"; return select("-Infinity", "Infinity", value < 0); } - var temp = ALLOC(MAX_DOUBLE_LENGTH << 1); + var temp = runtime.alloc(MAX_DOUBLE_LENGTH << 1); var length = dtoa_core(temp, value); var result = changetype(temp).substring(0, length); - FREE(temp); + runtime.free(temp); return result; } diff --git a/tests/compiler/empty.ts b/tests/compiler/empty.ts index 25da6068..e69de29b 100644 --- a/tests/compiler/empty.ts +++ b/tests/compiler/empty.ts @@ -1,9 +0,0 @@ -import "allocator/arena"; - -var arr = new Uint8Array(8); -arr[0] = 10; -arr[4] = 5; -var arr2 = arr.sort(); -arr2.forEach(x => { - trace("", 1, x); -}); diff --git a/tests/compiler/std/runtime.optimized.wat b/tests/compiler/std/runtime.optimized.wat index 9607eba6..95f0f5e0 100644 --- a/tests/compiler/std/runtime.optimized.wat +++ b/tests/compiler/std/runtime.optimized.wat @@ -18,7 +18,7 @@ (data (i32.const 152) "\01\00\00\00\10\00\00\00b\00a\00r\00r\00i\00e\00r\003") (data (i32.const 176) "\01\00\00\00\1e\00\00\00~\00l\00i\00b\00/\00r\00u\00n\00t\00i\00m\00e\00.\00t\00s") (table $0 1 funcref) - (elem (i32.const 0) $std/runtime/gc.collect) + (elem (i32.const 0) $null) (global $~lib/allocator/tlsf/ROOT (mut i32) (i32.const 0)) (global $std/runtime/register_ref (mut i32) (i32.const 0)) (global $std/runtime/barrier1 (mut i32) (i32.const 0)) @@ -34,9 +34,6 @@ (global $std/runtime/ref5 (mut i32) (i32.const 0)) (export "memory" (memory $0)) (export "table" (table $0)) - (export "gc.register" (func $std/runtime/gc.register)) - (export "gc.link" (func $std/runtime/gc.link)) - (export "gc.collect" (func $std/runtime/gc.collect)) (start $start) (func $~lib/allocator/tlsf/Root#setSLMap (; 2 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32) local.get $1 @@ -45,7 +42,7 @@ if i32.const 0 i32.const 16 - i32.const 140 + i32.const 132 i32.const 4 call $~lib/env/abort unreachable @@ -65,7 +62,7 @@ if i32.const 0 i32.const 16 - i32.const 163 + i32.const 155 i32.const 4 call $~lib/env/abort unreachable @@ -76,7 +73,7 @@ if i32.const 0 i32.const 16 - i32.const 164 + i32.const 156 i32.const 4 call $~lib/env/abort unreachable @@ -102,7 +99,7 @@ if i32.const 0 i32.const 16 - i32.const 85 + i32.const 77 i32.const 4 call $~lib/env/abort unreachable @@ -120,7 +117,7 @@ if i32.const 0 i32.const 16 - i32.const 86 + i32.const 78 i32.const 11 call $~lib/env/abort unreachable @@ -133,7 +130,7 @@ if i32.const 0 i32.const 16 - i32.const 424 + i32.const 416 i32.const 2 call $~lib/env/abort unreachable @@ -150,7 +147,7 @@ if i32.const 0 i32.const 16 - i32.const 154 + i32.const 146 i32.const 4 call $~lib/env/abort unreachable @@ -161,7 +158,7 @@ if i32.const 0 i32.const 16 - i32.const 155 + i32.const 147 i32.const 4 call $~lib/env/abort unreachable @@ -184,7 +181,7 @@ if i32.const 0 i32.const 16 - i32.const 134 + i32.const 126 i32.const 4 call $~lib/env/abort unreachable @@ -210,7 +207,7 @@ if i32.const 0 i32.const 16 - i32.const 254 + i32.const 246 i32.const 4 call $~lib/env/abort unreachable @@ -233,7 +230,7 @@ if i32.const 0 i32.const 16 - i32.const 256 + i32.const 248 i32.const 4 call $~lib/env/abort unreachable @@ -334,7 +331,7 @@ if i32.const 0 i32.const 16 - i32.const 77 + i32.const 69 i32.const 4 call $~lib/env/abort unreachable @@ -348,7 +345,7 @@ if i32.const 0 i32.const 16 - i32.const 78 + i32.const 70 i32.const 11 call $~lib/env/abort unreachable @@ -364,7 +361,7 @@ if i32.const 0 i32.const 16 - i32.const 330 + i32.const 322 i32.const 4 call $~lib/env/abort unreachable @@ -376,7 +373,7 @@ if i32.const 0 i32.const 16 - i32.const 331 + i32.const 323 i32.const 4 call $~lib/env/abort unreachable @@ -389,7 +386,7 @@ if i32.const 0 i32.const 16 - i32.const 332 + i32.const 324 i32.const 4 call $~lib/env/abort unreachable @@ -411,7 +408,7 @@ if i32.const 0 i32.const 16 - i32.const 185 + i32.const 177 i32.const 4 call $~lib/env/abort unreachable @@ -425,7 +422,7 @@ if i32.const 0 i32.const 16 - i32.const 187 + i32.const 179 i32.const 4 call $~lib/env/abort unreachable @@ -449,7 +446,7 @@ if i32.const 0 i32.const 16 - i32.const 189 + i32.const 181 i32.const 4 call $~lib/env/abort unreachable @@ -461,7 +458,7 @@ if i32.const 0 i32.const 16 - i32.const 193 + i32.const 185 i32.const 23 call $~lib/env/abort unreachable @@ -503,7 +500,7 @@ if i32.const 0 i32.const 16 - i32.const 207 + i32.const 199 i32.const 24 call $~lib/env/abort unreachable @@ -517,7 +514,7 @@ if i32.const 0 i32.const 16 - i32.const 209 + i32.const 201 i32.const 6 call $~lib/env/abort unreachable @@ -566,7 +563,7 @@ if i32.const 0 i32.const 16 - i32.const 222 + i32.const 214 i32.const 4 call $~lib/env/abort unreachable @@ -645,7 +642,7 @@ if i32.const 0 i32.const 16 - i32.const 373 + i32.const 365 i32.const 4 call $~lib/env/abort unreachable @@ -656,7 +653,7 @@ if i32.const 0 i32.const 16 - i32.const 374 + i32.const 366 i32.const 4 call $~lib/env/abort unreachable @@ -667,7 +664,7 @@ if i32.const 0 i32.const 16 - i32.const 375 + i32.const 367 i32.const 4 call $~lib/env/abort unreachable @@ -684,7 +681,7 @@ if i32.const 0 i32.const 16 - i32.const 380 + i32.const 372 i32.const 6 call $~lib/env/abort unreachable @@ -712,7 +709,7 @@ if i32.const 0 i32.const 16 - i32.const 389 + i32.const 381 i32.const 6 call $~lib/env/abort unreachable @@ -765,7 +762,7 @@ if i32.const 0 i32.const 16 - i32.const 418 + i32.const 410 i32.const 2 call $~lib/env/abort unreachable @@ -790,7 +787,7 @@ if i32.const 0 i32.const 16 - i32.const 292 + i32.const 284 i32.const 4 call $~lib/env/abort unreachable @@ -870,7 +867,7 @@ if i32.const 0 i32.const 16 - i32.const 319 + i32.const 311 i32.const 16 call $~lib/env/abort unreachable @@ -898,7 +895,7 @@ if i32.const 0 i32.const 16 - i32.const 344 + i32.const 336 i32.const 4 call $~lib/env/abort unreachable @@ -918,7 +915,7 @@ if i32.const 0 i32.const 16 - i32.const 345 + i32.const 337 i32.const 4 call $~lib/env/abort unreachable @@ -929,7 +926,7 @@ if i32.const 0 i32.const 16 - i32.const 346 + i32.const 338 i32.const 4 call $~lib/env/abort unreachable @@ -981,7 +978,7 @@ if i32.const 0 i32.const 16 - i32.const 364 + i32.const 356 i32.const 25 call $~lib/env/abort unreachable @@ -997,7 +994,7 @@ i32.const 8 i32.add ) - (func $~lib/allocator/tlsf/memory.allocate (; 16 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/allocator/tlsf/__memory_allocate (; 16 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) (local $1 i32) (local $2 i32) (local $3 i32) @@ -1146,8 +1143,8 @@ else i32.const 0 i32.const 16 - i32.const 476 - i32.const 14 + i32.const 465 + i32.const 12 call $~lib/env/abort unreachable end @@ -1163,8 +1160,8 @@ if i32.const 0 i32.const 16 - i32.const 479 - i32.const 4 + i32.const 468 + i32.const 2 call $~lib/env/abort unreachable end @@ -1183,7 +1180,7 @@ i32.clz i32.sub i32.shl - call $~lib/allocator/tlsf/memory.allocate + call $~lib/allocator/tlsf/__memory_allocate local.tee $1 i32.const -1520547049 i32.store @@ -1419,16 +1416,21 @@ end end ) - (func $~lib/runtime/ALLOC (; 19 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/memory/memory.fill (; 19 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32) + local.get $0 + local.get $1 + call $~lib/util/memory/memset + ) + (func $~lib/runtime/ALLOC (; 20 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) (local $1 i32) local.get $0 call $~lib/runtime/ALLOC_RAW local.tee $1 local.get $0 - call $~lib/util/memory/memset + call $~lib/memory/memory.fill local.get $1 ) - (func $~lib/util/memory/memcpy (; 20 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32) + (func $~lib/util/memory/memcpy (; 21 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32) (local $3 i32) (local $4 i32) (local $5 i32) @@ -2325,7 +2327,7 @@ i32.store8 end ) - (func $~lib/util/memory/memmove (; 21 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32) + (func $~lib/util/memory/memmove (; 22 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32) (local $3 i32) (local $4 i32) local.get $0 @@ -2523,7 +2525,7 @@ end end ) - (func $~lib/allocator/tlsf/memory.free (; 22 ;) (type $FUNCSIG$vi) (param $0 i32) + (func $~lib/allocator/tlsf/__memory_free (; 23 ;) (type $FUNCSIG$vi) (param $0 i32) (local $1 i32) (local $2 i32) (local $3 i32) @@ -2543,8 +2545,8 @@ if i32.const 0 i32.const 16 - i32.const 490 - i32.const 8 + i32.const 479 + i32.const 6 call $~lib/env/abort unreachable end @@ -2561,10 +2563,6 @@ end end ) - (func $std/runtime/gc.register (; 23 ;) (type $FUNCSIG$vi) (param $0 i32) - local.get $0 - global.set $std/runtime/register_ref - ) (func $~lib/runtime/REALLOC (; 24 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) (local $2 i32) (local $3 i32) @@ -2604,7 +2602,7 @@ i32.lt_u if local.get $4 - call $~lib/allocator/tlsf/memory.allocate + call $~lib/allocator/tlsf/__memory_allocate local.tee $5 i32.const -1520547049 i32.store @@ -2627,7 +2625,7 @@ local.get $1 local.get $2 i32.sub - call $~lib/util/memory/memset + call $~lib/memory/memory.fill local.get $3 i32.load i32.const -1520547049 @@ -2639,13 +2637,13 @@ if i32.const 0 i32.const 184 - i32.const 85 + i32.const 83 i32.const 8 call $~lib/env/abort unreachable end local.get $3 - call $~lib/allocator/tlsf/memory.free + call $~lib/allocator/tlsf/__memory_free else local.get $0 global.set $std/runtime/register_ref @@ -2661,7 +2659,7 @@ local.get $1 local.get $2 i32.sub - call $~lib/util/memory/memset + call $~lib/memory/memory.fill end end local.get $3 @@ -2676,7 +2674,7 @@ if i32.const 0 i32.const 184 - i32.const 106 + i32.const 104 i32.const 2 call $~lib/env/abort unreachable @@ -2691,7 +2689,7 @@ if i32.const 0 i32.const 184 - i32.const 108 + i32.const 106 i32.const 2 call $~lib/env/abort unreachable @@ -2715,20 +2713,20 @@ i32.clz i32.sub i32.shl - local.tee $1 + local.tee $2 i32.const 0 i32.ne - local.tee $2 - if (result i32) - local.get $1 + local.tee $1 + if + local.get $2 i32.const 1 i32.sub - local.get $1 + local.get $2 i32.and i32.eqz - else - local.get $2 + local.set $1 end + local.get $1 if local.get $0 i32.const 1 @@ -2738,7 +2736,7 @@ else i32.const 0 i32.const 72 - i32.const 38 + i32.const 36 i32.const 2 call $~lib/env/abort unreachable @@ -2756,6 +2754,7 @@ i32.const 1 i32.const 32 global.get $std/runtime/barrier2 + local.tee $1 i32.const 16 i32.add i32.clz @@ -2763,7 +2762,7 @@ i32.shl i32.const 1 i32.const 32 - global.get $std/runtime/barrier2 + local.get $1 i32.const 15 i32.add i32.clz @@ -2786,6 +2785,7 @@ i32.const 1 i32.const 32 global.get $std/runtime/barrier3 + local.tee $1 i32.const 16 i32.add i32.clz @@ -2793,7 +2793,7 @@ i32.shl i32.const 1 i32.const 32 - global.get $std/runtime/barrier3 + local.get $1 i32.const 15 i32.add i32.clz @@ -2849,7 +2849,7 @@ if i32.const 0 i32.const 72 - i32.const 53 + i32.const 51 i32.const 0 call $~lib/env/abort unreachable @@ -2861,7 +2861,7 @@ if i32.const 0 i32.const 72 - i32.const 54 + i32.const 52 i32.const 0 call $~lib/env/abort unreachable @@ -2875,7 +2875,7 @@ if i32.const 0 i32.const 72 - i32.const 55 + i32.const 53 i32.const 0 call $~lib/env/abort unreachable @@ -2887,7 +2887,7 @@ if i32.const 0 i32.const 72 - i32.const 56 + i32.const 54 i32.const 0 call $~lib/env/abort unreachable @@ -2902,7 +2902,7 @@ if i32.const 0 i32.const 72 - i32.const 58 + i32.const 56 i32.const 0 call $~lib/env/abort unreachable @@ -2918,14 +2918,14 @@ if i32.const 0 i32.const 72 - i32.const 60 + i32.const 58 i32.const 0 call $~lib/env/abort unreachable end global.get $std/runtime/ref2 call $~lib/runtime/unref - call $~lib/allocator/tlsf/memory.free + call $~lib/allocator/tlsf/__memory_free global.get $std/runtime/barrier2 call $~lib/runtime/ALLOC global.set $std/runtime/ref3 @@ -2935,7 +2935,7 @@ if i32.const 0 i32.const 72 - i32.const 63 + i32.const 61 i32.const 0 call $~lib/env/abort unreachable @@ -2956,7 +2956,7 @@ if i32.const 0 i32.const 72 - i32.const 67 + i32.const 65 i32.const 0 call $~lib/env/abort unreachable @@ -2972,7 +2972,7 @@ if i32.const 0 i32.const 72 - i32.const 69 + i32.const 67 i32.const 0 call $~lib/env/abort unreachable @@ -2984,7 +2984,7 @@ if i32.const 0 i32.const 72 - i32.const 70 + i32.const 68 i32.const 0 call $~lib/env/abort unreachable @@ -3001,7 +3001,7 @@ if i32.const 0 i32.const 72 - i32.const 73 + i32.const 71 i32.const 0 call $~lib/env/abort unreachable @@ -3017,19 +3017,16 @@ if i32.const 0 i32.const 72 - i32.const 74 + i32.const 72 i32.const 0 call $~lib/env/abort unreachable end ) - (func $std/runtime/gc.link (; 27 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32) - nop - ) - (func $std/runtime/gc.collect (; 28 ;) (type $FUNCSIG$v) - nop - ) - (func $start (; 29 ;) (type $FUNCSIG$v) + (func $start (; 27 ;) (type $FUNCSIG$v) call $start:std/runtime ) + (func $null (; 28 ;) (type $FUNCSIG$v) + nop + ) ) diff --git a/tests/compiler/std/runtime.ts b/tests/compiler/std/runtime.ts index 6611f2c1..14a78c8a 100644 --- a/tests/compiler/std/runtime.ts +++ b/tests/compiler/std/runtime.ts @@ -2,28 +2,20 @@ import "allocator/tlsf"; var register_ref: usize = 0; -@global namespace gc { - export function register(ref: usize): void { - register_ref = ref; - } - export function link(ref: usize, parentRef: usize): void { - } - export function collect(): void { - } +@global function __gc_register(ref: usize): void { + register_ref = ref; } -import { - HEADER, - HEADER_SIZE, - HEADER_MAGIC, - ADJUST, - ALLOC, - REALLOC, - FREE, - REGISTER, - ArrayBufferBase, - StringBase -} from "runtime"; +var link_ref: usize = 0; +var link_parentRef: usize = 0; + +@global function __gc_link(ref: usize, parentRef: usize): void { + link_ref = ref; + link_parentRef = parentRef; +} + +@global function __gc_collect(): void { +} class A {} class B {} @@ -33,42 +25,42 @@ function isPowerOf2(x: i32): bool { return x != 0 && (x & (x - 1)) == 0; } -assert(ADJUST(0) > 0); +assert(runtime.adjust(0) > 0); for (let i = 0; i < 9000; ++i) { - assert(isPowerOf2(ADJUST(i))); + assert(isPowerOf2(runtime.adjust(i))); } -var barrier1 = ADJUST(0); +var barrier1 = runtime.adjust(0); var barrier2 = barrier1 + 1; -while (ADJUST(barrier2 + 1) == ADJUST(barrier2)) ++barrier2; +while (runtime.adjust(barrier2 + 1) == runtime.adjust(barrier2)) ++barrier2; var barrier3 = barrier2 + 1; -while (ADJUST(barrier3 + 1) == ADJUST(barrier3)) ++barrier3; +while (runtime.adjust(barrier3 + 1) == runtime.adjust(barrier3)) ++barrier3; trace("barrier1", 1, barrier1); trace("barrier2", 1, barrier2); trace("barrier3", 1, barrier3); -var ref1 = ALLOC(1); -var header1 = changetype
(ref1 - HEADER_SIZE); -assert(header1.classId == HEADER_MAGIC); +var ref1 = runtime.alloc(1); +var header1 = changetype(ref1 - runtime.Header.SIZE); +assert(header1.classId == runtime.Header.MAGIC); assert(header1.payloadSize == 1); -assert(ref1 == REALLOC(ref1, barrier1)); // same segment +assert(ref1 == runtime.realloc(ref1, barrier1)); // same segment assert(header1.payloadSize == barrier1); -var ref2 = REALLOC(ref1, barrier2); +var ref2 = runtime.realloc(ref1, barrier2); assert(ref1 != ref2); // moves -var header2 = changetype
(ref2 - HEADER_SIZE); +var header2 = changetype(ref2 - runtime.Header.SIZE); assert(header2.payloadSize == barrier2); -FREE(ref2); -var ref3 = ALLOC(barrier2); +runtime.free(ref2); +var ref3 = runtime.alloc(barrier2); assert(ref1 == ref3); // reuses space of ref1 (free'd in realloc), ref2 (explicitly free'd) -var ref4 = ALLOC(barrier1); -REGISTER(ref4); // should call __REGISTER_IMPL +var ref4 = runtime.alloc(barrier1); +runtime.register(ref4); // should call __REGISTER_IMPL assert(register_ref == ref4); -var header4 = changetype
(register_ref - HEADER_SIZE); +var header4 = changetype(register_ref - runtime.Header.SIZE); assert(header4.classId == gc.classId()); assert(header4.payloadSize == barrier1); -var ref5 = ALLOC(10); -assert(changetype(ref5).byteLength == 10); -assert(changetype(ref5).length == 5); +var ref5 = runtime.alloc(10); +assert(changetype(ref5).byteLength == 10); +assert(changetype(ref5).length == 5); diff --git a/tests/compiler/std/runtime.untouched.wat b/tests/compiler/std/runtime.untouched.wat index 4b6aa611..b8192381 100644 --- a/tests/compiler/std/runtime.untouched.wat +++ b/tests/compiler/std/runtime.untouched.wat @@ -37,6 +37,7 @@ (global $~lib/allocator/tlsf/Root.SIZE i32 (i32.const 2916)) (global $~lib/allocator/tlsf/ROOT (mut i32) (i32.const 0)) (global $std/runtime/register_ref (mut i32) (i32.const 0)) + (global $~lib/gc/gc.implemented i32 (i32.const 1)) (global $std/runtime/barrier1 (mut i32) (i32.const 0)) (global $std/runtime/barrier2 (mut i32) (i32.const 0)) (global $std/runtime/barrier3 (mut i32) (i32.const 0)) @@ -48,12 +49,9 @@ (global $std/runtime/ref4 (mut i32) (i32.const 0)) (global $std/runtime/header4 (mut i32) (i32.const 0)) (global $std/runtime/ref5 (mut i32) (i32.const 0)) - (global $~lib/runtime/HEAP_BASE i32 (i32.const 216)) + (global $~lib/memory/HEAP_BASE i32 (i32.const 216)) (export "memory" (memory $0)) (export "table" (table $0)) - (export "gc.register" (func $std/runtime/gc.register)) - (export "gc.link" (func $std/runtime/gc.link)) - (export "gc.collect" (func $std/runtime/gc.collect)) (start $start) (func $start:~lib/allocator/tlsf (; 2 ;) (type $FUNCSIG$v) i32.const 1 @@ -65,7 +63,7 @@ if i32.const 0 i32.const 16 - i32.const 118 + i32.const 110 i32.const 0 call $~lib/env/abort unreachable @@ -114,7 +112,7 @@ if i32.const 0 i32.const 16 - i32.const 140 + i32.const 132 i32.const 4 call $~lib/env/abort unreachable @@ -135,7 +133,7 @@ if i32.const 0 i32.const 16 - i32.const 163 + i32.const 155 i32.const 4 call $~lib/env/abort unreachable @@ -147,7 +145,7 @@ if i32.const 0 i32.const 16 - i32.const 164 + i32.const 156 i32.const 4 call $~lib/env/abort unreachable @@ -180,7 +178,7 @@ if i32.const 0 i32.const 16 - i32.const 85 + i32.const 77 i32.const 4 call $~lib/env/abort unreachable @@ -200,7 +198,7 @@ if (result i32) i32.const 0 i32.const 16 - i32.const 86 + i32.const 78 i32.const 11 call $~lib/env/abort unreachable @@ -216,7 +214,7 @@ if i32.const 0 i32.const 16 - i32.const 424 + i32.const 416 i32.const 2 call $~lib/env/abort unreachable @@ -234,7 +232,7 @@ if i32.const 0 i32.const 16 - i32.const 154 + i32.const 146 i32.const 4 call $~lib/env/abort unreachable @@ -246,7 +244,7 @@ if i32.const 0 i32.const 16 - i32.const 155 + i32.const 147 i32.const 4 call $~lib/env/abort unreachable @@ -270,7 +268,7 @@ if i32.const 0 i32.const 16 - i32.const 134 + i32.const 126 i32.const 4 call $~lib/env/abort unreachable @@ -300,7 +298,7 @@ if i32.const 0 i32.const 16 - i32.const 254 + i32.const 246 i32.const 4 call $~lib/env/abort unreachable @@ -326,7 +324,7 @@ if i32.const 0 i32.const 16 - i32.const 256 + i32.const 248 i32.const 4 call $~lib/env/abort unreachable @@ -437,7 +435,7 @@ if i32.const 0 i32.const 16 - i32.const 77 + i32.const 69 i32.const 4 call $~lib/env/abort unreachable @@ -451,7 +449,7 @@ if (result i32) i32.const 0 i32.const 16 - i32.const 78 + i32.const 70 i32.const 11 call $~lib/env/abort unreachable @@ -468,7 +466,7 @@ if i32.const 0 i32.const 16 - i32.const 330 + i32.const 322 i32.const 4 call $~lib/env/abort unreachable @@ -481,7 +479,7 @@ if i32.const 0 i32.const 16 - i32.const 331 + i32.const 323 i32.const 4 call $~lib/env/abort unreachable @@ -494,7 +492,7 @@ if i32.const 0 i32.const 16 - i32.const 332 + i32.const 324 i32.const 4 call $~lib/env/abort unreachable @@ -520,7 +518,7 @@ if i32.const 0 i32.const 16 - i32.const 185 + i32.const 177 i32.const 4 call $~lib/env/abort unreachable @@ -535,7 +533,7 @@ if i32.const 0 i32.const 16 - i32.const 187 + i32.const 179 i32.const 4 call $~lib/env/abort unreachable @@ -561,7 +559,7 @@ if i32.const 0 i32.const 16 - i32.const 189 + i32.const 181 i32.const 4 call $~lib/env/abort unreachable @@ -573,7 +571,7 @@ if (result i32) i32.const 0 i32.const 16 - i32.const 193 + i32.const 185 i32.const 23 call $~lib/env/abort unreachable @@ -621,7 +619,7 @@ if (result i32) i32.const 0 i32.const 16 - i32.const 207 + i32.const 199 i32.const 24 call $~lib/env/abort unreachable @@ -639,7 +637,7 @@ if i32.const 0 i32.const 16 - i32.const 209 + i32.const 201 i32.const 6 call $~lib/env/abort unreachable @@ -694,7 +692,7 @@ if i32.const 0 i32.const 16 - i32.const 222 + i32.const 214 i32.const 4 call $~lib/env/abort unreachable @@ -785,7 +783,7 @@ if i32.const 0 i32.const 16 - i32.const 373 + i32.const 365 i32.const 4 call $~lib/env/abort unreachable @@ -798,7 +796,7 @@ if i32.const 0 i32.const 16 - i32.const 374 + i32.const 366 i32.const 4 call $~lib/env/abort unreachable @@ -811,7 +809,7 @@ if i32.const 0 i32.const 16 - i32.const 375 + i32.const 367 i32.const 4 call $~lib/env/abort unreachable @@ -832,7 +830,7 @@ if i32.const 0 i32.const 16 - i32.const 380 + i32.const 372 i32.const 6 call $~lib/env/abort unreachable @@ -861,7 +859,7 @@ if i32.const 0 i32.const 16 - i32.const 389 + i32.const 381 i32.const 6 call $~lib/env/abort unreachable @@ -932,7 +930,7 @@ if i32.const 0 i32.const 16 - i32.const 418 + i32.const 410 i32.const 2 call $~lib/env/abort unreachable @@ -948,7 +946,7 @@ if i32.const 0 i32.const 16 - i32.const 418 + i32.const 410 i32.const 2 call $~lib/env/abort unreachable @@ -978,7 +976,7 @@ if i32.const 0 i32.const 16 - i32.const 292 + i32.const 284 i32.const 4 call $~lib/env/abort unreachable @@ -1074,7 +1072,7 @@ else i32.const 0 i32.const 16 - i32.const 319 + i32.const 311 i32.const 16 call $~lib/env/abort unreachable @@ -1111,7 +1109,7 @@ if i32.const 0 i32.const 16 - i32.const 344 + i32.const 336 i32.const 4 call $~lib/env/abort unreachable @@ -1131,7 +1129,7 @@ if i32.const 0 i32.const 16 - i32.const 345 + i32.const 337 i32.const 4 call $~lib/env/abort unreachable @@ -1144,7 +1142,7 @@ if i32.const 0 i32.const 16 - i32.const 346 + i32.const 338 i32.const 4 call $~lib/env/abort unreachable @@ -1204,7 +1202,7 @@ if (result i32) i32.const 0 i32.const 16 - i32.const 364 + i32.const 356 i32.const 25 call $~lib/env/abort unreachable @@ -1225,7 +1223,7 @@ global.get $~lib/allocator/tlsf/Block.INFO i32.add ) - (func $~lib/allocator/tlsf/memory.allocate (; 22 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/allocator/tlsf/__memory_allocate (; 22 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) (local $1 i32) (local $2 i32) (local $3 i32) @@ -1238,7 +1236,7 @@ local.get $1 i32.eqz if - global.get $~lib/runtime/HEAP_BASE + global.get $~lib/memory/HEAP_BASE i32.const 7 i32.add i32.const 7 @@ -1430,8 +1428,8 @@ if (result i32) i32.const 0 i32.const 16 - i32.const 476 - i32.const 14 + i32.const 465 + i32.const 12 call $~lib/env/abort unreachable else @@ -1451,8 +1449,8 @@ if i32.const 0 i32.const 16 - i32.const 479 - i32.const 4 + i32.const 468 + i32.const 2 call $~lib/env/abort unreachable end @@ -1461,11 +1459,16 @@ local.get $0 call $~lib/allocator/tlsf/Root#use ) - (func $~lib/runtime/ALLOC_RAW (; 23 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/memory/memory.allocate (; 23 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + local.get $0 + call $~lib/allocator/tlsf/__memory_allocate + return + ) + (func $~lib/runtime/ALLOC_RAW (; 24 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) (local $1 i32) local.get $0 call $~lib/runtime/ADJUST - call $~lib/allocator/tlsf/memory.allocate + call $~lib/memory/memory.allocate local.set $1 local.get $1 i32.const -1520547049 @@ -1483,7 +1486,7 @@ i32.const 16 i32.add ) - (func $~lib/util/memory/memset (; 24 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32) + (func $~lib/util/memory/memset (; 25 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32) (local $3 i32) (local $4 i32) (local $5 i64) @@ -1737,29 +1740,24 @@ end end ) - (func $~lib/runtime/ALLOC (; 25 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/memory/memory.fill (; 26 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32) + local.get $0 + local.get $1 + local.get $2 + call $~lib/util/memory/memset + ) + (func $~lib/runtime/ALLOC (; 27 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) (local $1 i32) - (local $2 i32) - (local $3 i32) - (local $4 i32) local.get $0 call $~lib/runtime/ALLOC_RAW local.set $1 - block $~lib/runtime/memory.fill|inlined.0 - local.get $1 - local.set $2 - i32.const 0 - local.set $3 - local.get $0 - local.set $4 - local.get $2 - local.get $3 - local.get $4 - call $~lib/util/memory/memset - end + local.get $1 + i32.const 0 + local.get $0 + call $~lib/memory/memory.fill local.get $1 ) - (func $~lib/util/memory/memcpy (; 26 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32) + (func $~lib/util/memory/memcpy (; 28 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32) (local $3 i32) (local $4 i32) (local $5 i32) @@ -2960,7 +2958,7 @@ i32.store8 end ) - (func $~lib/util/memory/memmove (; 27 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32) + (func $~lib/util/memory/memmove (; 29 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32) (local $3 i32) local.get $0 local.get $1 @@ -3187,7 +3185,13 @@ end end ) - (func $~lib/allocator/tlsf/memory.free (; 28 ;) (type $FUNCSIG$vi) (param $0 i32) + (func $~lib/memory/memory.copy (; 30 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32) + local.get $0 + local.get $1 + local.get $2 + call $~lib/util/memory/memmove + ) + (func $~lib/allocator/tlsf/__memory_free (; 31 ;) (type $FUNCSIG$vi) (param $0 i32) (local $1 i32) (local $2 i32) (local $3 i32) @@ -3212,8 +3216,8 @@ if i32.const 0 i32.const 16 - i32.const 490 - i32.const 8 + i32.const 479 + i32.const 6 call $~lib/env/abort unreachable end @@ -3230,19 +3234,24 @@ end end ) - (func $std/runtime/gc.register (; 29 ;) (type $FUNCSIG$vi) (param $0 i32) + (func $~lib/memory/memory.free (; 32 ;) (type $FUNCSIG$vi) (param $0 i32) + local.get $0 + call $~lib/allocator/tlsf/__memory_free + ) + (func $std/runtime/__gc_register (; 33 ;) (type $FUNCSIG$vi) (param $0 i32) local.get $0 global.set $std/runtime/register_ref ) - (func $~lib/runtime/REALLOC (; 30 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) + (func $~lib/gc/gc.register (; 34 ;) (type $FUNCSIG$vi) (param $0 i32) + local.get $0 + call $std/runtime/__gc_register + ) + (func $~lib/runtime/REALLOC (; 35 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) (local $2 i32) (local $3 i32) (local $4 i32) (local $5 i32) (local $6 i32) - (local $7 i32) - (local $8 i32) - (local $9 i32) local.get $0 i32.const 16 i32.sub @@ -3261,14 +3270,14 @@ call $~lib/runtime/ADJUST i32.const 0 local.get $0 - global.get $~lib/runtime/HEAP_BASE + global.get $~lib/memory/HEAP_BASE i32.gt_u select local.get $4 i32.lt_u if local.get $4 - call $~lib/allocator/tlsf/memory.allocate + call $~lib/memory/memory.allocate local.set $5 local.get $5 i32.const -1520547049 @@ -3283,56 +3292,40 @@ i32.const 16 i32.add local.set $6 - block $~lib/runtime/memory.copy|inlined.0 - local.get $6 - local.set $7 - local.get $0 - local.set $8 - local.get $3 - local.set $9 - local.get $7 - local.get $8 - local.get $9 - call $~lib/util/memory/memmove - end - block $~lib/runtime/memory.fill|inlined.1 - local.get $6 - local.get $3 - i32.add - local.set $9 - i32.const 0 - local.set $8 - local.get $1 - local.get $3 - i32.sub - local.set $7 - local.get $9 - local.get $8 - local.get $7 - call $~lib/util/memory/memset - end + local.get $6 + local.get $0 + local.get $3 + call $~lib/memory/memory.copy + local.get $6 + local.get $3 + i32.add + i32.const 0 + local.get $1 + local.get $3 + i32.sub + call $~lib/memory/memory.fill local.get $2 i32.load i32.const -1520547049 i32.eq if local.get $0 - global.get $~lib/runtime/HEAP_BASE + global.get $~lib/memory/HEAP_BASE i32.gt_u i32.eqz if i32.const 0 i32.const 184 - i32.const 85 + i32.const 83 i32.const 8 call $~lib/env/abort unreachable end local.get $2 - call $~lib/allocator/tlsf/memory.free + call $~lib/memory/memory.free else local.get $0 - call $std/runtime/gc.register + call $~lib/gc/gc.register end local.get $5 local.set $2 @@ -3342,17 +3335,11 @@ local.get $0 local.get $3 i32.add - local.set $6 i32.const 0 - local.set $5 local.get $1 local.get $3 i32.sub - local.set $7 - local.get $6 - local.get $5 - local.get $7 - call $~lib/util/memory/memset + call $~lib/memory/memory.fill end else nop @@ -3362,10 +3349,10 @@ i32.store offset=4 local.get $0 ) - (func $~lib/runtime/unref (; 31 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/runtime/unref (; 36 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) (local $1 i32) local.get $0 - global.get $~lib/runtime/HEAP_BASE + global.get $~lib/memory/HEAP_BASE i32.const 16 i32.add i32.ge_u @@ -3373,7 +3360,7 @@ if i32.const 0 i32.const 184 - i32.const 106 + i32.const 104 i32.const 2 call $~lib/env/abort unreachable @@ -3390,25 +3377,25 @@ if i32.const 0 i32.const 184 - i32.const 108 + i32.const 106 i32.const 2 call $~lib/env/abort unreachable end local.get $1 ) - (func $~lib/runtime/FREE (; 32 ;) (type $FUNCSIG$vi) (param $0 i32) + (func $~lib/runtime/FREE (; 37 ;) (type $FUNCSIG$vi) (param $0 i32) local.get $0 call $~lib/runtime/unref - call $~lib/allocator/tlsf/memory.free + call $~lib/memory/memory.free ) - (func $~lib/runtime/ArrayBufferBase#get:byteLength (; 33 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/arraybuffer/ArrayBuffer#get:byteLength (; 38 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) local.get $0 i32.const 16 i32.sub i32.load offset=4 ) - (func $~lib/runtime/StringBase#get:length (; 34 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/string/String#get:length (; 39 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) local.get $0 i32.const 16 i32.sub @@ -3416,7 +3403,7 @@ i32.const 1 i32.shr_u ) - (func $start:std/runtime (; 35 ;) (type $FUNCSIG$v) + (func $start:std/runtime (; 40 ;) (type $FUNCSIG$v) (local $0 i32) call $start:~lib/allocator/tlsf i32.const 2 @@ -3426,7 +3413,7 @@ if i32.const 0 i32.const 72 - i32.const 30 + i32.const 28 i32.const 0 call $~lib/env/abort unreachable @@ -3439,7 +3426,7 @@ if i32.const 0 i32.const 72 - i32.const 36 + i32.const 34 i32.const 0 call $~lib/env/abort unreachable @@ -3460,7 +3447,7 @@ if i32.const 0 i32.const 72 - i32.const 38 + i32.const 36 i32.const 2 call $~lib/env/abort unreachable @@ -3563,7 +3550,7 @@ if i32.const 0 i32.const 72 - i32.const 53 + i32.const 51 i32.const 0 call $~lib/env/abort unreachable @@ -3576,7 +3563,7 @@ if i32.const 0 i32.const 72 - i32.const 54 + i32.const 52 i32.const 0 call $~lib/env/abort unreachable @@ -3590,7 +3577,7 @@ if i32.const 0 i32.const 72 - i32.const 55 + i32.const 53 i32.const 0 call $~lib/env/abort unreachable @@ -3603,7 +3590,7 @@ if i32.const 0 i32.const 72 - i32.const 56 + i32.const 54 i32.const 0 call $~lib/env/abort unreachable @@ -3619,7 +3606,7 @@ if i32.const 0 i32.const 72 - i32.const 58 + i32.const 56 i32.const 0 call $~lib/env/abort unreachable @@ -3636,7 +3623,7 @@ if i32.const 0 i32.const 72 - i32.const 60 + i32.const 58 i32.const 0 call $~lib/env/abort unreachable @@ -3653,7 +3640,7 @@ if i32.const 0 i32.const 72 - i32.const 63 + i32.const 61 i32.const 0 call $~lib/env/abort unreachable @@ -3669,7 +3656,7 @@ i32.const 2 i32.store local.get $0 - call $std/runtime/gc.register + call $~lib/gc/gc.register local.get $0 end drop @@ -3680,7 +3667,7 @@ if i32.const 0 i32.const 72 - i32.const 67 + i32.const 65 i32.const 0 call $~lib/env/abort unreachable @@ -3697,7 +3684,7 @@ if i32.const 0 i32.const 72 - i32.const 69 + i32.const 67 i32.const 0 call $~lib/env/abort unreachable @@ -3710,7 +3697,7 @@ if i32.const 0 i32.const 72 - i32.const 70 + i32.const 68 i32.const 0 call $~lib/env/abort unreachable @@ -3719,41 +3706,35 @@ call $~lib/runtime/ALLOC global.set $std/runtime/ref5 global.get $std/runtime/ref5 - call $~lib/runtime/ArrayBufferBase#get:byteLength + call $~lib/arraybuffer/ArrayBuffer#get:byteLength i32.const 10 i32.eq i32.eqz if i32.const 0 i32.const 72 - i32.const 73 + i32.const 71 i32.const 0 call $~lib/env/abort unreachable end global.get $std/runtime/ref5 - call $~lib/runtime/StringBase#get:length + call $~lib/string/String#get:length i32.const 5 i32.eq i32.eqz if i32.const 0 i32.const 72 - i32.const 74 + i32.const 72 i32.const 0 call $~lib/env/abort unreachable end ) - (func $std/runtime/gc.link (; 36 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32) - nop - ) - (func $std/runtime/gc.collect (; 37 ;) (type $FUNCSIG$v) - nop - ) - (func $start (; 38 ;) (type $FUNCSIG$v) + (func $start (; 41 ;) (type $FUNCSIG$v) call $start:std/runtime ) - (func $null (; 39 ;) (type $FUNCSIG$v) + (func $null (; 42 ;) (type $FUNCSIG$v) ) )