From cb77760562f59e1a4ed72f2b99f5bf09b476323a Mon Sep 17 00:00:00 2001 From: dcode Date: Sun, 10 Mar 2019 21:38:15 +0100 Subject: [PATCH] unsafe, stub --- src/ast.ts | 6 +- src/builtins.ts | 41 +- src/compiler.ts | 42 +- src/program.ts | 85 ++-- std/assembly/allocator/arena.ts | 38 +- std/assembly/allocator/buddy.ts | 366 +++++++++-------- std/assembly/allocator/emscripten.ts | 14 +- std/assembly/allocator/system.ts | 12 +- std/assembly/allocator/tlsf.ts | 119 +++--- std/assembly/builtins.ts | 4 +- std/assembly/gc.ts | 10 - std/assembly/index.d.ts | 4 + std/assembly/memory.ts | 55 --- std/assembly/{runtime/index.ts => runtime.ts} | 105 ++++- std/assembly/runtime/itcm.ts | 8 - std/assembly/string.ts | 73 +--- tests/compiler/std/runtime.optimized.wat | 250 ++++++----- tests/compiler/std/runtime.ts | 24 +- tests/compiler/std/runtime.untouched.wat | 387 +++++++++--------- 19 files changed, 824 insertions(+), 819 deletions(-) delete mode 100644 std/assembly/gc.ts delete mode 100644 std/assembly/memory.ts rename std/assembly/{runtime/index.ts => runtime.ts} (50%) delete mode 100644 std/assembly/runtime/itcm.ts diff --git a/src/ast.ts b/src/ast.ts index b0fdd9db..52bbbb71 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1162,7 +1162,9 @@ export enum DecoratorKind { EXTERNAL, BUILTIN, LAZY, - START + START, + UNSAFE, + STUB } /** Returns the kind of the specified decorator. Defaults to {@link DecoratorKind.CUSTOM}. */ @@ -1199,10 +1201,12 @@ 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: { if (nameStr == "unmanaged") return DecoratorKind.UNMANAGED; + if (nameStr == "unsafe") return DecoratorKind.UNSAFE; break; } } diff --git a/src/builtins.ts b/src/builtins.ts index a0fbbbd1..9944c5c8 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -96,6 +96,7 @@ 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"; @@ -466,17 +467,15 @@ export namespace BuiltinSymbols { export const ERROR = "~lib/diagnostics/ERROR"; export const WARNING = "~lib/diagnostics/WARNING"; export const INFO = "~lib/diagnostics/INFO"; - // 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 iterateRoots = "~lib/gc/iterateRoots"; - // internals - export const rt_classid = "~lib/builtins/__rt_classid"; - export const rt_iterateroots = "~lib/builtins/__rt_iterateroots"; + + // 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"; } /** Compiles a call to a built-in function. */ @@ -605,6 +604,20 @@ 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 ( @@ -3596,15 +3609,15 @@ export function compileCall( // === Internal runtime ======================================================================= - case BuiltinSymbols.rt_classid: { + case BuiltinSymbols.gc_classId: { let type = evaluateConstantType(compiler, typeArguments, operands, reportNode); compiler.currentType = Type.u32; if (!type) return module.createUnreachable(); let classReference = type.classReference; if (!classReference) return module.createUnreachable(); - return module.createI32(classReference.prototype.classId); + return module.createI32(classReference.id); } - case BuiltinSymbols.rt_iterateroots: { + case BuiltinSymbols.gc_iterateRoots: { if ( checkTypeAbsent(typeArguments, reportNode, prototype) | checkArgsRequired(operands, 1, reportNode, compiler) diff --git a/src/compiler.ts b/src/compiler.ts index 59b12553..5b9dfb21 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -6364,51 +6364,29 @@ export class Compiler extends DiagnosticEmitter { /** Ensures that the specified string exists in static memory and returns a pointer to it. */ ensureStaticString(stringValue: string): ExpressionRef { var program = this.program; - var hasGC = program.hasGC; - var gcHeaderSize = program.gcHeaderSize; - + var rtHeaderSize = program.runtimeHeaderSize; var stringInstance = assert(program.stringInstance); var stringSegment: MemorySegment; - - // if the string already exists, reuse it var segments = this.stringSegments; if (segments.has(stringValue)) { - stringSegment = segments.get(stringValue); - - // otherwise create it + stringSegment = segments.get(stringValue)!; // reuse } else { let length = stringValue.length; - let headerSize = (stringInstance.currentMemoryOffset + 1) & ~1; - let totalSize = headerSize + length * 2; - - let buf: Uint8Array; - let pos: u32; - - if (hasGC) { - buf = new Uint8Array(gcHeaderSize + totalSize); - pos = gcHeaderSize; - writeI32(ensureGCHook(this, stringInstance), buf, program.gcHookOffset); - } else { - buf = new Uint8Array(totalSize); - pos = 0; - } - writeI32(length, buf, pos + stringInstance.offsetof(LibrarySymbols.length)); - pos += headerSize; + let buffer = new Uint8Array(rtHeaderSize + (length << 1)); + program.writeRuntimeHeader(buffer, 0, stringInstance, length << 1); for (let i = 0; i < length; ++i) { - writeI16(stringValue.charCodeAt(i), buf, pos + (i << 1)); + writeI16(stringValue.charCodeAt(i), buffer, rtHeaderSize + (i << 1)); } - stringSegment = this.addMemorySegment(buf); + stringSegment = this.addMemorySegment(buffer); segments.set(stringValue, stringSegment); } - var stringOffset = stringSegment.offset; - if (hasGC) stringOffset = i64_add(stringOffset, i64_new(gcHeaderSize)); - + var ref = i64_add(stringSegment.offset, i64_new(rtHeaderSize)); this.currentType = stringInstance.type; if (this.options.isWasm64) { - return this.module.createI64(i64_low(stringOffset), i64_high(stringOffset)); + return this.module.createI64(i64_low(ref), i64_high(ref)); } else { - assert(i64_is_u32(stringOffset)); - return this.module.createI32(i64_low(stringOffset)); + assert(i64_is_u32(ref)); + return this.module.createI32(i64_low(ref)); } } diff --git a/src/program.ts b/src/program.ts index a66f5c7a..8da3db04 100644 --- a/src/program.ts +++ b/src/program.ts @@ -84,7 +84,7 @@ import { } from "./module"; import { - CharCode + CharCode, writeI32 } from "./util"; import { @@ -376,6 +376,17 @@ export class Program extends DiagnosticEmitter { this.resolver = new Resolver(this); } + /** Gets the size of a common runtime header. */ + get runtimeHeaderSize(): i32 { + return this.hasGC ? 16 : 8; + } + + /** Writes a common runtime header to the specified buffer. */ + writeRuntimeHeader(buffer: Uint8Array, offset: i32, classInstance: Class, payloadSize: u32): void { + writeI32(classInstance.id, buffer, offset); + writeI32(payloadSize, buffer, offset + 4); + } + /** Creates a native variable declaration. */ makeNativeVariableDeclaration( /** The simple name of the variable */ @@ -911,7 +922,7 @@ export class Program extends DiagnosticEmitter { } /** Ensures that the given global element exists. Attempts to merge duplicates. */ - ensureGlobal(name: string, element: DeclaredElement): void { + ensureGlobal(name: string, element: DeclaredElement): DeclaredElement { var elementsByName = this.elementsByName; if (elementsByName.has(name)) { let actual = elementsByName.get(name); @@ -927,12 +938,13 @@ export class Program extends DiagnosticEmitter { DiagnosticCode.Duplicate_identifier_0, element.identifierNode.range, name ); - return; + return element; } element = merged; } } elementsByName.set(name, element); + return element; } /** Looks up the element of the specified name in the global scope. */ @@ -1532,7 +1544,7 @@ export class Program extends DiagnosticEmitter { parent: Element ): void { var name = declaration.name.text; - var validDecorators = DecoratorFlags.NONE; + var validDecorators = DecoratorFlags.UNSAFE | DecoratorFlags.STUB; if (declaration.is(CommonFlags.AMBIENT)) { validDecorators |= DecoratorFlags.EXTERNAL; } else { @@ -1615,44 +1627,50 @@ export class Program extends DiagnosticEmitter { queuedImplements: ClassPrototype[] ): void { var name = declaration.name.text; - var element = new Namespace(name, parent, declaration); - if (!parent.add(name, element)) return; - element = assert(parent.lookupInSelf(name)); // possibly merged + var original = new Namespace( + name, + parent, + declaration, + this.checkDecorators(declaration.decorators, DecoratorFlags.GLOBAL) + ); + if (!parent.add(name, original)) return; + var element = assert(parent.lookupInSelf(name)); // possibly merged var members = declaration.members; for (let i = 0, k = members.length; i < k; ++i) { let member = members[i]; switch (member.kind) { case NodeKind.CLASSDECLARATION: { - this.initializeClass(member, element, queuedExtends, queuedImplements); + this.initializeClass(member, original, queuedExtends, queuedImplements); break; } case NodeKind.ENUMDECLARATION: { - this.initializeEnum(member, element); + this.initializeEnum(member, original); break; } case NodeKind.FUNCTIONDECLARATION: { - this.initializeFunction(member, element); + this.initializeFunction(member, original); break; } case NodeKind.INTERFACEDECLARATION: { - this.initializeInterface(member, element); + this.initializeInterface(member, original); break; } case NodeKind.NAMESPACEDECLARATION: { - this.initializeNamespace(member, element, queuedExtends, queuedImplements); + this.initializeNamespace(member, original, queuedExtends, queuedImplements); break; } case NodeKind.TYPEDECLARATION: { - this.initializeTypeDefinition(member, element); + this.initializeTypeDefinition(member, original); break; } case NodeKind.VARIABLE: { - this.initializeVariables(member, element); + this.initializeVariables(member, original); break; } default: assert(false); // namespace member expected } } + if (original != element) copyMembers(original, element); // retain original parent } /** Initializes a `type` definition. */ @@ -1766,7 +1784,11 @@ export enum DecoratorFlags { /** Is compiled lazily. */ LAZY = 1 << 9, /** Is the explicit start function. */ - START = 1 << 10 + START = 1 << 10, + /** Is considered unsafe code. */ + UNSAFE = 1 << 11, + /** Is a stub that can be overridden. */ + STUB = 1 << 12 } /** Translates a decorator kind to the respective decorator flag. */ @@ -1784,6 +1806,8 @@ export function decoratorKindToFlag(kind: DecoratorKind): DecoratorFlags { case DecoratorKind.BUILTIN: return DecoratorFlags.BUILTIN; 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; } } @@ -1859,8 +1883,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) { - // override non-own element + if (actual.parent !== this || actual.hasDecorator(DecoratorFlags.STUB)) { + // override non-own or stub element } else { let merged = tryMerge(actual, element); if (merged) { @@ -1987,15 +2011,17 @@ export class File extends Element { /* @override */ add(name: string, element: DeclaredElement, isImport: bool = false): bool { + if (element.hasDecorator(DecoratorFlags.GLOBAL)) { + element = this.program.ensureGlobal(name, element); // possibly merged globally + } if (!super.add(name, element)) return false; - element = assert(this.lookupInSelf(name)); // possibly merged + element = assert(this.lookupInSelf(name)); // possibly merged locally if (element.is(CommonFlags.EXPORT) && !isImport) { this.ensureExport( element.name, element ); } - if (element.hasDecorator(DecoratorFlags.GLOBAL)) this.program.ensureGlobal(name, element); return true; } @@ -2117,7 +2143,9 @@ export class Namespace extends DeclaredElement { /** Parent element, usually a file or another namespace. */ parent: Element, /** Declaration reference. */ - declaration: NamespaceDeclaration + declaration: NamespaceDeclaration, + /** Pre-checked flags indicating built-in decorators. */ + decoratorFlags: DecoratorFlags = DecoratorFlags.NONE ) { super( ElementKind.NAMESPACE, @@ -2127,6 +2155,7 @@ export class Namespace extends DeclaredElement { parent, declaration ); + this.decoratorFlags = decoratorFlags; } /* @override */ @@ -2795,8 +2824,6 @@ export class ClassPrototype extends DeclaredElement { overloadPrototypes: Map = new Map(); /** Already resolved instances. */ instances: Map | null = null; - /** Unique class id. */ - classId: u32 = 0; constructor( /** Simple name. */ @@ -2818,8 +2845,6 @@ export class ClassPrototype extends DeclaredElement { declaration ); this.decoratorFlags = decoratorFlags; - this.classId = u32(this.program.nextClassId++); - assert(this.classId); // must not wrap around to 0 } /** Gets the associated type parameter nodes. */ @@ -2908,6 +2933,14 @@ export class Class extends TypedElement { overloads: Map | null = null; /** Function index of the GC hook. */ gcHookIndex: u32 = -1; + /** Unique class id. */ + private _id: u32 = 0; + /** Gets the unique id of this class. */ + get id(): u32 { + var id = this._id; + if (!id) this._id = id = this.program.nextClassId++; + return id; + } /** Constructs a new class. */ constructor( @@ -3151,7 +3184,9 @@ function tryMerge(older: Element, newer: Element): DeclaredElement | null { } } if (merged) { - if (older.is(CommonFlags.EXPORT) != newer.is(CommonFlags.EXPORT)) { + let olderIsExport = older.is(CommonFlags.EXPORT) || older.hasDecorator(DecoratorFlags.GLOBAL); + let newerIsExport = newer.is(CommonFlags.EXPORT) || newer.hasDecorator(DecoratorFlags.GLOBAL); + if (olderIsExport != newerIsExport) { older.program.error( DiagnosticCode.Individual_declarations_in_merged_declaration_0_must_be_all_exported_or_all_local, merged.identifierNode.range, merged.identifierNode.text diff --git a/std/assembly/allocator/arena.ts b/std/assembly/allocator/arena.ts index 5d74b56c..7a5b0364 100644 --- a/std/assembly/allocator/arena.ts +++ b/std/assembly/allocator/arena.ts @@ -12,28 +12,30 @@ import { AL_MASK, MAX_SIZE_32 } from "../internal/allocator"; var startOffset: usize = (HEAP_BASE + AL_MASK) & ~AL_MASK; var offset: usize = startOffset; -// Memory allocator interface +// Memory allocator implementation +@global namespace memory { -@global export 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 + 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 + } } } + offset = newPtr; + return ptr; } - offset = newPtr; - return ptr; -} -@global export function __memory_free(ptr: usize): void { /* nop */ } + export function free(ptr: usize): void { /* nop */ } -@global export function __memory_reset(): void { - offset = startOffset; + export function reset(): void { + offset = startOffset; + } } diff --git a/std/assembly/allocator/buddy.ts b/std/assembly/allocator/buddy.ts index 2aea2240..cec54d04 100644 --- a/std/assembly/allocator/buddy.ts +++ b/std/assembly/allocator/buddy.ts @@ -338,203 +338,205 @@ function lower_bucket_limit(bucket: usize): u32 { return 1; } -// Memory allocator interface +// Memory allocator implementation +@global namespace memory { -@global export function __memory_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; + export function allocate(request: usize): usize { + var original_bucket: usize, bucket: 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; - } + * 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(); /* - * 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)) { + * 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; } /* - * 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). - */ + * 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); - if (i != 0) { + + /* + * 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--; } /* - * 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; + * 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))); } - - return 0; -} - -@global export function __memory_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 92956b76..e34a4bd9 100644 --- a/std/assembly/allocator/emscripten.ts +++ b/std/assembly/allocator/emscripten.ts @@ -11,12 +11,14 @@ declare function _malloc(size: usize): usize; declare function _free(ptr: usize): void; -// Memory allocator interface +// Memory allocator implementation +@global namespace memory { -@global export function __memory_allocate(size: usize): usize { - return _malloc(size); -} + @inline export function allocate(size: usize): usize { + return _malloc(size); + } -@global export function __memory_free(ptr: usize): void { - _free(ptr); + @inline export function free(ptr: usize): void { + _free(ptr); + } } diff --git a/std/assembly/allocator/system.ts b/std/assembly/allocator/system.ts index 76b3293b..46dd3827 100644 --- a/std/assembly/allocator/system.ts +++ b/std/assembly/allocator/system.ts @@ -11,11 +11,13 @@ declare function malloc(size: usize): usize; declare function free(ptr: usize): void; // Memory allocator interface +@global namespace memory { -@global export function __memory_allocate(size: usize): usize { - return malloc(size); -} + @inline export function allocate(size: usize): usize { + return malloc(size); + } -@global export function __memory_free(ptr: usize): void { - free(ptr); + @inline export function free(ptr: usize): void { + free(ptr); + } } diff --git a/std/assembly/allocator/tlsf.ts b/std/assembly/allocator/tlsf.ts index 04b027dc..ad625041 100644 --- a/std/assembly/allocator/tlsf.ts +++ b/std/assembly/allocator/tlsf.ts @@ -434,70 +434,67 @@ function fls(word: T): T { var ROOT: Root = changetype(0); // Memory allocator interface +@global namespace memory { -/** Allocates a chunk of memory. */ -@global export function __memory_allocate(size: usize): usize { + /** 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); + } + } + root.addMemory((rootOffset + Root.SIZE + AL_MASK) & ~AL_MASK, memory.size() << 16); + } - // 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); + // 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. */ + 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)); } } - 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); -} - -/** Frees the chunk of memory at the specified address. */ -@global export 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)); - } } } - -@global export function __memory_reset(): void { - unreachable(); -} diff --git a/std/assembly/builtins.ts b/std/assembly/builtins.ts index bd9c55fe..df7f0acb 100644 --- a/std/assembly/builtins.ts +++ b/std/assembly/builtins.ts @@ -13,6 +13,7 @@ @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; } @@ -501,6 +502,3 @@ export namespace v8x16 { } @builtin export declare function start(): void; - -@builtin export declare function __rt_classid(): u32; -@builtin export declare function __rt_iterateroots(fn: (ref: usize) => void): void; diff --git a/std/assembly/gc.ts b/std/assembly/gc.ts deleted file mode 100644 index 895e47a8..00000000 --- a/std/assembly/gc.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* tslint:disable */ - -export namespace gc { - - export function collect(): void { - if (isDefined(__gc_collect)) { __gc_collect(); return; } - WARNING("Calling 'gc.collect' requires a garbage collector to be present."); - unreachable(); - } -} diff --git a/std/assembly/index.d.ts b/std/assembly/index.d.ts index d103983f..cdcb2b57 100644 --- a/std/assembly/index.d.ts +++ b/std/assembly/index.d.ts @@ -144,6 +144,8 @@ 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. */ @@ -980,6 +982,8 @@ declare namespace memory { export function fill(dst: usize, value: u8, count: usize): void; /** Copies n bytes from the specified source to the specified destination in memory. These regions may overlap. */ export function copy(dst: usize, src: usize, n: usize): void; + /** Repeats `src` of length `srcLength` `count` times at `dst`. */ + export function repeat(dst: usize, src: usize, srcLength: usize, count: usize): void; /** Copies elements from a passive element segment to a table. */ // export function init(segmentIndex: u32, srcOffset: usize, dstOffset: usize, n: usize): void; /** Prevents further use of a passive element segment. */ diff --git a/std/assembly/memory.ts b/std/assembly/memory.ts deleted file mode 100644 index 7c92f50e..00000000 --- a/std/assembly/memory.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { memcmp, memmove, memset } from "./internal/memory"; - -@builtin export declare const HEAP_BASE: usize; // tslint:disable-line - -/* tslint:disable */ - -export namespace memory { - - @builtin export declare function size(): i32; - - @builtin export declare function grow(pages: i32): i32; - - @builtin @inline - export function fill(dest: usize, c: u8, n: usize): void { // see: musl/src/string/memset - memset(dest, c, n); // fallback if "bulk-memory" isn't enabled - } - - @builtin @inline - export function copy(dest: usize, src: usize, n: usize): void { // see: musl/src/string/memmove.c - memmove(dest, src, n); // fallback if "bulk-memory" isn't enabled - } - - @inline export function compare(vl: usize, vr: usize, n: usize): i32 { // see: musl/src/string/memcmp.c - return memcmp(vl, vr, n); - } - - // Passive segments - - // export function init(segmentIndex: u32, srcOffset: usize, dstOffset: usize, n: usize): void { - // __memory_init(segmentIndex, srcOffset, dstOffset); - // } - - // export function drop(segmentIndex: u32): void { - // __memory_drop(segmentIndex); - // } - - // Allocator - - @inline export function allocate(size: usize): usize { - if (isDefined(__memory_allocate)) return __memory_allocate(size); - WARNING("Calling 'memory.allocate' requires a memory manager to be present."); - return unreachable(); - } - - @inline export function free(ptr: usize): void { - if (isDefined(__memory_free)) { __memory_free(ptr); return; } - WARNING("Calling 'memory.free' requires a memory manager to be present."); - unreachable(); - } - - @inline export function reset(): void { - if (isDefined(__memory_reset)) { __memory_reset(); return; } - unreachable(); - } -} diff --git a/std/assembly/runtime/index.ts b/std/assembly/runtime.ts similarity index 50% rename from std/assembly/runtime/index.ts rename to std/assembly/runtime.ts index 57ce7441..157ce440 100644 --- a/std/assembly/runtime/index.ts +++ b/std/assembly/runtime.ts @@ -1,5 +1,9 @@ -import { AL_MASK, MAX_SIZE_32 } from "../internal/allocator"; -import { __rt_classid } from "../builtins"; +import { + AL_MASK, + MAX_SIZE_32 +} from "./internal/allocator"; + +@builtin export declare const HEAP_BASE: usize; /** Common runtime header of all objects. */ @unmanaged export class HEADER { @@ -18,7 +22,7 @@ import { __rt_classid } from "../builtins"; // runtime will most likely change significantly once reftypes and WASM GC are a thing. /** Whether a GC is present or not. */ -@inline export const GC = isDefined(gc); +@inline export const GC = isImplemented(gc.register) && isImplemented(gc.link); /** Size of the common runtime header. */ @inline export const HEADER_SIZE: usize = GC @@ -28,8 +32,8 @@ import { __rt_classid } from "../builtins"; /** Magic value used to validate common runtime headers. */ @inline export const HEADER_MAGIC: u32 = 0xA55E4B17; -/** Aligns an allocation to actual block size. Primarily targets TLSF. */ -export function ALIGN(payloadSize: usize): usize { +/** 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 @@ -40,8 +44,8 @@ export function ALIGN(payloadSize: usize): usize { } /** Allocates a new object and returns a pointer to its payload. */ -export function ALLOC(payloadSize: u32): usize { - var header = changetype
(memory.allocate(ALIGN(payloadSize))); +@unsafe export function ALLOC(payloadSize: u32): usize { + var header = changetype
(memory.allocate(ADJUST(payloadSize))); header.classId = HEADER_MAGIC; header.payloadSize = payloadSize; if (GC) { @@ -54,14 +58,18 @@ export function ALLOC(payloadSize: u32): usize { } /** Reallocates an object if necessary. Returns a pointer to its (moved) payload. */ -export function REALLOC(ref: usize, newPayloadSize: u32): usize { +@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 newAlignedSize = ALIGN(newPayloadSize); - if (ALIGN(payloadSize) < newAlignedSize) { - // move if the allocation isn't large enough to hold the new payload - let newHeader = changetype
(memory.allocate(newAlignedSize)); + 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; @@ -72,6 +80,7 @@ export function REALLOC(ref: usize, newPayloadSize: u32): usize { 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)); } header = newHeader; @@ -82,8 +91,7 @@ export function REALLOC(ref: usize, newPayloadSize: u32): usize { } } else { // if the size is the same or less, just update the header accordingly. - // it is not necessary to free unused space here because it is cleared - // when grown again anyway. + // unused space is cleared when grown, so no need to do this here. } header.payloadSize = newPayloadSize; return ref; @@ -97,20 +105,21 @@ function unref(ref: usize): HEADER { } /** Frees an object. Must not have been registered with GC yet. */ -export function FREE(ref: usize): void { +@unsafe export function FREE(ref: usize): void { memory.free(changetype(unref(ref))); } /** Registers a managed object. Cannot be free'd anymore afterwards. */ -@inline export function REGISTER(ref: usize): T { - // inline this because it's generic so we don't get a bunch of functions - unref(ref).classId = __rt_classid(); +@unsafe @inline export function REGISTER(ref: usize): T { + // 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. */ -export function LINK(ref: usize, parentRef: usize): void { +@unsafe export function LINK(ref: usize, parentRef: usize): void { assert(ref >= HEAP_BASE + HEADER_SIZE); // must be a heap object var header = changetype
(ref - HEADER_SIZE); assert(header.classId != HEADER_MAGIC && header.gc1 != 0 && header.gc2 != 0); // must be registered @@ -119,7 +128,7 @@ export function LINK(ref: usize, parentRef: usize): void { /** ArrayBuffer base class. */ export abstract class ArrayBufferBase { - static readonly MAX_BYTELENGTH: i32 = MAX_SIZE_32 - HEADER_SIZE; + @lazy static readonly MAX_BYTELENGTH: i32 = MAX_SIZE_32 - HEADER_SIZE; get byteLength(): i32 { return changetype
(changetype(this) - HEADER_SIZE).payloadSize; } @@ -127,8 +136,62 @@ export abstract class ArrayBufferBase { /** String base class. */ export abstract class StringBase { - static readonly MAX_LENGTH: i32 = (MAX_SIZE_32 - HEADER_SIZE) >> 1; + @lazy static readonly MAX_LENGTH: i32 = (MAX_SIZE_32 - HEADER_SIZE) >> 1; get length(): i32 { return changetype
(changetype(this) - HEADER_SIZE).payloadSize >> 1; } } + +import { memcmp, memmove, memset } from "./internal/memory"; + +/** Memory manager interface. */ +export namespace memory { + @builtin export declare function size(): i32; + @builtin @unsafe export declare function grow(pages: i32): i32; + @builtin @unsafe @inline export function fill(dst: usize, c: u8, n: usize): void { + memset(dst, c, n); // fallback if "bulk-memory" isn't enabled + } + @builtin @unsafe @inline export function copy(dst: usize, src: usize, n: usize): void { + memmove(dst, src, n); // fallback if "bulk-memory" isn't enabled + } + @unsafe export function init(segmentIndex: u32, srcOffset: usize, dstOffset: usize, n: usize): void { + ERROR("not implemented"); + } + @unsafe export function drop(segmentIndex: u32): void { + ERROR("not implemented"); + } + @stub @inline export function allocate(size: usize): usize { + ERROR("stub: missing memory manager"); + return unreachable(); + } + @stub @unsafe @inline export function free(ptr: usize): void { + ERROR("stub: missing memory manager"); + } + @stub @unsafe @inline export function reset(): void { + ERROR("stub: not supported by memory manager"); + } + @inline export function compare(vl: usize, vr: usize, n: usize): i32 { + return memcmp(vl, vr, n); + } + @unsafe export function repeat(dst: usize, src: usize, srcLength: usize, count: usize): void { + var index: usize = 0; + var total = srcLength * count; + while (index < total) { + memory.copy(dst + index, src, srcLength); + index += srcLength; + } + } +} + +/** Garbage collector interface. */ +export namespace gc { + @builtin @unsafe export declare function classId(): u32; + @builtin @unsafe export declare function iterateRoots(fn: (ref: usize) => void): void; + @stub @unsafe export function register(ref: usize): void { + ERROR("stub: missing garbage collector"); + } + @stub @unsafe export function link(ref: usize, parentRef: usize): void { + ERROR("stub: missing garbage collector"); + } + @stub export function collect(): void {} +} diff --git a/std/assembly/runtime/itcm.ts b/std/assembly/runtime/itcm.ts deleted file mode 100644 index 19da440b..00000000 --- a/std/assembly/runtime/itcm.ts +++ /dev/null @@ -1,8 +0,0 @@ -@global namespace gc { - @unsafe export function register(ref: usize): void { - } - @unsafe export function link(ref: usize, parentRef: usize): void { - } - export function collect(): void { - } -} diff --git a/std/assembly/string.ts b/std/assembly/string.ts index 816bab4f..c13959db 100644 --- a/std/assembly/string.ts +++ b/std/assembly/string.ts @@ -13,75 +13,16 @@ import { STORE } from "./internal/arraybuffer"; -function compareImpl(str1: String, offset1: usize, str2: String, offset2: usize, len: usize): i32 { +function compareImpl(str1: String, index1: usize, str2: String, index2: usize, len: usize): i32 { var result: i32 = 0; - var ptr1 = changetype(str1) + (offset1 << 1); - var ptr2 = changetype(str2) + (offset2 << 1); + var ptr1 = changetype(str1) + (index1 << 1); + var ptr2 = changetype(str2) + (index2 << 1); while (len && !(result = load(ptr1) - load(ptr2))) { --len, ptr1 += 2, ptr2 += 2; } return result; } -function repeatImpl(dst: usize, dstIndex: usize, src: String, count: i32): void { - var length = src.length; - if (ASC_SHRINK_LEVEL > 1) { - let strLen = length << 1; - let to = changetype(dst) + (dstIndex << 1); - let from = changetype(src); - for (let i = 0, len = strLen * count; i < len; i += strLen) { - memory.copy(to + i, from, strLen); - } - } else { - switch (length) { - case 0: break; - case 1: { - let cc = load(changetype(src)); - let out = changetype(dst) + (dstIndex << 1); - for (let i = 0; i < count; ++i) { - store(out + (i << 1), cc); - } - break; - } - case 2: { - let cc = load(changetype(src)); - let out = changetype(dst) + (dstIndex << 1); - for (let i = 0; i < count; ++i) { - store(out + (i << 2), cc); - } - break; - } - case 3: { - let cc1 = load(changetype(src)); - let cc2 = load(changetype(src), 4); - let out = changetype(dst) + (dstIndex << 1); - for (let i = 0; i < count; ++i) { - store(out + (i << 2), cc1); - store(out + (i << 1), cc2, 4); - } - break; - } - case 4: { - let cc = load(changetype(src)); - let out = changetype(dst) + (dstIndex << 1); - for (let i = 0; i < count; ++i) { - store(out + (i << 3), cc); - } - break; - } - default: { - let strLen = length << 1; - let to = changetype(dst) + (dstIndex << 1); - let from = changetype(src); - for (let i = 0, len = strLen * count; i < len; i += strLen) { - memory.copy(to + i, from, strLen); - } - break; - } - } - } -} - function isWhiteSpaceOrLineTerminator(c: u16): bool { switch (c) { case 9: // @@ -373,7 +314,7 @@ export class String extends StringBase { let count = (len - 1) / padLen; let base = count * padLen; let rest = len - base; - repeatImpl(out, 0, padString, count); + memory.repeat(out, changetype(padString), padString.length << 1, count); if (rest) { memory.copy(out + (base << 1), changetype(padString), rest << 1); } @@ -400,7 +341,7 @@ export class String extends StringBase { let count = (len - 1) / padLen; let base = count * padLen; let rest = len - base; - repeatImpl(out, length, padString, count); + memory.repeat(out + (length << 1), changetype(padString), padString.length << 1, count); if (rest) { memory.copy(out + ((base + length) << 1), changetype(padString), rest << 1); } @@ -422,7 +363,7 @@ export class String extends StringBase { if (count == 0 || !length) return changetype(""); if (count == 1) return this; var out = ALLOC(length * count); - repeatImpl(out, 0, this, count); + memory.repeat(out, changetype(this), length << 1, count); return REGISTER(out); } @@ -468,7 +409,7 @@ export class String extends StringBase { } var result = new Array(); var end = 0, start = 0, i = 0; - while ((end = this.indexOf(separator, start)) != -1) { + while ((end = this.indexOf(separator!, start)) != -1) { let len = end - start; if (len > 0) { let out = ALLOC(len << 1); diff --git a/tests/compiler/std/runtime.optimized.wat b/tests/compiler/std/runtime.optimized.wat index aa243d22..2dcfc063 100644 --- a/tests/compiler/std/runtime.optimized.wat +++ b/tests/compiler/std/runtime.optimized.wat @@ -11,14 +11,14 @@ (import "env" "abort" (func $~lib/env/abort (param i32 i32 i32 i32))) (import "env" "trace" (func $~lib/env/trace (param i32 i32 f64 f64 f64 f64 f64))) (memory $0 1) - (data (i32.const 8) "\16\00\00\00~\00l\00i\00b\00/\00a\00l\00l\00o\00c\00a\00t\00o\00r\00/\00t\00l\00s\00f\00.\00t\00s") - (data (i32.const 56) "\0e\00\00\00s\00t\00d\00/\00r\00u\00n\00t\00i\00m\00e\00.\00t\00s") - (data (i32.const 88) "\08\00\00\00b\00a\00r\00r\00i\00e\00r\001") - (data (i32.const 112) "\08\00\00\00b\00a\00r\00r\00i\00e\00r\002") - (data (i32.const 136) "\08\00\00\00b\00a\00r\00r\00i\00e\00r\003") - (data (i32.const 160) "\15\00\00\00~\00l\00i\00b\00/\00r\00u\00n\00t\00i\00m\00e\00/\00i\00n\00d\00e\00x\00.\00t\00s") + (data (i32.const 8) "\01\00\00\00,\00\00\00~\00l\00i\00b\00/\00a\00l\00l\00o\00c\00a\00t\00o\00r\00/\00t\00l\00s\00f\00.\00t\00s") + (data (i32.const 64) "\01\00\00\00\1c\00\00\00s\00t\00d\00/\00r\00u\00n\00t\00i\00m\00e\00.\00t\00s") + (data (i32.const 104) "\01\00\00\00\10\00\00\00b\00a\00r\00r\00i\00e\00r\001") + (data (i32.const 128) "\01\00\00\00\10\00\00\00b\00a\00r\00r\00i\00e\00r\002") + (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) $null) + (elem (i32.const 0) $~lib/runtime/gc.collect) (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,6 +34,9 @@ (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 $~lib/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 @@ -41,7 +44,7 @@ i32.ge_u if i32.const 0 - i32.const 8 + i32.const 16 i32.const 144 i32.const 4 call $~lib/env/abort @@ -61,7 +64,7 @@ i32.ge_u if i32.const 0 - i32.const 8 + i32.const 16 i32.const 167 i32.const 4 call $~lib/env/abort @@ -72,7 +75,7 @@ i32.ge_u if i32.const 0 - i32.const 8 + i32.const 16 i32.const 168 i32.const 4 call $~lib/env/abort @@ -98,7 +101,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 89 i32.const 4 call $~lib/env/abort @@ -116,7 +119,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 90 i32.const 11 call $~lib/env/abort @@ -129,7 +132,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 428 i32.const 2 call $~lib/env/abort @@ -146,7 +149,7 @@ i32.ge_u if i32.const 0 - i32.const 8 + i32.const 16 i32.const 158 i32.const 4 call $~lib/env/abort @@ -157,7 +160,7 @@ i32.ge_u if i32.const 0 - i32.const 8 + i32.const 16 i32.const 159 i32.const 4 call $~lib/env/abort @@ -180,7 +183,7 @@ i32.ge_u if i32.const 0 - i32.const 8 + i32.const 16 i32.const 138 i32.const 4 call $~lib/env/abort @@ -206,7 +209,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 258 i32.const 4 call $~lib/env/abort @@ -229,7 +232,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 260 i32.const 4 call $~lib/env/abort @@ -330,7 +333,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 81 i32.const 4 call $~lib/env/abort @@ -344,7 +347,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 82 i32.const 11 call $~lib/env/abort @@ -360,7 +363,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 334 i32.const 4 call $~lib/env/abort @@ -372,7 +375,7 @@ i32.ne if i32.const 0 - i32.const 8 + i32.const 16 i32.const 335 i32.const 4 call $~lib/env/abort @@ -385,7 +388,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 336 i32.const 4 call $~lib/env/abort @@ -407,7 +410,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 189 i32.const 4 call $~lib/env/abort @@ -421,7 +424,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 191 i32.const 4 call $~lib/env/abort @@ -445,7 +448,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 193 i32.const 4 call $~lib/env/abort @@ -457,7 +460,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 197 i32.const 23 call $~lib/env/abort @@ -499,7 +502,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 211 i32.const 24 call $~lib/env/abort @@ -513,7 +516,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 213 i32.const 6 call $~lib/env/abort @@ -562,7 +565,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 226 i32.const 4 call $~lib/env/abort @@ -641,7 +644,7 @@ i32.gt_u if i32.const 0 - i32.const 8 + i32.const 16 i32.const 377 i32.const 4 call $~lib/env/abort @@ -652,7 +655,7 @@ i32.and if i32.const 0 - i32.const 8 + i32.const 16 i32.const 378 i32.const 4 call $~lib/env/abort @@ -663,7 +666,7 @@ i32.and if i32.const 0 - i32.const 8 + i32.const 16 i32.const 379 i32.const 4 call $~lib/env/abort @@ -680,7 +683,7 @@ i32.lt_u if i32.const 0 - i32.const 8 + i32.const 16 i32.const 384 i32.const 6 call $~lib/env/abort @@ -708,7 +711,7 @@ i32.lt_u if i32.const 0 - i32.const 8 + i32.const 16 i32.const 393 i32.const 6 call $~lib/env/abort @@ -761,7 +764,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 422 i32.const 2 call $~lib/env/abort @@ -786,7 +789,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 296 i32.const 4 call $~lib/env/abort @@ -866,7 +869,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 323 i32.const 16 call $~lib/env/abort @@ -894,7 +897,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 348 i32.const 4 call $~lib/env/abort @@ -914,7 +917,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 349 i32.const 4 call $~lib/env/abort @@ -925,7 +928,7 @@ i32.and if i32.const 0 - i32.const 8 + i32.const 16 i32.const 350 i32.const 4 call $~lib/env/abort @@ -977,7 +980,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 368 i32.const 25 call $~lib/env/abort @@ -994,7 +997,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) @@ -1021,14 +1024,14 @@ if unreachable end - i32.const 208 + i32.const 216 local.set $3 - i32.const 208 + i32.const 216 global.set $~lib/allocator/tlsf/ROOT i32.const 2912 i32.const 0 i32.store - i32.const 208 + i32.const 216 i32.const 0 i32.store i32.const 0 @@ -1038,7 +1041,7 @@ i32.const 22 i32.lt_u if - i32.const 208 + i32.const 216 local.get $1 i32.const 0 call $~lib/allocator/tlsf/Root#setSLMap @@ -1049,7 +1052,7 @@ i32.const 32 i32.lt_u if - i32.const 208 + i32.const 216 local.get $1 local.get $2 i32.const 0 @@ -1068,8 +1071,8 @@ br $repeat|0 end end - i32.const 208 - i32.const 3128 + i32.const 216 + i32.const 3136 current_memory i32.const 16 i32.shl @@ -1142,9 +1145,9 @@ local.get $2 else i32.const 0 - i32.const 8 + i32.const 16 i32.const 480 - i32.const 12 + i32.const 14 call $~lib/env/abort unreachable end @@ -1159,9 +1162,9 @@ end if i32.const 0 - i32.const 8 + i32.const 16 i32.const 483 - i32.const 2 + i32.const 4 call $~lib/env/abort unreachable end @@ -1389,7 +1392,7 @@ end end ) - (func $~lib/runtime/index/ALLOC (; 18 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/runtime/ALLOC (; 18 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) (local $1 i32) i32.const 1 i32.const 32 @@ -1399,7 +1402,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 @@ -2515,7 +2518,7 @@ end end ) - (func $~lib/allocator/tlsf/__memory_free (; 21 ;) (type $FUNCSIG$vi) (param $0 i32) + (func $~lib/allocator/tlsf/memory.free (; 21 ;) (type $FUNCSIG$vi) (param $0 i32) (local $1 i32) (local $2 i32) (local $3 i32) @@ -2534,9 +2537,9 @@ i32.and if i32.const 0 - i32.const 8 + i32.const 16 i32.const 494 - i32.const 6 + i32.const 8 call $~lib/env/abort unreachable end @@ -2553,7 +2556,7 @@ end end ) - (func $~lib/runtime/index/REALLOC (; 22 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) + (func $~lib/runtime/REALLOC (; 22 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) (local $2 i32) (local $3 i32) (local $4 i32) @@ -2575,6 +2578,11 @@ i32.clz i32.sub i32.shl + i32.const 0 + local.get $0 + i32.const 216 + i32.gt_u + select i32.const 1 i32.const 32 local.get $1 @@ -2587,7 +2595,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 @@ -2616,8 +2624,19 @@ i32.const -1520547049 i32.eq if + local.get $0 + i32.const 216 + i32.le_u + if + i32.const 0 + i32.const 184 + 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 end local.get $5 local.set $3 @@ -2638,14 +2657,14 @@ i32.store offset=4 local.get $0 ) - (func $~lib/runtime/index/ensureUnregistered (; 23 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/runtime/unref (; 23 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) local.get $0 - i32.const 224 + i32.const 232 i32.lt_u if i32.const 0 - i32.const 160 - i32.const 89 + i32.const 184 + i32.const 101 i32.const 2 call $~lib/env/abort unreachable @@ -2659,15 +2678,19 @@ i32.ne if i32.const 0 - i32.const 160 - i32.const 91 + i32.const 184 + i32.const 103 i32.const 2 call $~lib/env/abort unreachable end local.get $0 ) - (func $start:std/runtime (; 24 ;) (type $FUNCSIG$v) + (func $std/runtime/gc.register (; 24 ;) (type $FUNCSIG$vi) (param $0 i32) + local.get $0 + global.set $std/runtime/register_ref + ) + (func $start:std/runtime (; 25 ;) (type $FUNCSIG$v) (local $0 i32) (local $1 i32) (local $2 i32) @@ -2706,8 +2729,8 @@ br $repeat|0 else i32.const 0 - i32.const 56 - i32.const 32 + i32.const 72 + i32.const 36 i32.const 2 call $~lib/env/abort unreachable @@ -2777,7 +2800,7 @@ br $continue|2 end end - i32.const 88 + i32.const 112 i32.const 1 global.get $std/runtime/barrier1 f64.convert_i32_u @@ -2786,7 +2809,7 @@ f64.const 0 f64.const 0 call $~lib/env/trace - i32.const 112 + i32.const 136 i32.const 1 global.get $std/runtime/barrier2 f64.convert_i32_u @@ -2795,7 +2818,7 @@ f64.const 0 f64.const 0 call $~lib/env/trace - i32.const 136 + i32.const 160 i32.const 1 global.get $std/runtime/barrier3 f64.convert_i32_u @@ -2805,7 +2828,7 @@ f64.const 0 call $~lib/env/trace i32.const 1 - call $~lib/runtime/index/ALLOC + call $~lib/runtime/ALLOC global.set $std/runtime/ref1 global.get $std/runtime/ref1 i32.const 16 @@ -2817,8 +2840,8 @@ i32.ne if i32.const 0 - i32.const 56 - i32.const 47 + i32.const 72 + i32.const 51 i32.const 0 call $~lib/env/abort unreachable @@ -2829,8 +2852,8 @@ i32.ne if i32.const 0 - i32.const 56 - i32.const 48 + i32.const 72 + i32.const 52 i32.const 0 call $~lib/env/abort unreachable @@ -2839,12 +2862,12 @@ local.tee $0 local.get $0 global.get $std/runtime/barrier1 - call $~lib/runtime/index/REALLOC + call $~lib/runtime/REALLOC i32.ne if i32.const 0 - i32.const 56 - i32.const 49 + i32.const 72 + i32.const 53 i32.const 0 call $~lib/env/abort unreachable @@ -2855,23 +2878,23 @@ i32.ne if i32.const 0 - i32.const 56 - i32.const 50 + i32.const 72 + i32.const 54 i32.const 0 call $~lib/env/abort unreachable end global.get $std/runtime/ref1 global.get $std/runtime/barrier2 - call $~lib/runtime/index/REALLOC + call $~lib/runtime/REALLOC global.set $std/runtime/ref2 global.get $std/runtime/ref1 global.get $std/runtime/ref2 i32.eq if i32.const 0 + i32.const 72 i32.const 56 - i32.const 52 i32.const 0 call $~lib/env/abort unreachable @@ -2886,36 +2909,36 @@ i32.ne if i32.const 0 - i32.const 56 - i32.const 54 + i32.const 72 + i32.const 58 i32.const 0 call $~lib/env/abort unreachable end global.get $std/runtime/ref2 - call $~lib/runtime/index/ensureUnregistered - call $~lib/allocator/tlsf/__memory_free + call $~lib/runtime/unref + call $~lib/allocator/tlsf/memory.free global.get $std/runtime/barrier2 - call $~lib/runtime/index/ALLOC + call $~lib/runtime/ALLOC global.set $std/runtime/ref3 global.get $std/runtime/ref1 global.get $std/runtime/ref3 i32.ne if i32.const 0 - i32.const 56 - i32.const 57 + i32.const 72 + i32.const 61 i32.const 0 call $~lib/env/abort unreachable end global.get $std/runtime/barrier1 - call $~lib/runtime/index/ALLOC + call $~lib/runtime/ALLOC global.set $std/runtime/ref4 global.get $std/runtime/ref4 local.tee $0 - call $~lib/runtime/index/ensureUnregistered - i32.const 43 + call $~lib/runtime/unref + i32.const 2 i32.store local.get $0 global.set $std/runtime/register_ref @@ -2924,8 +2947,8 @@ i32.ne if i32.const 0 - i32.const 56 - i32.const 61 + i32.const 72 + i32.const 65 i32.const 0 call $~lib/env/abort unreachable @@ -2936,12 +2959,12 @@ global.set $std/runtime/header4 global.get $std/runtime/header4 i32.load - i32.const 43 + i32.const 2 i32.ne if i32.const 0 - i32.const 56 - i32.const 63 + i32.const 72 + i32.const 67 i32.const 0 call $~lib/env/abort unreachable @@ -2952,14 +2975,14 @@ i32.ne if i32.const 0 - i32.const 56 - i32.const 64 + i32.const 72 + i32.const 68 i32.const 0 call $~lib/env/abort unreachable end i32.const 10 - call $~lib/runtime/index/ALLOC + call $~lib/runtime/ALLOC global.set $std/runtime/ref5 global.get $std/runtime/ref5 i32.const 16 @@ -2969,8 +2992,8 @@ i32.ne if i32.const 0 - i32.const 56 - i32.const 67 + i32.const 72 + i32.const 71 i32.const 0 call $~lib/env/abort unreachable @@ -2985,17 +3008,20 @@ i32.ne if i32.const 0 - i32.const 56 - i32.const 68 + i32.const 72 + i32.const 72 i32.const 0 call $~lib/env/abort unreachable end ) - (func $start (; 25 ;) (type $FUNCSIG$v) - call $start:std/runtime - ) - (func $null (; 26 ;) (type $FUNCSIG$v) + (func $std/runtime/gc.link (; 26 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32) nop ) + (func $~lib/runtime/gc.collect (; 27 ;) (type $FUNCSIG$v) + nop + ) + (func $start (; 28 ;) (type $FUNCSIG$v) + call $start:std/runtime + ) ) diff --git a/tests/compiler/std/runtime.ts b/tests/compiler/std/runtime.ts index 3b952392..d1fba50a 100644 --- a/tests/compiler/std/runtime.ts +++ b/tests/compiler/std/runtime.ts @@ -2,15 +2,19 @@ import "allocator/tlsf"; var register_ref: usize = 0; -@global function __REGISTER_IMPL(ref: usize): void { - register_ref = ref; +@global namespace gc { + export function register(ref: usize): void { + register_ref = ref; + } + export function link(ref: usize, parentRef: usize): void { + } } import { HEADER, HEADER_SIZE, HEADER_MAGIC, - ALIGN, + ADJUST, ALLOC, REALLOC, FREE, @@ -21,22 +25,22 @@ import { class A {} class B {} -assert(__rt_classid() != __rt_classid()); +assert(gc.classId() != gc.classId()); function isPowerOf2(x: i32): bool { return x != 0 && (x & (x - 1)) == 0; } -assert(ALIGN(0) > 0); +assert(ADJUST(0) > 0); for (let i = 0; i < 9000; ++i) { - assert(isPowerOf2(ALIGN(i))); + assert(isPowerOf2(ADJUST(i))); } -var barrier1 = ALIGN(0); +var barrier1 = ADJUST(0); var barrier2 = barrier1 + 1; -while (ALIGN(barrier2 + 1) == ALIGN(barrier2)) ++barrier2; +while (ADJUST(barrier2 + 1) == ADJUST(barrier2)) ++barrier2; var barrier3 = barrier2 + 1; -while (ALIGN(barrier3 + 1) == ALIGN(barrier3)) ++barrier3; +while (ADJUST(barrier3 + 1) == ADJUST(barrier3)) ++barrier3; trace("barrier1", 1, barrier1); trace("barrier2", 1, barrier2); @@ -60,7 +64,7 @@ var ref4 = ALLOC(barrier1); REGISTER(ref4); // should call __REGISTER_IMPL assert(register_ref == ref4); var header4 = changetype
(register_ref - HEADER_SIZE); -assert(header4.classId == __rt_classid()); +assert(header4.classId == gc.classId()); assert(header4.payloadSize == barrier1); var ref5 = ALLOC(10); diff --git a/tests/compiler/std/runtime.untouched.wat b/tests/compiler/std/runtime.untouched.wat index c4fd9d9e..69d6344d 100644 --- a/tests/compiler/std/runtime.untouched.wat +++ b/tests/compiler/std/runtime.untouched.wat @@ -11,12 +11,12 @@ (import "env" "abort" (func $~lib/env/abort (param i32 i32 i32 i32))) (import "env" "trace" (func $~lib/env/trace (param i32 i32 f64 f64 f64 f64 f64))) (memory $0 1) - (data (i32.const 8) "\16\00\00\00~\00l\00i\00b\00/\00a\00l\00l\00o\00c\00a\00t\00o\00r\00/\00t\00l\00s\00f\00.\00t\00s\00") - (data (i32.const 56) "\0e\00\00\00s\00t\00d\00/\00r\00u\00n\00t\00i\00m\00e\00.\00t\00s\00") - (data (i32.const 88) "\08\00\00\00b\00a\00r\00r\00i\00e\00r\001\00") - (data (i32.const 112) "\08\00\00\00b\00a\00r\00r\00i\00e\00r\002\00") - (data (i32.const 136) "\08\00\00\00b\00a\00r\00r\00i\00e\00r\003\00") - (data (i32.const 160) "\15\00\00\00~\00l\00i\00b\00/\00r\00u\00n\00t\00i\00m\00e\00/\00i\00n\00d\00e\00x\00.\00t\00s\00") + (data (i32.const 8) "\01\00\00\00,\00\00\00~\00l\00i\00b\00/\00a\00l\00l\00o\00c\00a\00t\00o\00r\00/\00t\00l\00s\00f\00.\00t\00s\00") + (data (i32.const 64) "\01\00\00\00\1c\00\00\00s\00t\00d\00/\00r\00u\00n\00t\00i\00m\00e\00.\00t\00s\00") + (data (i32.const 104) "\01\00\00\00\10\00\00\00b\00a\00r\00r\00i\00e\00r\001\00") + (data (i32.const 128) "\01\00\00\00\10\00\00\00b\00a\00r\00r\00i\00e\00r\002\00") + (data (i32.const 152) "\01\00\00\00\10\00\00\00b\00a\00r\00r\00i\00e\00r\003\00") + (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\00") (table $0 1 funcref) (elem (i32.const 0) $null) (global $~lib/allocator/tlsf/SL_BITS i32 (i32.const 5)) @@ -48,9 +48,12 @@ (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/memory/HEAP_BASE i32 (i32.const 208)) + (global $~lib/runtime/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 $~lib/runtime/gc.collect)) (start $start) (func $start:~lib/allocator/tlsf (; 2 ;) (type $FUNCSIG$v) i32.const 1 @@ -61,14 +64,14 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 122 i32.const 0 call $~lib/env/abort unreachable end ) - (func $~lib/runtime/index/ALIGN (; 3 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/runtime/ADJUST (; 3 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) i32.const 1 i32.const 32 local.get $0 @@ -110,7 +113,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 144 i32.const 4 call $~lib/env/abort @@ -131,7 +134,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 167 i32.const 4 call $~lib/env/abort @@ -143,7 +146,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 168 i32.const 4 call $~lib/env/abort @@ -176,7 +179,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 89 i32.const 4 call $~lib/env/abort @@ -196,7 +199,7 @@ i32.eqz if (result i32) i32.const 0 - i32.const 8 + i32.const 16 i32.const 90 i32.const 11 call $~lib/env/abort @@ -212,7 +215,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 428 i32.const 2 call $~lib/env/abort @@ -230,7 +233,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 158 i32.const 4 call $~lib/env/abort @@ -242,7 +245,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 159 i32.const 4 call $~lib/env/abort @@ -266,7 +269,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 138 i32.const 4 call $~lib/env/abort @@ -296,7 +299,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 258 i32.const 4 call $~lib/env/abort @@ -322,7 +325,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 260 i32.const 4 call $~lib/env/abort @@ -433,7 +436,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 81 i32.const 4 call $~lib/env/abort @@ -447,7 +450,7 @@ i32.eqz if (result i32) i32.const 0 - i32.const 8 + i32.const 16 i32.const 82 i32.const 11 call $~lib/env/abort @@ -464,7 +467,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 334 i32.const 4 call $~lib/env/abort @@ -477,7 +480,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 335 i32.const 4 call $~lib/env/abort @@ -490,7 +493,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 336 i32.const 4 call $~lib/env/abort @@ -516,7 +519,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 189 i32.const 4 call $~lib/env/abort @@ -531,7 +534,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 191 i32.const 4 call $~lib/env/abort @@ -557,7 +560,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 193 i32.const 4 call $~lib/env/abort @@ -569,7 +572,7 @@ i32.eqz if (result i32) i32.const 0 - i32.const 8 + i32.const 16 i32.const 197 i32.const 23 call $~lib/env/abort @@ -617,7 +620,7 @@ i32.eqz if (result i32) i32.const 0 - i32.const 8 + i32.const 16 i32.const 211 i32.const 24 call $~lib/env/abort @@ -635,7 +638,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 213 i32.const 6 call $~lib/env/abort @@ -690,7 +693,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 226 i32.const 4 call $~lib/env/abort @@ -781,7 +784,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 377 i32.const 4 call $~lib/env/abort @@ -794,7 +797,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 378 i32.const 4 call $~lib/env/abort @@ -807,7 +810,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 379 i32.const 4 call $~lib/env/abort @@ -828,7 +831,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 384 i32.const 6 call $~lib/env/abort @@ -857,7 +860,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 393 i32.const 6 call $~lib/env/abort @@ -928,7 +931,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 422 i32.const 2 call $~lib/env/abort @@ -944,7 +947,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 422 i32.const 2 call $~lib/env/abort @@ -974,7 +977,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 296 i32.const 4 call $~lib/env/abort @@ -1070,7 +1073,7 @@ local.get $7 else i32.const 0 - i32.const 8 + i32.const 16 i32.const 323 i32.const 16 call $~lib/env/abort @@ -1107,7 +1110,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 348 i32.const 4 call $~lib/env/abort @@ -1127,7 +1130,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 349 i32.const 4 call $~lib/env/abort @@ -1140,7 +1143,7 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 350 i32.const 4 call $~lib/env/abort @@ -1200,7 +1203,7 @@ i32.eqz if (result i32) i32.const 0 - i32.const 8 + i32.const 16 i32.const 368 i32.const 25 call $~lib/env/abort @@ -1222,7 +1225,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) @@ -1235,7 +1238,7 @@ local.get $1 i32.eqz if - global.get $~lib/memory/HEAP_BASE + global.get $~lib/runtime/HEAP_BASE i32.const 7 i32.add i32.const 7 @@ -1426,9 +1429,9 @@ i32.eqz if (result i32) i32.const 0 - i32.const 8 + i32.const 16 i32.const 480 - i32.const 12 + i32.const 14 call $~lib/env/abort unreachable else @@ -1447,9 +1450,9 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 483 - i32.const 2 + i32.const 4 call $~lib/env/abort unreachable end @@ -1712,50 +1715,45 @@ end end ) - (func $~lib/runtime/index/ALLOC (; 24 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/runtime/ALLOC (; 24 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) (local $1 i32) (local $2 i32) (local $3 i32) (local $4 i32) (local $5 i32) - block $~lib/memory/memory.allocate|inlined.0 (result i32) - local.get $0 - call $~lib/runtime/index/ALIGN - local.set $1 - local.get $1 - call $~lib/allocator/tlsf/__memory_allocate - br $~lib/memory/memory.allocate|inlined.0 - end - local.set $2 - local.get $2 + local.get $0 + call $~lib/runtime/ADJUST + call $~lib/allocator/tlsf/memory.allocate + local.set $1 + local.get $1 i32.const -1520547049 i32.store - local.get $2 + local.get $1 local.get $0 i32.store offset=4 - local.get $2 + local.get $1 i32.const 0 i32.store offset=8 - local.get $2 + local.get $1 i32.const 0 i32.store offset=12 - local.get $2 + local.get $1 i32.const 16 i32.add - local.set $3 - block $~lib/memory/memory.fill|inlined.0 - local.get $3 - local.set $1 + local.set $2 + block $~lib/runtime/memory.fill|inlined.0 + local.get $2 + local.set $3 i32.const 0 local.set $4 local.get $0 local.set $5 - local.get $1 + local.get $3 local.get $4 local.get $5 call $~lib/internal/memory/memset end - local.get $3 + local.get $2 ) (func $~lib/internal/memory/memcpy (; 25 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32) (local $3 i32) @@ -3185,7 +3183,7 @@ end end ) - (func $~lib/allocator/tlsf/__memory_free (; 27 ;) (type $FUNCSIG$vi) (param $0 i32) + (func $~lib/allocator/tlsf/memory.free (; 27 ;) (type $FUNCSIG$vi) (param $0 i32) (local $1 i32) (local $2 i32) (local $3 i32) @@ -3209,9 +3207,9 @@ i32.eqz if i32.const 0 - i32.const 8 + i32.const 16 i32.const 494 - i32.const 6 + i32.const 8 call $~lib/env/abort unreachable end @@ -3228,7 +3226,7 @@ end end ) - (func $~lib/runtime/index/REALLOC (; 28 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) + (func $~lib/runtime/REALLOC (; 28 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) (local $2 i32) (local $3 i32) (local $4 i32) @@ -3249,20 +3247,20 @@ i32.lt_u if local.get $1 - call $~lib/runtime/index/ALIGN + call $~lib/runtime/ADJUST local.set $4 local.get $3 - call $~lib/runtime/index/ALIGN + call $~lib/runtime/ADJUST + i32.const 0 + local.get $0 + global.get $~lib/runtime/HEAP_BASE + i32.gt_u + select local.get $4 i32.lt_u if - block $~lib/memory/memory.allocate|inlined.1 (result i32) - local.get $4 - local.set $5 - local.get $5 - call $~lib/allocator/tlsf/__memory_allocate - br $~lib/memory/memory.allocate|inlined.1 - end + local.get $4 + call $~lib/allocator/tlsf/memory.allocate local.set $5 local.get $5 i32.const -1520547049 @@ -3277,7 +3275,7 @@ i32.const 16 i32.add local.set $6 - block $~lib/memory/memory.copy|inlined.0 + block $~lib/runtime/memory.copy|inlined.0 local.get $6 local.set $7 local.get $0 @@ -3289,7 +3287,7 @@ local.get $9 call $~lib/internal/memory/memmove end - block $~lib/memory/memory.fill|inlined.1 + block $~lib/runtime/memory.fill|inlined.1 local.get $6 local.get $3 i32.add @@ -3310,13 +3308,20 @@ i32.const -1520547049 i32.eq if - block $~lib/memory/memory.free|inlined.0 - local.get $2 - local.set $7 - local.get $7 - call $~lib/allocator/tlsf/__memory_free - br $~lib/memory/memory.free|inlined.0 + local.get $0 + global.get $~lib/runtime/HEAP_BASE + i32.gt_u + i32.eqz + if + i32.const 0 + i32.const 184 + i32.const 83 + i32.const 8 + call $~lib/env/abort + unreachable end + local.get $2 + call $~lib/allocator/tlsf/memory.free end local.get $5 local.set $2 @@ -3346,18 +3351,18 @@ i32.store offset=4 local.get $0 ) - (func $~lib/runtime/index/ensureUnregistered (; 29 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/runtime/unref (; 29 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) (local $1 i32) local.get $0 - global.get $~lib/memory/HEAP_BASE + global.get $~lib/runtime/HEAP_BASE i32.const 16 i32.add i32.ge_u i32.eqz if i32.const 0 - i32.const 160 - i32.const 89 + i32.const 184 + i32.const 101 i32.const 2 call $~lib/env/abort unreachable @@ -3373,36 +3378,30 @@ i32.eqz if i32.const 0 - i32.const 160 - i32.const 91 + i32.const 184 + i32.const 103 i32.const 2 call $~lib/env/abort unreachable end local.get $1 ) - (func $~lib/runtime/index/FREE (; 30 ;) (type $FUNCSIG$vi) (param $0 i32) - (local $1 i32) - block $~lib/memory/memory.free|inlined.1 - local.get $0 - call $~lib/runtime/index/ensureUnregistered - local.set $1 - local.get $1 - call $~lib/allocator/tlsf/__memory_free - br $~lib/memory/memory.free|inlined.1 - end + (func $~lib/runtime/FREE (; 30 ;) (type $FUNCSIG$vi) (param $0 i32) + local.get $0 + call $~lib/runtime/unref + call $~lib/allocator/tlsf/memory.free ) - (func $std/runtime/__REGISTER_IMPL (; 31 ;) (type $FUNCSIG$vi) (param $0 i32) + (func $std/runtime/gc.register (; 31 ;) (type $FUNCSIG$vi) (param $0 i32) local.get $0 global.set $std/runtime/register_ref ) - (func $~lib/runtime/index/ArrayBufferBase#get:byteLength (; 32 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/runtime/ArrayBufferBase#get:byteLength (; 32 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) local.get $0 i32.const 16 i32.sub i32.load offset=4 ) - (func $~lib/runtime/index/StringBase#get:length (; 33 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/runtime/StringBase#get:length (; 33 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) local.get $0 i32.const 16 i32.sub @@ -3413,27 +3412,27 @@ (func $start:std/runtime (; 34 ;) (type $FUNCSIG$v) (local $0 i32) call $start:~lib/allocator/tlsf - i32.const 43 - i32.const 44 + i32.const 2 + i32.const 3 i32.ne i32.eqz if i32.const 0 - i32.const 56 - i32.const 24 + i32.const 72 + i32.const 28 i32.const 0 call $~lib/env/abort unreachable end i32.const 0 - call $~lib/runtime/index/ALIGN + call $~lib/runtime/ADJUST i32.const 0 i32.gt_u i32.eqz if i32.const 0 - i32.const 56 - i32.const 30 + i32.const 72 + i32.const 34 i32.const 0 call $~lib/env/abort unreachable @@ -3448,13 +3447,13 @@ i32.eqz br_if $break|0 local.get $0 - call $~lib/runtime/index/ALIGN + call $~lib/runtime/ADJUST call $std/runtime/isPowerOf2 i32.eqz if i32.const 0 - i32.const 56 - i32.const 32 + i32.const 72 + i32.const 36 i32.const 2 call $~lib/env/abort unreachable @@ -3469,7 +3468,7 @@ unreachable end i32.const 0 - call $~lib/runtime/index/ALIGN + call $~lib/runtime/ADJUST global.set $std/runtime/barrier1 global.get $std/runtime/barrier1 i32.const 1 @@ -3480,9 +3479,9 @@ global.get $std/runtime/barrier2 i32.const 1 i32.add - call $~lib/runtime/index/ALIGN + call $~lib/runtime/ADJUST global.get $std/runtime/barrier2 - call $~lib/runtime/index/ALIGN + call $~lib/runtime/ADJUST i32.eq if global.get $std/runtime/barrier2 @@ -3502,9 +3501,9 @@ global.get $std/runtime/barrier3 i32.const 1 i32.add - call $~lib/runtime/index/ALIGN + call $~lib/runtime/ADJUST global.get $std/runtime/barrier3 - call $~lib/runtime/index/ALIGN + call $~lib/runtime/ADJUST i32.eq if global.get $std/runtime/barrier3 @@ -3515,7 +3514,7 @@ end end end - i32.const 88 + i32.const 112 i32.const 1 global.get $std/runtime/barrier1 f64.convert_i32_u @@ -3524,7 +3523,7 @@ f64.const 0 f64.const 0 call $~lib/env/trace - i32.const 112 + i32.const 136 i32.const 1 global.get $std/runtime/barrier2 f64.convert_i32_u @@ -3533,7 +3532,7 @@ f64.const 0 f64.const 0 call $~lib/env/trace - i32.const 136 + i32.const 160 i32.const 1 global.get $std/runtime/barrier3 f64.convert_i32_u @@ -3543,7 +3542,7 @@ f64.const 0 call $~lib/env/trace i32.const 1 - call $~lib/runtime/index/ALLOC + call $~lib/runtime/ALLOC global.set $std/runtime/ref1 global.get $std/runtime/ref1 i32.const 16 @@ -3556,8 +3555,8 @@ i32.eqz if i32.const 0 - i32.const 56 - i32.const 47 + i32.const 72 + i32.const 51 i32.const 0 call $~lib/env/abort unreachable @@ -3569,8 +3568,8 @@ i32.eqz if i32.const 0 - i32.const 56 - i32.const 48 + i32.const 72 + i32.const 52 i32.const 0 call $~lib/env/abort unreachable @@ -3578,13 +3577,13 @@ global.get $std/runtime/ref1 global.get $std/runtime/ref1 global.get $std/runtime/barrier1 - call $~lib/runtime/index/REALLOC + call $~lib/runtime/REALLOC i32.eq i32.eqz if i32.const 0 - i32.const 56 - i32.const 49 + i32.const 72 + i32.const 53 i32.const 0 call $~lib/env/abort unreachable @@ -3596,15 +3595,15 @@ i32.eqz if i32.const 0 - i32.const 56 - i32.const 50 + i32.const 72 + i32.const 54 i32.const 0 call $~lib/env/abort unreachable end global.get $std/runtime/ref1 global.get $std/runtime/barrier2 - call $~lib/runtime/index/REALLOC + call $~lib/runtime/REALLOC global.set $std/runtime/ref2 global.get $std/runtime/ref1 global.get $std/runtime/ref2 @@ -3612,8 +3611,8 @@ i32.eqz if i32.const 0 + i32.const 72 i32.const 56 - i32.const 52 i32.const 0 call $~lib/env/abort unreachable @@ -3629,16 +3628,16 @@ i32.eqz if i32.const 0 - i32.const 56 - i32.const 54 + i32.const 72 + i32.const 58 i32.const 0 call $~lib/env/abort unreachable end global.get $std/runtime/ref2 - call $~lib/runtime/index/FREE + call $~lib/runtime/FREE global.get $std/runtime/barrier2 - call $~lib/runtime/index/ALLOC + call $~lib/runtime/ALLOC global.set $std/runtime/ref3 global.get $std/runtime/ref1 global.get $std/runtime/ref3 @@ -3646,33 +3645,35 @@ i32.eqz if i32.const 0 - i32.const 56 - i32.const 57 + i32.const 72 + i32.const 61 i32.const 0 call $~lib/env/abort unreachable end global.get $std/runtime/barrier1 - call $~lib/runtime/index/ALLOC + call $~lib/runtime/ALLOC global.set $std/runtime/ref4 - block $~lib/runtime/index/REGISTER|inlined.0 + block $~lib/runtime/REGISTER|inlined.0 (result i32) global.get $std/runtime/ref4 local.set $0 local.get $0 - call $~lib/runtime/index/ensureUnregistered - i32.const 43 + call $~lib/runtime/unref + i32.const 2 i32.store local.get $0 - call $std/runtime/__REGISTER_IMPL + call $std/runtime/gc.register + local.get $0 end + drop global.get $std/runtime/register_ref global.get $std/runtime/ref4 i32.eq i32.eqz if i32.const 0 - i32.const 56 - i32.const 61 + i32.const 72 + i32.const 65 i32.const 0 call $~lib/env/abort unreachable @@ -3683,13 +3684,13 @@ global.set $std/runtime/header4 global.get $std/runtime/header4 i32.load - i32.const 43 + i32.const 2 i32.eq i32.eqz if i32.const 0 - i32.const 56 - i32.const 63 + i32.const 72 + i32.const 67 i32.const 0 call $~lib/env/abort unreachable @@ -3701,45 +3702,51 @@ i32.eqz if i32.const 0 - i32.const 56 - i32.const 64 - i32.const 0 - call $~lib/env/abort - unreachable - end - i32.const 10 - call $~lib/runtime/index/ALLOC - global.set $std/runtime/ref5 - global.get $std/runtime/ref5 - call $~lib/runtime/index/ArrayBufferBase#get:byteLength - i32.const 10 - i32.eq - i32.eqz - if - i32.const 0 - i32.const 56 - i32.const 67 - i32.const 0 - call $~lib/env/abort - unreachable - end - global.get $std/runtime/ref5 - call $~lib/runtime/index/StringBase#get:length - i32.const 5 - i32.eq - i32.eqz - if - i32.const 0 - i32.const 56 + i32.const 72 i32.const 68 i32.const 0 call $~lib/env/abort unreachable end + i32.const 10 + call $~lib/runtime/ALLOC + global.set $std/runtime/ref5 + global.get $std/runtime/ref5 + call $~lib/runtime/ArrayBufferBase#get:byteLength + i32.const 10 + i32.eq + i32.eqz + if + i32.const 0 + i32.const 72 + i32.const 71 + i32.const 0 + call $~lib/env/abort + unreachable + end + global.get $std/runtime/ref5 + call $~lib/runtime/StringBase#get:length + i32.const 5 + i32.eq + i32.eqz + if + i32.const 0 + i32.const 72 + i32.const 72 + i32.const 0 + call $~lib/env/abort + unreachable + end ) - (func $start (; 35 ;) (type $FUNCSIG$v) + (func $std/runtime/gc.link (; 35 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32) + nop + ) + (func $~lib/runtime/gc.collect (; 36 ;) (type $FUNCSIG$v) + nop + ) + (func $start (; 37 ;) (type $FUNCSIG$v) call $start:std/runtime ) - (func $null (; 36 ;) (type $FUNCSIG$v) + (func $null (; 38 ;) (type $FUNCSIG$v) ) )