From ba1a0c23693b31816bb0b847397599624e2344a9 Mon Sep 17 00:00:00 2001 From: dcode Date: Sun, 12 May 2019 13:50:28 +0200 Subject: [PATCH] Initial new rt integration --- cli/asc.js | 4 +- cli/asc.json | 9 +- lib/lint/base.json | 3 - lib/loader/index.js | 41 +- src/builtins.ts | 116 ++-- src/common.ts | 18 +- src/compiler.ts | 732 +++++++++++---------- src/program.ts | 215 +++--- src/types.ts | 7 +- std/assembly/allocator/README.md | 21 - std/assembly/allocator/arena.ts | 41 -- std/assembly/allocator/emscripten.ts | 19 - std/assembly/allocator/index.d.ts | 3 - std/assembly/allocator/system.ts | 19 - std/assembly/allocator/tlsf.ts | 529 --------------- std/assembly/array.ts | 243 ++----- std/assembly/arraybuffer.ts | 26 +- std/assembly/builtins.ts | 20 + std/assembly/collector/README.md | 113 ---- std/assembly/collector/dummy.ts | 37 -- std/assembly/collector/dummyrc.ts | 29 - std/assembly/collector/index.d.ts | 15 - std/assembly/collector/itcm.ts | 258 -------- std/assembly/collector/pure.ts | 269 -------- std/assembly/dataview.ts | 6 +- std/assembly/fixedarray.ts | 50 +- std/assembly/map.ts | 127 +--- std/assembly/memory.ts | 35 - std/assembly/rt/README.md | 63 +- std/assembly/rt/common.ts | 69 +- std/assembly/rt/index-full.ts | 3 + std/assembly/rt/index-none.ts | 1 + std/assembly/rt/{stub.ts => index-stub.ts} | 54 +- std/assembly/rt/index.d.ts | 13 + std/assembly/rt/index.ts | 58 -- std/assembly/rt/{pure.ts => purerc.ts} | 58 +- std/assembly/rt/tlsf.ts | 43 +- std/assembly/runtime.ts | 148 ----- std/assembly/runtime/README.md | 38 -- std/assembly/runtime/arena.ts | 3 - std/assembly/runtime/none.ts | 0 std/assembly/runtime/trace.ts | 4 - std/assembly/set.ts | 63 +- std/assembly/string.ts | 127 ++-- std/assembly/typedarray.ts | 13 +- std/assembly/util/allocator.ts | 19 - std/assembly/util/number.ts | 37 +- std/assembly/util/runtime.ts | 158 ----- tests/runtime/index.html | 10 +- tests/runtime/optimized.wat | 28 +- tests/runtime/untouched.wasm | Bin 4786 -> 4755 bytes tests/runtime/untouched.wat | 36 +- 52 files changed, 1066 insertions(+), 2985 deletions(-) delete mode 100644 std/assembly/allocator/README.md delete mode 100644 std/assembly/allocator/arena.ts delete mode 100644 std/assembly/allocator/emscripten.ts delete mode 100644 std/assembly/allocator/index.d.ts delete mode 100644 std/assembly/allocator/system.ts delete mode 100644 std/assembly/allocator/tlsf.ts delete mode 100644 std/assembly/collector/README.md delete mode 100644 std/assembly/collector/dummy.ts delete mode 100644 std/assembly/collector/dummyrc.ts delete mode 100644 std/assembly/collector/index.d.ts delete mode 100644 std/assembly/collector/itcm.ts delete mode 100644 std/assembly/collector/pure.ts create mode 100644 std/assembly/rt/index-full.ts create mode 100644 std/assembly/rt/index-none.ts rename std/assembly/rt/{stub.ts => index-stub.ts} (55%) create mode 100644 std/assembly/rt/index.d.ts delete mode 100644 std/assembly/rt/index.ts rename std/assembly/rt/{pure.ts => purerc.ts} (83%) delete mode 100644 std/assembly/runtime.ts delete mode 100644 std/assembly/runtime/README.md delete mode 100644 std/assembly/runtime/arena.ts delete mode 100644 std/assembly/runtime/none.ts delete mode 100644 std/assembly/runtime/trace.ts delete mode 100644 std/assembly/util/allocator.ts delete mode 100644 std/assembly/util/runtime.ts diff --git a/cli/asc.js b/cli/asc.js index 61f6601a..1697f459 100644 --- a/cli/asc.js +++ b/cli/asc.js @@ -392,11 +392,11 @@ exports.main = function main(argv, options, callback) { // Include runtime template { let templateName = String(args.runtime); - let templateText = exports.libraryFiles["runtime/" + templateName]; + let templateText = exports.libraryFiles["rt/index-" + templateName]; if (templateText == null) { templateText = readFile(templateName + ".ts", baseDir); if (templateText == null) { - return callback(Error("Runtime template '" + templateName + " not found.")); + return callback(Error("Runtime template '" + templateName + "' not found.")); } } stats.parseCount++; diff --git a/cli/asc.json b/cli/asc.json index 76be15b2..44c87ed0 100644 --- a/cli/asc.json +++ b/cli/asc.json @@ -83,15 +83,14 @@ }, "runtime": { "description": [ - "Specifies the runtime template to include in the program.", + "Specifies the runtime implementation to include in the program.", "", - " none No allocator/GC or compose your own. [default]", - " trace TLSF memory allocator and ITCM garbage collector.", - " arena Just the arena memory allocator. No free/GC.", + " full Default runtime based on TLSF and reference counting.", + " stub Minimal stub implementation without free/GC support.", "" ], "type": "s", - "default": "none" + "default": "full" }, "debug": { "description": "Enables debug information in emitted binaries.", diff --git a/lib/lint/base.json b/lib/lint/base.json index 5fd658b4..74938c6e 100644 --- a/lib/lint/base.json +++ b/lib/lint/base.json @@ -42,9 +42,6 @@ "no-default-export": { "severity": "error" }, - "no-duplicate-imports": { - "severity": "error" - }, "no-duplicate-super": { "severity": "error" }, diff --git a/lib/loader/index.js b/lib/loader/index.js index 5e58a1bf..aa32b1cc 100644 --- a/lib/loader/index.js +++ b/lib/loader/index.js @@ -1,23 +1,31 @@ "use strict"; -const hasBigInt64 = typeof BigUint64Array !== "undefined"; -const thisPtr = Symbol(); +/** Size of the runtime header, in bytes. */ +const HEADER_SIZE = 16; +/** Runtime header offset of `classId`. */ +const CLASSID_OFFSET = -HEADER_SIZE; +/** Runtime header offset of `payloadLength`. */ +const PAYLOADLENGTH_OFFSET = -HEADER_SIZE + 4; +/** Whether BigInt arrays are supported. */ +const SUPPORTS_BIGINT = typeof BigUint64Array !== "undefined"; +/** Unique symbol for memoized 'this'. */ +const THIS = Symbol(); /** Gets a string from an U32 and an U16 view on a memory. */ function getStringImpl(U32, U16, ptr) { - var dataLength = U32[ptr >>> 2]; - var dataOffset = (ptr + 4) >>> 1; - var dataRemain = dataLength; + var size32 = U32[(ptr + PAYLOADLENGTH_OFFSET) >>> 2]; + var offset16 = ptr >>> 1; + var remain32 = size32; var parts = []; const chunkSize = 1024; - while (dataRemain > chunkSize) { - let last = U16[dataOffset + chunkSize - 1]; + while (remain32 > chunkSize) { + let last = U16[offset16 + chunkSize - 1]; let size = last >= 0xD800 && last < 0xDC00 ? chunkSize - 1 : chunkSize; - let part = U16.subarray(dataOffset, dataOffset += size); + let part = U16.subarray(offset16, offset16 += size); parts.push(String.fromCharCode.apply(String, part)); - dataRemain -= size; + remain32 -= size; } - return parts.join("") + String.fromCharCode.apply(String, U16.subarray(dataOffset, dataOffset + dataRemain)); + return parts.join("") + String.fromCharCode.apply(String, U16.subarray(offset16, offset16 + remain32)); } /** Prepares the base module prior to instantiation. */ @@ -54,8 +62,7 @@ function postInstantiate(baseModule, instance) { var memory_fill = rawExports["memory.fill"]; var memory_free = rawExports["memory.free"]; var table = rawExports.table; - var capabilities = rawExports[".capabilities"] || 0; - var setargc = rawExports[".setargc"] || function() {}; + var setargc = rawExports["$.setArgc"] || function() {}; // Provide views for all sorts of basic values var buffer, I8, U8, I16, U16, I32, U32, F32, F64, I64, U64; @@ -71,7 +78,7 @@ function postInstantiate(baseModule, instance) { U16 = new Uint16Array(buffer); I32 = new Int32Array(buffer); U32 = new Uint32Array(buffer); - if (hasBigInt64) { + if (SUPPORTS_BIGINT) { I64 = new BigInt64Array(buffer); U64 = new BigUint64Array(buffer); } @@ -271,7 +278,7 @@ function demangle(exports, baseModule) { }; ctor.prototype = {}; ctor.wrap = function(thisValue) { - return Object.create(ctor.prototype, { [thisPtr]: { value: thisValue, writable: false } }); + return Object.create(ctor.prototype, { [THIS]: { value: thisValue, writable: false } }); }; if (classElem) Object.getOwnPropertyNames(classElem).forEach(name => Object.defineProperty(ctor, name, Object.getOwnPropertyDescriptor(classElem, name)) @@ -285,8 +292,8 @@ function demangle(exports, baseModule) { let getter = exports[internalName.replace("set:", "get:")]; let setter = exports[internalName.replace("get:", "set:")]; Object.defineProperty(curr, name, { - get: function() { return getter(this[thisPtr]); }, - set: function(value) { setter(this[thisPtr], value); }, + get: function() { return getter(this[THIS]); }, + set: function(value) { setter(this[THIS], value); }, enumerable: true }); } @@ -297,7 +304,7 @@ function demangle(exports, baseModule) { Object.defineProperty(curr, name, { value: function (...args) { setargc(args.length); - return elem(this[thisPtr], ...args); + return elem(this[THIS], ...args); } }); } diff --git a/src/builtins.ts b/src/builtins.ts index d451131d..4488a01f 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -461,13 +461,19 @@ export namespace BuiltinSymbols { export const v8x16_shuffle = "~lib/builtins/v8x16.shuffle"; + // internals + export const HEAP_BASE = "~lib/builtins/HEAP_BASE"; + export const RTTI_BASE = "~lib/builtins/RTTI_BASE"; + export const idof = "~lib/builtins/idof"; + export const visit_globals = "~lib/builtins/visit_globals"; + export const visit_members = "~lib/builtins/visit_members"; + // std/diagnostics.ts 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"; @@ -477,8 +483,6 @@ export namespace BuiltinSymbols { export const memory_reset = "~lib/memory/memory.reset"; // std/runtime.ts - export const RTTI_BASE = "~lib/runtime/RTTI_BASE"; - export const runtime_id = "~lib/runtime/__runtime_id"; export const runtime_instanceof = "~lib/runtime/runtime.instanceof"; export const runtime_flags = "~lib/runtime/runtime.flags"; export const runtime_allocate = "~lib/util/runtime/allocate"; @@ -486,8 +490,6 @@ export namespace BuiltinSymbols { export const runtime_register = "~lib/util/runtime/register"; export const runtime_discard = "~lib/util/runtime/discard"; export const runtime_makeArray = "~lib/util/runtime/makeArray"; - export const gc_mark_roots = "~lib/runtime/__gc_mark_roots"; - export const gc_mark_members = "~lib/runtime/__gc_mark_members"; // std/typedarray.ts export const Int8Array = "~lib/typedarray/Int8Array"; @@ -3630,7 +3632,7 @@ export function compileCall( // === Internal runtime ======================================================================= - case BuiltinSymbols.runtime_id: { + case BuiltinSymbols.idof: { let type = evaluateConstantType(compiler, typeArguments, operands, reportNode); compiler.currentType = Type.u32; if (!type) return module.createUnreachable(); @@ -3644,31 +3646,32 @@ export function compileCall( } return module.createI32(classReference.id); } - case BuiltinSymbols.gc_mark_roots: { + case BuiltinSymbols.visit_globals: { if ( checkTypeAbsent(typeArguments, reportNode, prototype) | - checkArgsRequired(operands, 0, reportNode, compiler) - ) { - compiler.currentType = Type.void; - return module.createUnreachable(); - } - compiler.needsGcMark = true; - compiler.currentType = Type.void; - return module.createCall(BuiltinSymbols.gc_mark_roots, null, NativeType.None); - } - case BuiltinSymbols.gc_mark_members: { - if ( - checkTypeAbsent(typeArguments, reportNode, prototype) | - checkArgsRequired(operands, 2, reportNode, compiler) + checkArgsRequired(operands, 1, reportNode, compiler) // cookie ) { compiler.currentType = Type.void; return module.createUnreachable(); } let arg0 = compiler.compileExpression(operands[0], Type.u32, ConversionKind.IMPLICIT, WrapMode.NONE); - let arg1 = compiler.compileExpression(operands[1], compiler.options.usizeType, ConversionKind.IMPLICIT, WrapMode.NONE); - compiler.needsGcMark = true; + compiler.needsVisitGlobals = true; compiler.currentType = Type.void; - return module.createCall(BuiltinSymbols.gc_mark_members, [ arg0, arg1 ], NativeType.None); + return module.createCall(BuiltinSymbols.visit_globals, [ arg0 ], NativeType.None); + } + case BuiltinSymbols.visit_members: { + if ( + checkTypeAbsent(typeArguments, reportNode, prototype) | + checkArgsRequired(operands, 2, reportNode, compiler) // ref, cookie + ) { + compiler.currentType = Type.void; + return module.createUnreachable(); + } + let arg0 = compiler.compileExpression(operands[0], compiler.options.usizeType, ConversionKind.IMPLICIT, WrapMode.NONE); + let arg1 = compiler.compileExpression(operands[1], Type.u32, ConversionKind.IMPLICIT, WrapMode.NONE); + compiler.needsVisitMembers = true; + compiler.currentType = Type.void; + return module.createCall(BuiltinSymbols.visit_members, [ arg0, arg1 ], NativeType.None); } } @@ -4051,15 +4054,15 @@ export function compileAbort( ]); } -/** Compiles the `__gc_mark_roots` function. */ -export function compileMarkRoots(compiler: Compiler): void { +/** Compiles the `visit_globals` function. */ +export function compileVisitGlobals(compiler: Compiler): void { var module = compiler.module; var exprs = new Array(); - var typeRef = compiler.ensureFunctionType(null, Type.void); + var typeRef = compiler.ensureFunctionType([ Type.u32 ], Type.void); // cookie var nativeSizeType = compiler.options.nativeSizeType; - var markRef = assert(compiler.program.markRef); + var visitInstance = assert(compiler.program.visitInstance); - compiler.compileFunction(markRef); + compiler.compileFunction(visitInstance); for (let element of compiler.program.elementsByName.values()) { if (element.kind != ElementKind.GLOBAL) continue; @@ -4074,7 +4077,7 @@ export function compileMarkRoots(compiler: Compiler): void { let value = global.constantIntegerValue; if (i64_low(value) || i64_high(value)) { exprs.push( - module.createCall(markRef.internalName, [ + module.createCall(visitInstance.internalName, [ compiler.options.isWasm64 ? module.createI64(i64_low(value), i64_high(value)) : module.createI32(i64_low(value)) @@ -4084,35 +4087,35 @@ export function compileMarkRoots(compiler: Compiler): void { } else { exprs.push( module.createIf( - module.createTeeLocal( - 0, + module.createTeeLocal(1, module.createGetGlobal(global.internalName, nativeSizeType) ), - module.createCall(markRef.internalName, [ - module.createGetLocal(0, nativeSizeType) + module.createCall(visitInstance.internalName, [ + module.createGetLocal(1, nativeSizeType), // tempRef != null + module.createGetLocal(0, NativeType.I32) // cookie ], NativeType.None) ) ); } } } - module.addFunction(BuiltinSymbols.gc_mark_roots, typeRef, [ nativeSizeType ], + module.addFunction(BuiltinSymbols.visit_globals, typeRef, [ nativeSizeType ], exprs.length ? module.createBlock(null, exprs) : module.createNop() ); } -/** Compiles the `__gc_mark_members` function. */ -export function compileMarkMembers(compiler: Compiler): void { +/** Compiles the `visit_members` function. */ +export function compileVisitMembers(compiler: Compiler): void { var program = compiler.program; var module = compiler.module; var usizeType = program.options.usizeType; var nativeSizeType = usizeType.toNativeType(); var nativeSizeSize = usizeType.byteSize; - var ftype = compiler.ensureFunctionType([ Type.i32, usizeType ], Type.void); + var ftype = compiler.ensureFunctionType([ usizeType, Type.i32 ], Type.void); // ref, cookie var managedClasses = program.managedClasses; - var markRef = assert(program.markRef); + var visitInstance = assert(program.visitInstance); var names: string[] = [ "invalid" ]; // classId=0 is invalid var blocks = new Array(); var lastId = 0; @@ -4136,7 +4139,7 @@ export function compileMarkMembers(compiler: Compiler): void { } blocks.push([ module.createCall(traverseFunc.internalName, [ - module.createGetLocal(1, nativeSizeType) + module.createGetLocal(0, nativeSizeType) ], NativeType.None), module.createReturn() ]); @@ -4160,19 +4163,16 @@ export function compileMarkMembers(compiler: Compiler): void { // if ($2 = value) FIELDCLASS~traverse($2) module.createIf( module.createTeeLocal(2, - module.createLoad( - nativeSizeSize, - false, - module.createGetLocal(1, nativeSizeType), - nativeSizeType, - fieldOffset + module.createLoad(nativeSizeSize, false, + module.createGetLocal(0, nativeSizeType), + nativeSizeType, fieldOffset ) ), module.createBlock(null, [ - module.createCall(markRef.internalName, [ + module.createCall(visitInstance.internalName, [ module.createGetLocal(2, nativeSizeType) ], NativeType.None), - module.createCall(BuiltinSymbols.gc_mark_members, [ + module.createCall(BuiltinSymbols.visit_members, [ module.createI32(fieldClassId), module.createGetLocal(2, nativeSizeType) ], NativeType.None) @@ -4191,15 +4191,29 @@ export function compileMarkMembers(compiler: Compiler): void { var current: ExpressionRef; if (blocks.length) { - // create a big switch mapping class ids to traversal logic + // create a big switch mapping runtime ids to traversal functions current = module.createBlock(names[1], [ - module.createSwitch(names, "invalid", module.createGetLocal(0, NativeType.I32)) + module.createSwitch(names, "invalid", + module.createLoad(nativeSizeSize, false, + nativeSizeType == NativeType.I64 + ? module.createBinary(BinaryOp.SubI64, + module.createGetLocal(0, nativeSizeType), + module.createI64(8) + ) + : module.createBinary(BinaryOp.SubI32, + module.createGetLocal(0, nativeSizeType), + module.createI32(8) // rtId is at -8 + ), + NativeType.I32, + 0 + ) + ) ]); for (let i = 0, k = blocks.length; i < k; ++i) { blocks[i].unshift(current); current = module.createBlock(i == k - 1 ? "invalid" : names[i + 2], blocks[i]); } - compiler.compileFunction(markRef); + compiler.compileFunction(visitInstance); // wrap the function with a terminating unreachable current = module.createBlock(null, [ current, @@ -4209,7 +4223,7 @@ export function compileMarkMembers(compiler: Compiler): void { // simplify current = module.createUnreachable(); } - module.addFunction(BuiltinSymbols.gc_mark_members, ftype, [ nativeSizeType ], current); + module.addFunction(BuiltinSymbols.visit_members, ftype, [ nativeSizeType ], current); } function typeToRuntimeFlags(type: Type, program: Program): RTTIFlags { diff --git a/src/common.ts b/src/common.ts index 78d58400..8a2ba600 100644 --- a/src/common.ts +++ b/src/common.ts @@ -182,13 +182,17 @@ export namespace CommonSymbols { export const abort = "abort"; export const pow = "pow"; export const mod = "mod"; - export const allocate = "allocate"; - export const reallocate = "reallocate"; - export const register = "register"; - export const discard = "discard"; - export const newString = "newString"; - export const newArrayBuffer = "newArrayBuffer"; - export const newArray = "newArray"; + export const alloc = "__alloc"; + export const realloc = "__realloc"; + export const free = "__free"; + export const retain = "__retain"; + export const release = "__release"; + export const retainRelease = "__retainRelease"; + export const collect = "__collect"; + export const typeinfo = "__typeinfo"; + export const instanceof_ = "__instanceof"; + export const visit = "__visit"; + export const allocArray = "__allocArray"; } // shared diff --git a/src/compiler.ts b/src/compiler.ts index 81a74e77..3d467e6b 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -7,8 +7,8 @@ import { BuiltinSymbols, compileCall as compileBuiltinCall, compileAbort, - compileMarkRoots, - compileMarkMembers, + compileVisitGlobals, + compileVisitMembers, compileRTTI, } from "./builtins"; @@ -281,8 +281,10 @@ export class Compiler extends DiagnosticEmitter { argcSet: FunctionRef = 0; /** Whether HEAP_BASE is required. */ needsHeap: bool = false; - /** Indicates whether the __gc_mark_* functions must be generated. */ - needsGcMark: bool = false; + /** Indicates whether the __visit_globals function must be generated. */ + needsVisitGlobals: bool = false; + /** Indicated whether the __visit_members function must be generated. */ + needsVisitMembers: bool = false; /** Whether RTTI is required. */ needsRTTI: bool = false; @@ -359,10 +361,8 @@ export class Compiler extends DiagnosticEmitter { } // compile gc features if utilized - if (this.needsGcMark) { - compileMarkRoots(this); - compileMarkMembers(this); - } + if (this.needsVisitGlobals) compileVisitGlobals(this); + if (this.needsVisitMembers) compileVisitMembers(this); // compile runtime type information module.removeGlobal(BuiltinSymbols.RTTI_BASE); @@ -593,71 +593,32 @@ export class Compiler extends DiagnosticEmitter { var nativeType = type.toNativeType(); var usizeType = this.options.usizeType; var nativeSizeType = usizeType.toNativeType(); + var valueExpr: ExpressionRef; if (type.isManaged(program)) { - let fn1: Function | null, fn2: Function | null; - let body: ExpressionRef[] = []; - if (fn1 = program.linkRef) { // tracing - if (fn2 = program.unlinkRef) { - body.push( - module.createCall(fn2.internalName, [ - module.createGetLocal(2, nativeType), - module.createGetLocal(0, nativeSizeType) - ], NativeType.None) - ); - } - body.push( - module.createCall(fn1.internalName, [ - module.createGetLocal(1, nativeSizeType), - module.createGetLocal(0, nativeSizeType) - ], NativeType.None) - ); - } else if (fn1 = program.retainRef) { // arc - fn2 = assert(program.releaseRef); - body.push( - module.createCall(fn2.internalName, [ - module.createGetLocal(2, nativeType) - ], NativeType.None) - ); - body.push( - module.createCall(fn1.internalName, [ - module.createGetLocal(1, nativeSizeType) - ], NativeType.None) - ); - } - module.addFunction( - name, - this.ensureFunctionType([ type ], Type.void, usizeType), - [ nativeType ], - module.createIf( // if (value != oldValue) release/retain .. - module.createBinary( - nativeSizeType == NativeType.I64 - ? BinaryOp.NeI64 - : BinaryOp.NeI32, - module.createGetLocal(1, nativeType), - module.createTeeLocal(2, - module.createLoad(type.byteSize, false, - module.createGetLocal(0, nativeSizeType), - nativeType, field.memoryOffset - ) - ) - ), - module.createBlock(null, body) - ) - ); - } else { - module.addFunction( - name, - this.ensureFunctionType([ type ], Type.void, usizeType), - null, - module.createStore( - type.byteSize, + let retainReleaseInstance = program.retainReleaseInstance; + this.compileFunction(retainReleaseInstance); + valueExpr = module.createCall(retainReleaseInstance.internalName, [ + module.createGetLocal(1, nativeType), // newRef + module.createLoad(type.byteSize, false, // oldRef module.createGetLocal(0, nativeSizeType), - module.createGetLocal(1, nativeType), - nativeType, - field.memoryOffset + nativeType, field.memoryOffset ) - ); + ], nativeType); + } else { + valueExpr = module.createGetLocal(1, nativeType); } + module.addFunction( + name, + this.ensureFunctionType([ type ], Type.void, usizeType), + null, + module.createStore( + type.byteSize, + module.createGetLocal(0, nativeSizeType), + valueExpr, + nativeType, + field.memoryOffset + ) + ); module.addFunctionExport(name, name); } @@ -969,8 +930,11 @@ export class Compiler extends DiagnosticEmitter { ); } module.addGlobal(internalName, nativeType, true, type.toNativeZero(module)); - if (type.isManaged(this.program) && this.program.retainRef) { - initExpr = this.makeInsertRef(initExpr, null, type.is(TypeFlags.NULLABLE)); + let program = this.program; + if (type.isManaged(program)) { + let retainInstance = program.retainInstance; + this.compileFunction(retainInstance); + initExpr = module.createCall(retainInstance.internalName, [ initExpr ], nativeType); } this.currentBody.push( module.createSetGlobal(internalName, initExpr) @@ -5230,75 +5194,124 @@ export class Compiler extends DiagnosticEmitter { return module.createUnreachable(); } + /** Makes an assignment to a local, possibly retaining and releasing affected references and keeping track of wrap and null states. */ makeLocalAssignment( local: Local, valueExpr: ExpressionRef, tee: bool, possiblyNull: bool ): ExpressionRef { - // TBD: use REPLACE macro to keep track of managed refcounts? or can the compiler evaluate - // this statically in closed contexts like functions in order to safe the extra work? + var module = this.module; + var program = this.program; var type = local.type; assert(type != Type.void); + var nativeType = type.toNativeType(); var flow = this.currentFlow; var localIndex = local.index; + if (type.is(TypeFlags.SHORT | TypeFlags.INTEGER)) { if (!flow.canOverflow(valueExpr, type)) flow.setLocalFlag(localIndex, LocalFlags.WRAPPED); else flow.unsetLocalFlag(localIndex, LocalFlags.WRAPPED); } + if (type.is(TypeFlags.NULLABLE)) { if (possiblyNull) flow.unsetLocalFlag(localIndex, LocalFlags.NONNULL); else flow.setLocalFlag(localIndex, LocalFlags.NONNULL); } - if (tee) { - this.currentType = type; - return this.module.createTeeLocal(localIndex, valueExpr); + + // TODO: retain/release on each local assignment is costly in that increments and decrements + // easily involve cache misses when updating refcounts. ultimate goal should be to statically + // eliminate as many retain/release calls on locals as possible, i.e. where it can be proven + // that refcount doesn't change during the execution of a function, respectively refcount on + // arguments (which are locals) can be proven to remain the same from pre-call to post-call. + + if (type.isManaged(program)) { + let retainReleaseInstance = program.retainReleaseInstance; + this.compileFunction(retainReleaseInstance); + if (tee) { // TEE(local = __retainRelease(value, local)) + this.currentType = type; + return module.createTeeLocal(localIndex, + module.createCall(retainReleaseInstance.internalName, [ + valueExpr, + module.createGetLocal(localIndex, nativeType) + ], nativeType) + ); + } else { // local = __retainRelease(value, local) + this.currentType = Type.void; + return module.createSetLocal(localIndex, + module.createCall(retainReleaseInstance.internalName, [ + valueExpr, + module.createGetLocal(localIndex, nativeType) + ], nativeType) + ); + } } else { - this.currentType = Type.void; - return this.module.createSetLocal(localIndex, valueExpr); + if (tee) { // TEE(local = value) + this.currentType = type; + return this.module.createTeeLocal(localIndex, valueExpr); + } else { // local = value + this.currentType = Type.void; + return this.module.createSetLocal(localIndex, valueExpr); + } } } + /** Makes an assignment to a global, possibly retaining and releasing affected references. */ makeGlobalAssignment(global: Global, valueExpr: ExpressionRef, tee: bool): ExpressionRef { var module = this.module; + var program = this.program; var type = global.type; assert(type != Type.void); var nativeType = type.toNativeType(); - // MANAGED (reference counting) - if (type.isManaged(this.program)) { - if (this.program.retainRef) { - valueExpr = this.makeReplaceRef( - valueExpr, - module.createGetGlobal(global.internalName, nativeType), - null, - type.is(TypeFlags.NULLABLE) + if (type.isManaged(program)) { + let retainReleaseInstance = program.retainReleaseInstance; + this.compileFunction(retainReleaseInstance); + if (tee) { // (global = __retainRelease(t1 = value, global)), t1 + let tempValue = this.currentFlow.getAndFreeTempLocal(type, true); // globals are wrapped + this.currentType = type; + return module.createBlock(null, [ + module.createSetGlobal(global.internalName, + module.createCall(retainReleaseInstance.internalName, [ + module.createTeeLocal(tempValue.index, valueExpr), + module.createGetGlobal(global.internalName, nativeType) + ], nativeType) + ), + module.createGetLocal(tempValue.index, nativeType) + ], nativeType); + } else { // global = __retainRelease(value, global) + this.currentType = Type.void; + return module.createSetGlobal(global.internalName, + module.createCall(retainReleaseInstance.internalName, [ + valueExpr, + module.createGetGlobal(global.internalName, nativeType) + ], nativeType) ); } - - // UNMANAGED } else { - valueExpr = this.ensureSmallIntegerWrap(valueExpr, type); // global values must be wrapped - } - - if (tee) { - let tempValue = this.currentFlow.getAndFreeTempLocal(type, true); - this.currentType = type; - return module.createBlock(null, [ - module.createSetGlobal(global.internalName, - module.createTeeLocal(tempValue.index, valueExpr) - ), - module.createGetLocal(tempValue.index, nativeType) - ], nativeType); - } else { - this.currentType = Type.void; - return this.module.createSetGlobal(global.internalName, valueExpr); + valueExpr = this.ensureSmallIntegerWrap(valueExpr, type); // globals must be wrapped + if (tee) { // (global = (t1 = value)), t1 + let tempValue = this.currentFlow.getAndFreeTempLocal(type, true); + this.currentType = type; + return module.createBlock(null, [ + module.createSetGlobal(global.internalName, + module.createTeeLocal(tempValue.index, valueExpr) + ), + module.createGetLocal(tempValue.index, nativeType) + ], nativeType); + } else { // global = value + this.currentType = Type.void; + return module.createSetGlobal(global.internalName, + valueExpr + ); + } } } + /** Makes an assignment to a field, possibly retaining and releasing affected references. */ makeFieldAssignment(field: Field, valueExpr: ExpressionRef, thisExpr: ExpressionRef, tee: bool): ExpressionRef { - var program = this.program; var module = this.module; + var program = this.program; var flow = this.currentFlow; var fieldType = field.type; var nativeFieldType = fieldType.toNativeType(); @@ -5306,69 +5319,63 @@ export class Compiler extends DiagnosticEmitter { var thisType = (field.parent).type; var nativeThisType = thisType.toNativeType(); - // MANAGED: this.field = replace(value, this.field) - if (fieldType.isManaged(program)) { + if (fieldType.isManaged(program) && thisType.isManaged(program)) { let tempThis = flow.getTempLocal(thisType, false); - let expr: ExpressionRef; - if (tee) { // tee value to a temp local and make it the block's result - let tempValue = flow.getTempLocal(fieldType, !flow.canOverflow(valueExpr, fieldType)); - expr = module.createBlock(null, [ + let retainReleaseInstance = program.retainReleaseInstance; + this.compileFunction(retainReleaseInstance); + if (tee) { // ((t1 = this).field = __retainRelease(t2 = value, t1.field)), t2 + let tempValue = flow.getAndFreeTempLocal(fieldType, !flow.canOverflow(valueExpr, fieldType)); + flow.freeTempLocal(tempThis); + this.currentType = fieldType; + return module.createBlock(null, [ module.createStore(fieldType.byteSize, module.createTeeLocal(tempThis.index, thisExpr), - this.makeReplaceRef( - module.createTeeLocal(tempValue.index, valueExpr), - module.createLoad(fieldType.byteSize, fieldType.is(TypeFlags.SIGNED), + module.createCall(retainReleaseInstance.internalName, [ + module.createTeeLocal(tempValue.index, valueExpr), // newRef + module.createLoad(fieldType.byteSize, fieldType.is(TypeFlags.SIGNED), // oldRef module.createGetLocal(tempThis.index, nativeThisType), nativeFieldType, field.memoryOffset - ), - tempThis, - fieldType.is(TypeFlags.NULLABLE) - ), + ) + ], nativeFieldType), nativeFieldType, field.memoryOffset ), module.createGetLocal(tempValue.index, nativeFieldType) ], nativeFieldType); - flow.freeTempLocal(tempValue); - this.currentType = fieldType; - } else { // no need for a temp local - expr = module.createStore(fieldType.byteSize, + } else { // (t1 = this).field = __retainRelease(value, t1.field) + flow.freeTempLocal(tempThis); + this.currentType = Type.void; + return module.createStore(fieldType.byteSize, module.createTeeLocal(tempThis.index, thisExpr), - this.makeReplaceRef( - valueExpr, - module.createLoad(fieldType.byteSize, fieldType.is(TypeFlags.SIGNED), + module.createCall(retainReleaseInstance.internalName, [ + valueExpr, // newRef + module.createLoad(fieldType.byteSize, fieldType.is(TypeFlags.SIGNED), // oldRef module.createGetLocal(tempThis.index, nativeThisType), nativeFieldType, field.memoryOffset - ), - tempThis, - fieldType.is(TypeFlags.NULLABLE) - ), + ) + ], nativeFieldType), nativeFieldType, field.memoryOffset ); - this.currentType = Type.void; } - flow.freeTempLocal(tempThis); - return expr; - } - - // UNMANAGED: this.field = value - if (tee) { - this.currentType = fieldType; - let tempValue = flow.getAndFreeTempLocal(fieldType, !flow.canOverflow(valueExpr, fieldType)); - return module.createBlock(null, [ - module.createStore(fieldType.byteSize, - thisExpr, - module.createTeeLocal(tempValue.index, valueExpr), - nativeFieldType, field.memoryOffset - ), - module.createGetLocal(tempValue.index, nativeFieldType) - ], nativeFieldType); } else { - this.currentType = Type.void; - return module.createStore(fieldType.byteSize, - thisExpr, - valueExpr, - nativeFieldType, field.memoryOffset - ); + if (tee) { // (this.field = (t1 = value)), t1 + let tempValue = flow.getAndFreeTempLocal(fieldType, !flow.canOverflow(valueExpr, fieldType)); + this.currentType = fieldType; + return module.createBlock(null, [ + module.createStore(fieldType.byteSize, + thisExpr, + module.createTeeLocal(tempValue.index, valueExpr), + nativeFieldType, field.memoryOffset + ), + module.createGetLocal(tempValue.index, nativeFieldType) + ], nativeFieldType); + } else { // this.field = value + this.currentType = Type.void; + return module.createStore(fieldType.byteSize, + thisExpr, + valueExpr, + nativeFieldType, field.memoryOffset + ) + } } } @@ -7015,7 +7022,7 @@ export class Compiler extends DiagnosticEmitter { // otherwise allocate a new array header and make it wrap a copy of the static buffer } else { // makeArray(length, alignLog2, classId, staticBuffer) - let expr = this.makeCallDirect(assert(program.makeArrayInstance), [ + let expr = this.makeCallDirect(program.allocArrayInstance, [ module.createI32(length), program.options.isWasm64 ? module.createI64(elementType.alignLog2) @@ -7044,12 +7051,11 @@ export class Compiler extends DiagnosticEmitter { var flow = this.currentFlow; var tempThis = flow.getTempLocal(arrayType, false); var tempDataStart = flow.getTempLocal(arrayBufferInstance.type); - var newArrayInstance = assert(program.makeArrayInstance); var stmts = new Array(); // tempThis = makeArray(length, alignLog2, classId, source = 0) stmts.push( module.createSetLocal(tempThis.index, - this.makeCallDirect(newArrayInstance, [ + this.makeCallDirect(program.allocArrayInstance, [ module.createI32(length), program.options.isWasm64 ? module.createI64(elementType.alignLog2) @@ -7080,12 +7086,10 @@ export class Compiler extends DiagnosticEmitter { ? this.compileExpression(valueExpression, elementType, ConversionKind.IMPLICIT, WrapMode.NONE) : elementType.toNativeZero(module); if (isManaged) { - // value = link/retain(value[, tempThis]) - valueExpr = this.makeInsertRef( - valueExpr, - tempThis, - elementType.is(TypeFlags.NULLABLE) - ); + // value = __retain(value) + valueExpr = this.makeCallDirect(program.retainInstance, [ + valueExpr + ], reportNode); } // store(tempData, value, immOffset) stmts.push( @@ -8290,35 +8294,17 @@ export class Compiler extends DiagnosticEmitter { var module = this.module; var options = this.options; var classType = classInstance.type; - - if (!program.allocateMem) { - this.error( - DiagnosticCode.An_allocator_must_be_present_to_use_0, - reportNode.range, "new" - ); - } - - if (classInstance.hasDecorator(DecoratorFlags.UNMANAGED)) { - // memory.allocate(sizeof()) - this.currentType = classType; - return this.makeCallDirect(assert(program.memoryAllocateInstance), [ - options.isWasm64 - ? module.createI64(classInstance.currentMemoryOffset) - : module.createI32(classInstance.currentMemoryOffset) - ], reportNode); - - } else { - // register(allocate(sizeof()), classId) - this.currentType = classType; - return this.makeCallDirect(assert(program.registerInstance), [ - this.makeCallDirect(assert(program.allocateInstance), [ - options.isWasm64 - ? module.createI64(classInstance.currentMemoryOffset) - : module.createI32(classInstance.currentMemoryOffset) - ], reportNode), - module.createI32(classInstance.id) - ], reportNode); - } + this.currentType = classType; + return this.makeCallDirect(program.allocInstance, [ + options.isWasm64 + ? module.createI64(classInstance.currentMemoryOffset) + : module.createI32(classInstance.currentMemoryOffset), + module.createI32( + classInstance.hasDecorator(DecoratorFlags.UNMANAGED) + ? 0 + : classInstance.id + ) + ], reportNode); } /** Makes the initializers for a class's fields. */ @@ -8383,182 +8369,206 @@ export class Compiler extends DiagnosticEmitter { return stmts; } - /** Wraps a reference in a `retain` call. Returns the reference if `tempLocal` is specified. */ - makeRetain(valueExpr: ExpressionRef, tempIndex: i32 = -1): ExpressionRef { - var module = this.module; - var program = this.program; - var retainFn = assert(program.retainRef); - this.compileFunction(retainFn); - if (tempIndex >= 0) { - let nativeSizeType = this.options.nativeSizeType; - return module.createBlock(null, [ - module.createCall(retainFn.internalName, [ - module.createTeeLocal(tempIndex, valueExpr) - ], NativeType.None), - module.createGetLocal(tempIndex, nativeSizeType) - ], nativeSizeType); - } else { - return module.createCall(retainFn.internalName, [ valueExpr ], NativeType.None); - } - } + // private makeRetainOrRelease(fn: Function, expr: ExpressionRef, possiblyNull: bool, tee: bool, tempIndex: i32 = -1): ExpressionRef { + // var module = this.module; + // var nativeSizeType = this.options.nativeSizeType; + // if (tee) { + // if (possiblyNull) { + // assert(tempIndex >= 0); + // return module.createBlock(null, [ + // module.createIf( + // module.createTeeLocal(tempIndex, expr), + // module.createCall(fn.internalName, [ + // module.createGetLocal(tempIndex, nativeSizeType) + // ], NativeType.None) + // ), + // module.createGetLocal(tempIndex, nativeSizeType) + // ], nativeSizeType); + // } else { + // assert(tempIndex >= 0); + // return module.createBlock(null, [ + // module.createCall(fn.internalName, [ + // module.createTeeLocal(tempIndex, expr) + // ], NativeType.None), + // module.createGetLocal(tempIndex, nativeSizeType) + // ], nativeSizeType); + // } + // } else { + // if (possiblyNull) { + // assert(tempIndex >= 0); + // return module.createIf( + // module.createTeeLocal(tempIndex, expr), + // module.createCall(fn.internalName, [ + // module.createGetLocal(tempIndex, nativeSizeType) + // ], NativeType.None) + // ); + // } else { + // return module.createCall(fn.internalName, [ expr ], NativeType.None); + // } + // } + // } - /** Wraps a reference in `release` call. Returns the reference if `tempLocal` is specified. */ - makeRelease(valueExpr: ExpressionRef, tempIndex: i32 = -1): ExpressionRef { - var module = this.module; - var program = this.program; - var releaseFn = assert(program.releaseRef); - this.compileFunction(releaseFn); - if (tempIndex >= 0) { - let nativeSizeType = this.options.nativeSizeType; - return module.createBlock(null, [ - module.createCall(releaseFn.internalName, [ - module.createTeeLocal(tempIndex, valueExpr) - ], NativeType.None), - module.createGetLocal(tempIndex, nativeSizeType) - ], nativeSizeType); - } else { - return module.createCall(releaseFn.internalName, [ valueExpr ], NativeType.None); - } - } + // /** Wraps an expression of a reference type in a `retain` call. */ + // makeRetain(expr: ExpressionRef, possiblyNull: bool, tee: bool, tempIndex: i32 = -1): ExpressionRef { + // return this.makeRetainOrRelease(this.program.retainInstance, expr, possiblyNull, tee, tempIndex); + // } - /** Wraps a new and an old reference in a sequence of `retain` and `release` calls. */ - makeRetainRelease(newValueExpr: ExpressionRef, oldValueExpr: ExpressionRef, tempIndex: i32 = -1): ExpressionRef { - // TODO: checking `newValue != oldValue` significantly reduces strain on the roots buffer - // when cyclic structures may be immediately released but also requires a tempIndex. might - // be worth to require a temp here. furthermore it might be worth to require it for retain - // and release as well so we can emit != null checks where necessary only? - var module = this.module; - if (tempIndex >= 0) { - let nativeSizeType = this.options.nativeSizeType; - return module.createBlock(null, [ - this.makeRetain(module.createTeeLocal(tempIndex, newValueExpr)), - this.makeRelease(oldValueExpr), - module.createGetLocal(tempIndex, nativeSizeType) - ], nativeSizeType); - } else { - return module.createBlock(null, [ - this.makeRetain(newValueExpr), - this.makeRelease(oldValueExpr) - ]); - } - } + // /** Wraps an expression of a reference type in a `release` call. */ + // makeRelease(expr: ExpressionRef, possiblyNull: bool, tee: bool, tempIndex: i32 = -1): ExpressionRef { + // return this.makeRetainOrRelease(this.program.releaseInstance, expr, possiblyNull, tee, tempIndex); + // } - /** Prepares the insertion of a reference into an _uninitialized_ parent using the GC interface. */ - makeInsertRef( - valueExpr: ExpressionRef, - tempParent: Local | null, - nullable: bool - ): ExpressionRef { - var module = this.module; - var program = this.program; - var usizeType = this.options.usizeType; - var nativeSizeType = this.options.nativeSizeType; - var flow = this.currentFlow; - var tempValue = flow.getTempLocal(usizeType, false); - var handle: ExpressionRef; - var fn: Function | null; - if (fn = program.linkRef) { // tracing - handle = module.createCall(fn.internalName, [ - module.createGetLocal(tempValue.index, nativeSizeType), - module.createGetLocal(assert(tempParent).index, nativeSizeType) - ], NativeType.None); - } else if (fn = program.retainRef) { // arc - handle = module.createCall(fn.internalName, [ - module.createGetLocal(tempValue.index, nativeSizeType) - ], NativeType.None); - } else { - assert(false); - return module.createUnreachable(); - } - flow.freeTempLocal(tempValue); - if (!this.compileFunction(fn)) return module.createUnreachable(); - // { - // [if (value !== null)] link/retain(value[, parent]) - // } -> value - return module.createBlock(null, [ - module.createSetLocal(tempValue.index, valueExpr), - nullable - ? module.createIf( - module.createGetLocal(tempValue.index, nativeSizeType), - handle - ) - : handle, - module.createGetLocal(tempValue.index, nativeSizeType) - ], nativeSizeType); - } + // /** Wraps a new and an old expression of a reference type in a `retain` call for the new and a `release` call for the old expression. */ + // makeRetainRelease(newExpr: ExpressionRef, oldExpr: ExpressionRef, possiblyNull: bool, tempIndexNew: i32, tempIndexOld: i32): ExpressionRef { + // var module = this.module; + // var nativeSizeType = this.options.nativeSizeType; + // return module.createIf( + // module.createBinary( + // nativeSizeType == NativeType.I32 + // ? BinaryOp.NeI32 + // : BinaryOp.NeI64, + // module.createTeeLocal(tempIndexNew, newExpr), + // module.createTeeLocal(tempIndexOld, oldExpr) + // ), + // module.createBlock(null, [ + // this.makeRetain(module.createGetLocal(tempIndexNew, nativeSizeType), possiblyNull, false, tempIndexNew), - /** Prepares the replaces a reference hold by an _initialized_ parent using the GC interface. */ - makeReplaceRef( - valueExpr: ExpressionRef, - oldValueExpr: ExpressionRef, - tempParent: Local | null, - nullable: bool - ): ExpressionRef { - var module = this.module; - var program = this.program; - var usizeType = this.options.usizeType; - var nativeSizeType = this.options.nativeSizeType; - var flow = this.currentFlow; - var tempValue = flow.getTempLocal(usizeType, false); - var tempOldValue = flow.getTempLocal(usizeType, false); - var handleOld: ExpressionRef = 0; - var handleNew: ExpressionRef; - var fn1: Function | null, fn2: Function | null; - if (fn1 = program.linkRef) { // tracing - tempParent = assert(tempParent); - if (fn2 = program.unlinkRef) { - handleOld = module.createCall(fn2.internalName, [ - module.createGetLocal(tempOldValue.index, nativeSizeType), - module.createGetLocal(tempParent.index, nativeSizeType) - ], NativeType.None); - } - handleNew = module.createCall(fn1.internalName, [ - module.createGetLocal(tempValue.index, nativeSizeType), - module.createGetLocal(tempParent.index, nativeSizeType) - ], NativeType.None); - } else if (fn1 = program.retainRef) { // arc - fn2 = assert(program.releaseRef); - handleOld = module.createCall(fn2.internalName, [ - module.createGetLocal(tempOldValue.index, nativeSizeType) - ], NativeType.None); - handleNew = module.createCall(fn1.internalName, [ - module.createGetLocal(tempValue.index, nativeSizeType) - ], NativeType.None); - } else { - assert(false); - return module.createUnreachable(); - } - flow.freeTempLocal(tempValue); - flow.freeTempLocal(tempOldValue); - if (!this.compileFunction(fn1)) return module.createUnreachable(); - if (fn2 && !this.compileFunction(fn2)) return module.createUnreachable(); - // if (value != oldValue) { - // if (oldValue !== null) unlink/release(oldValue[, parent]) - // [if (value !== null)] link/retain(value[, parent]) - // } -> value - return module.createIf( - module.createBinary(nativeSizeType == NativeType.I32 ? BinaryOp.NeI32 : BinaryOp.NeI64, - module.createTeeLocal(tempValue.index, valueExpr), - module.createTeeLocal(tempOldValue.index, oldValueExpr) - ), - module.createBlock(null, [ - handleOld - ? module.createIf( - module.createGetLocal(tempOldValue.index, nativeSizeType), - handleOld - ) - : module.createNop(), - nullable - ? module.createIf( - module.createGetLocal(tempValue.index, nativeSizeType), - handleNew - ) - : handleNew, - module.createGetLocal(tempValue.index, nativeSizeType) - ], nativeSizeType), - module.createGetLocal(tempValue.index, nativeSizeType) - ); - } + // ], NativeType.None) + // ) + // return module.createBlock(null, [ + // this.makeRetain(newExpr, possiblyNull, true, tempIndex), + // this.makeRelease(oldExpr, possiblyNull, false), // wrong: reuses tempIndex if possiblyNull + + // ], nativeSizeType); + // } + + // /** Wraps a new and an old reference in a sequence of `retain` and `release` calls. */ + // makeRetainRelease(newValueExpr: ExpressionRef, oldValueExpr: ExpressionRef, tempIndex: i32, possiblyNull: bool = true): ExpressionRef { + // var module = this.module; + // var nativeSizeType = this.options.nativeSizeType; + // return module.createBlock(null, [ + // this.makeRetain(module.createTeeLocal(tempIndex, newValueExpr), possiblyNull ? tempIndex : -1), + // this.makeRelease(oldValueExpr, possiblyNull ? tempIndex : -1), + // module.createGetLocal(tempIndex, nativeSizeType) + // ], nativeSizeType); + // } + + // /** Prepares the insertion of a reference into an _uninitialized_ parent using the GC interface. */ + // makeInsertRef( + // valueExpr: ExpressionRef, + // tempParent: Local | null, + // nullable: bool + // ): ExpressionRef { + // var module = this.module; + // var program = this.program; + // var usizeType = this.options.usizeType; + // var nativeSizeType = this.options.nativeSizeType; + // var flow = this.currentFlow; + // var tempValue = flow.getTempLocal(usizeType, false); + // var handle: ExpressionRef; + // var fn: Function | null; + // if (fn = program.linkRef) { // tracing + // handle = module.createCall(fn.internalName, [ + // module.createGetLocal(tempValue.index, nativeSizeType), + // module.createGetLocal(assert(tempParent).index, nativeSizeType) + // ], NativeType.None); + // } else if (fn = program.retainRef) { // arc + // handle = module.createCall(fn.internalName, [ + // module.createGetLocal(tempValue.index, nativeSizeType) + // ], NativeType.None); + // } else { + // assert(false); + // return module.createUnreachable(); + // } + // flow.freeTempLocal(tempValue); + // if (!this.compileFunction(fn)) return module.createUnreachable(); + // // { + // // [if (value !== null)] link/retain(value[, parent]) + // // } -> value + // return module.createBlock(null, [ + // module.createSetLocal(tempValue.index, valueExpr), + // nullable + // ? module.createIf( + // module.createGetLocal(tempValue.index, nativeSizeType), + // handle + // ) + // : handle, + // module.createGetLocal(tempValue.index, nativeSizeType) + // ], nativeSizeType); + // } + + // /** Prepares the replaces a reference hold by an _initialized_ parent using the GC interface. */ + // makeReplaceRef( + // valueExpr: ExpressionRef, + // oldValueExpr: ExpressionRef, + // tempParent: Local | null, + // nullable: bool + // ): ExpressionRef { + // var module = this.module; + // var program = this.program; + // var usizeType = this.options.usizeType; + // var nativeSizeType = this.options.nativeSizeType; + // var flow = this.currentFlow; + // var tempValue = flow.getTempLocal(usizeType, false); + // var tempOldValue = flow.getTempLocal(usizeType, false); + // var handleOld: ExpressionRef = 0; + // var handleNew: ExpressionRef; + // var fn1: Function | null, fn2: Function | null; + // if (fn1 = program.linkRef) { // tracing + // tempParent = assert(tempParent); + // if (fn2 = program.unlinkRef) { + // handleOld = module.createCall(fn2.internalName, [ + // module.createGetLocal(tempOldValue.index, nativeSizeType), + // module.createGetLocal(tempParent.index, nativeSizeType) + // ], NativeType.None); + // } + // handleNew = module.createCall(fn1.internalName, [ + // module.createGetLocal(tempValue.index, nativeSizeType), + // module.createGetLocal(tempParent.index, nativeSizeType) + // ], NativeType.None); + // } else if (fn1 = program.retainRef) { // arc + // fn2 = assert(program.releaseRef); + // handleOld = module.createCall(fn2.internalName, [ + // module.createGetLocal(tempOldValue.index, nativeSizeType) + // ], NativeType.None); + // handleNew = module.createCall(fn1.internalName, [ + // module.createGetLocal(tempValue.index, nativeSizeType) + // ], NativeType.None); + // } else { + // assert(false); + // return module.createUnreachable(); + // } + // flow.freeTempLocal(tempValue); + // flow.freeTempLocal(tempOldValue); + // if (!this.compileFunction(fn1)) return module.createUnreachable(); + // if (fn2 && !this.compileFunction(fn2)) return module.createUnreachable(); + // // if (value != oldValue) { + // // if (oldValue !== null) unlink/release(oldValue[, parent]) + // // [if (value !== null)] link/retain(value[, parent]) + // // } -> value + // return module.createIf( + // module.createBinary(nativeSizeType == NativeType.I32 ? BinaryOp.NeI32 : BinaryOp.NeI64, + // module.createTeeLocal(tempValue.index, valueExpr), + // module.createTeeLocal(tempOldValue.index, oldValueExpr) + // ), + // module.createBlock(null, [ + // handleOld + // ? module.createIf( + // module.createGetLocal(tempOldValue.index, nativeSizeType), + // handleOld + // ) + // : module.createNop(), + // nullable + // ? module.createIf( + // module.createGetLocal(tempValue.index, nativeSizeType), + // handleNew + // ) + // : handleNew, + // module.createGetLocal(tempValue.index, nativeSizeType) + // ], nativeSizeType), + // module.createGetLocal(tempValue.index, nativeSizeType) + // ); + // } makeInstanceOfClass( expr: ExpressionRef, diff --git a/src/program.ts b/src/program.ts index 4a4e90a4..ec76d48a 100644 --- a/src/program.ts +++ b/src/program.ts @@ -349,58 +349,49 @@ export class Program extends DiagnosticEmitter { /** Managed classes contained in the program, by id. */ managedClasses: Map = new Map(); - // runtime references + // standard references /** ArrayBufferView reference. */ - arrayBufferViewInstance: Class | null = null; + arrayBufferViewInstance: Class; /** ArrayBuffer instance reference. */ - arrayBufferInstance: Class | null = null; + arrayBufferInstance: Class; /** Array prototype reference. */ - arrayPrototype: ClassPrototype | null = null; + arrayPrototype: ClassPrototype; /** Set prototype reference. */ - setPrototype: ClassPrototype | null = null; + setPrototype: ClassPrototype; /** Map prototype reference. */ - mapPrototype: ClassPrototype | null = null; + mapPrototype: ClassPrototype; /** Fixed array prototype reference. */ - fixedArrayPrototype: ClassPrototype | null = null; + fixedArrayPrototype: ClassPrototype; /** String instance reference. */ - stringInstance: Class | null = null; + stringInstance: Class; /** Abort function reference, if present. */ - abortInstance: Function | null = null; + abortInstance: Function; - /** Runtime allocation function. `allocate(payloadSize: usize): usize` */ - allocateInstance: Function | null = null; - /** Memory allocation function. `memory.allocate(size)` */ - memoryAllocateInstance: Function | null = null; - /** Runtime reallocation function. `reallocate(ref: usize, newPayloadSize: usize): usize` */ - reallocateInstance: Function | null = null; - /** Runtime discard function. `discard(ref: usize): void` */ - discardInstance: Function | null = null; - /** Runtime register function. `register(ref: usize, cid: u32): usize` */ - registerInstance: Function | null = null; - /** Runtime make array function. `newArray(length: i32, alignLog2: usize, id: u32, source: usize = 0): usize` */ - makeArrayInstance: Function | null = null; - /** Runtime instanceof function. */ - instanceofInstance: Function | null = null; - /** Runtime flags function. */ - flagsInstance: Function | null = null; + // runtime references - /** The kind of garbage collector being present. */ - collectorKind: CollectorKind = CollectorKind.NONE; - /** Memory allocation implementation, if present: `__mem_allocate(size: usize): usize` */ - allocateMem: Function | null = null; - /** Memory free implementation, if present: `__mem_free(ref: usize): void` */ - freeMem: Function | null = null; - /** Reference link implementation, if present: `__ref_link(ref: usize, parentRef: usize): void` */ - linkRef: Function | null = null; - /** Reference unlink implementation, if present: `__ref_unlink(ref: usize, parentRef: usize): void` */ - unlinkRef: Function | null = null; - /** Reference retain implementation, if present: `__ref_retain(ref: usize): void` */ - retainRef: Function | null = null; - /** Reference release implementation, if present: `__ref_release(ref: usize): void` */ - releaseRef: Function | null = null; - /** Reference mark implementation, if present: `__ref_mark(ref: usize): void` */ - markRef: Function | null = null; + /** RT `__alloc(size: usize, id: u32): usize` */ + allocInstance: Function; + /** RT `__realloc(ref: usize, newSize: usize): usize` */ + reallocInstance: Function; + /** RT `__free(ref: usize): void` */ + freeInstance: Function; + /** RT `__retain(ref: usize): usize` */ + retainInstance: Function; + /** RT `__release(ref: usize): void` */ + releaseInstance: Function; + /** RT `__retainRelease(newRef: usize, oldRef: usize): usize` */ + retainReleaseInstance: Function; + /** RT `__collect(): void` */ + collectInstance: Function; + /** RT `__visit(ref: usize, cookie: u32): void` */ + visitInstance: Function; + /** RT `__typeinfo(id: u32): RTTIFlags` */ + typeinfoInstance: Function; + /** RT `__instanceof(ref: usize, superId: u32): bool` */ + instanceofInstance: Function; + /** RT `__allocArray(length: i32, alignLog2: usize, id: u32, data: usize = 0): usize` */ + allocArrayInstance: Function; /** Next class id. */ nextClassId: u32 = 1; @@ -805,104 +796,26 @@ export class Program extends DiagnosticEmitter { } } - // register library elements - { - let element: Element | null; - if (element = this.lookupGlobal(CommonSymbols.ArrayBufferView)) { - assert(element.kind == ElementKind.CLASS_PROTOTYPE); - this.arrayBufferViewInstance = resolver.resolveClass(element, null); - } - if (element = this.lookupGlobal(CommonSymbols.ArrayBuffer)) { - assert(element.kind == ElementKind.CLASS_PROTOTYPE); - this.arrayBufferInstance = resolver.resolveClass(element, null); - } - if (element = this.lookupGlobal(CommonSymbols.String)) { - assert(element.kind == ElementKind.CLASS_PROTOTYPE); - this.stringInstance = resolver.resolveClass(element, null); - } - if (element = this.lookupGlobal(CommonSymbols.Array)) { - assert(element.kind == ElementKind.CLASS_PROTOTYPE); - this.arrayPrototype = element; - } - if (element = this.lookupGlobal(CommonSymbols.FixedArray)) { - assert(element.kind == ElementKind.CLASS_PROTOTYPE); - this.fixedArrayPrototype = element; - } - if (element = this.lookupGlobal(CommonSymbols.Set)) { - assert(element.kind == ElementKind.CLASS_PROTOTYPE); - this.setPrototype = element; - } - if (element = this.lookupGlobal(CommonSymbols.Map)) { - assert(element.kind == ElementKind.CLASS_PROTOTYPE); - this.mapPrototype = element; - } - if (element = this.lookupGlobal(CommonSymbols.abort)) { - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - this.abortInstance = this.resolver.resolveFunction(element, null); - } - if (element = this.lookupGlobal(BuiltinSymbols.runtime_allocate)) { - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - this.allocateInstance = this.resolver.resolveFunction(element, null); - } - if (element = this.lookupGlobal(BuiltinSymbols.memory_allocate)) { - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - this.memoryAllocateInstance = this.resolver.resolveFunction(element, null); - } - if (element = this.lookupGlobal(BuiltinSymbols.runtime_reallocate)) { - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - this.reallocateInstance = this.resolver.resolveFunction(element, null); - } - if (element = this.lookupGlobal(BuiltinSymbols.runtime_discard)) { - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - this.discardInstance = this.resolver.resolveFunction(element, null); - } - if (element = this.lookupGlobal(BuiltinSymbols.runtime_register)) { - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - this.registerInstance = this.resolver.resolveFunction(element, null); - } - if (element = this.lookupGlobal(BuiltinSymbols.runtime_makeArray)) { - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - this.makeArrayInstance = this.resolver.resolveFunction(element, null); - } - if (element = this.lookupGlobal(BuiltinSymbols.runtime_instanceof)) { - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - this.instanceofInstance = this.resolver.resolveFunction(element, null); - } - if (element = this.lookupGlobal(BuiltinSymbols.runtime_flags)) { - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - this.flagsInstance = this.resolver.resolveFunction(element, null); - } - // memory allocator interface - if (element = this.lookupGlobal("__mem_allocate")) { - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - this.allocateMem = this.resolver.resolveFunction(element, null); - element = assert(this.lookupGlobal("__mem_free")); - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - this.freeMem = this.resolver.resolveFunction(element, null); - } - // garbage collector interface - if (this.lookupGlobal("__ref_collect")) { - if (element = this.lookupGlobal("__ref_link")) { - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - this.linkRef = this.resolver.resolveFunction(element, null); - if (element = this.lookupGlobal("__ref_unlink")) { - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - this.unlinkRef = this.resolver.resolveFunction(element, null); - } - element = assert(this.lookupGlobal("__ref_mark")); - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - this.markRef = this.resolver.resolveFunction(element, null); - this.collectorKind = CollectorKind.TRACING; - } else if (element = this.lookupGlobal("__ref_retain")) { - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - this.retainRef = this.resolver.resolveFunction(element, null); - element = assert(this.lookupGlobal("__ref_release")); - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - this.releaseRef = this.resolver.resolveFunction(element, null); - this.collectorKind = CollectorKind.COUNTING; - } - } - } + // register stdlib components + this.arrayBufferViewInstance = this.requireClass(CommonSymbols.ArrayBufferView); + this.arrayBufferInstance = this.requireClass(CommonSymbols.ArrayBuffer); + this.stringInstance = this.requireClass(CommonSymbols.String); + this.arrayPrototype = this.require(CommonSymbols.Array, ElementKind.CLASS_PROTOTYPE); + this.fixedArrayPrototype = this.require(CommonSymbols.FixedArray, ElementKind.CLASS_PROTOTYPE); + this.setPrototype = this.require(CommonSymbols.Set, ElementKind.CLASS_PROTOTYPE); + this.mapPrototype = this.require(CommonSymbols.Map, ElementKind.CLASS_PROTOTYPE); + this.abortInstance = this.requireFunction(CommonSymbols.abort); + this.allocInstance = this.requireFunction(CommonSymbols.alloc); + this.reallocInstance = this.requireFunction(CommonSymbols.realloc); + this.freeInstance = this.requireFunction(CommonSymbols.free); + this.retainInstance = this.requireFunction(CommonSymbols.retain); + this.releaseInstance = this.requireFunction(CommonSymbols.release); + this.retainReleaseInstance = this.requireFunction(CommonSymbols.retainRelease); + this.collectInstance = this.requireFunction(CommonSymbols.collect); + this.typeinfoInstance = this.requireFunction(CommonSymbols.typeinfo); + this.instanceofInstance = this.requireFunction(CommonSymbols.instanceof_); + this.visitInstance = this.requireFunction(CommonSymbols.visit); + this.allocArrayInstance = this.requireFunction(CommonSymbols.allocArray); // mark module exports, i.e. to apply proper wrapping behavior on the boundaries for (let file of this.filesByName.values()) { @@ -912,6 +825,30 @@ export class Program extends DiagnosticEmitter { } } + /** Requires that a global library element of the specified kind is present and returns it. */ + private require(name: string, kind: ElementKind): Element { + var element = this.lookupGlobal(name); + if (!element) throw new Error("missing " + name); + if (element.kind != kind) throw new Error("unexpected " + name); + return element; + } + + /** Requires that a non-generic global class is present and returns it. */ + private requireClass(name: string): Class { + var prototype = this.require(name, ElementKind.CLASS_PROTOTYPE); + var resolved = this.resolver.resolveClass(prototype, null); + if (!resolved) throw new Error("invalid " + name); + return resolved; + } + + /** Requires that a non-generic global function is present and returns it. */ + private requireFunction(name: string): Function { + var prototype = this.require(name, ElementKind.FUNCTION_PROTOTYPE); + var resolved = this.resolver.resolveFunction(prototype, null); + if (!resolved) throw new Error("invalid " + name); + return resolved; + } + /** Marks an element and its children as a module export. */ private markModuleExport(element: Element): void { element.set(CommonFlags.MODULE_EXPORT); diff --git a/src/types.ts b/src/types.ts index 2276f22a..eb126207 100644 --- a/src/types.ts +++ b/src/types.ts @@ -153,11 +153,8 @@ export class Type { /** Tests if this is a managed type that needs GC hooks. */ isManaged(program: Program): bool { - if (program.collectorKind != CollectorKind.NONE) { - let classReference = this.classReference; - return classReference !== null && !classReference.hasDecorator(DecoratorFlags.UNMANAGED); - } - return false; + var classReference = this.classReference; + return classReference !== null && !classReference.hasDecorator(DecoratorFlags.UNMANAGED); } /** Tests if this is a class type explicitly annotated as unmanaged. */ diff --git a/std/assembly/allocator/README.md b/std/assembly/allocator/README.md deleted file mode 100644 index d020dece..00000000 --- a/std/assembly/allocator/README.md +++ /dev/null @@ -1,21 +0,0 @@ -Memory manager interface -======================== - -A memory manager for AssemblyScript must implement the following common and may implement any number of optional interfaces: - -Common ------- - -* **__mem_allocate**(size: `usize`): `usize`
- Dynamically allocates a chunk of memory of at least the specified size and returns its address. - Alignment must be guaranteed to be at least 8 bytes, but there are considerations to increase - alignment to 16 bytes to fit SIMD v128 values. - -* **__mem_free**(ref: `usize`): `void`
- Frees a dynamically allocated chunk of memory by its address. - -Optional --------- - -* **__mem_reset**(ref: `usize`, parentRef: `usize`): `void`
- Resets dynamic memory to its initial state. Used by the arena allocator. diff --git a/std/assembly/allocator/arena.ts b/std/assembly/allocator/arena.ts deleted file mode 100644 index 759eba2e..00000000 --- a/std/assembly/allocator/arena.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { HEAP_BASE, memory } from "../memory"; -import { AL_MASK, MAX_SIZE_32 } from "../util/allocator"; - -// @ts-ignore: decorator -@lazy -var startOffset: usize = (HEAP_BASE + AL_MASK) & ~AL_MASK; - -// @ts-ignore: decorator -@lazy -var offset: usize = startOffset; - -// @ts-ignore: decorator -@unsafe @global -function __mem_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; -} - -// @ts-ignore: decorator -@unsafe @global -function __mem_free(ptr: usize): void { -} - -// @ts-ignore: decorator -@unsafe @global -function __mem_reset(): void { - offset = startOffset; -} diff --git a/std/assembly/allocator/emscripten.ts b/std/assembly/allocator/emscripten.ts deleted file mode 100644 index c0a68517..00000000 --- a/std/assembly/allocator/emscripten.ts +++ /dev/null @@ -1,19 +0,0 @@ -// @ts-ignore: decorator -@unsafe -declare function _malloc(size: usize): usize; - -// @ts-ignore: decorator -@unsafe -declare function _free(ptr: usize): void; - -// @ts-ignore: decorator -@unsafe @global -function __mem_allocate(size: usize): usize { - return _malloc(size); -} - -// @ts-ignore: decorator -@unsafe @global -function __mem_free(ptr: usize): void { - _free(ptr); -} diff --git a/std/assembly/allocator/index.d.ts b/std/assembly/allocator/index.d.ts deleted file mode 100644 index 73d569fb..00000000 --- a/std/assembly/allocator/index.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare function __mem_allocate(size: usize): usize; -declare function __mem_free(ref: usize): void; -declare function __mem_reset(): void; diff --git a/std/assembly/allocator/system.ts b/std/assembly/allocator/system.ts deleted file mode 100644 index ceeee580..00000000 --- a/std/assembly/allocator/system.ts +++ /dev/null @@ -1,19 +0,0 @@ -// @ts-ignore: decorator -@unsafe -declare function malloc(size: usize): usize; - -// @ts-ignore: decorator -@unsafe -declare function free(ptr: usize): void; - -// @ts-ignore: decorator -@unsafe @global -function __mem_allocate(size: usize): usize { - return malloc(size); -} - -// @ts-ignore: decorator -@unsafe @global -function __mem_free(ptr: usize): void { - free(ptr); -} diff --git a/std/assembly/allocator/tlsf.ts b/std/assembly/allocator/tlsf.ts deleted file mode 100644 index 9f30d605..00000000 --- a/std/assembly/allocator/tlsf.ts +++ /dev/null @@ -1,529 +0,0 @@ -// Two-Level Segregate Fit Memory Allocator. -// -// A general purpose dynamic memory allocator specifically designed to meet real-time requirements. -// Always aligns to 8 bytes. - -// ╒══════════════ 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 -// β”œβ”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”Όβ”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β•«β”€β”΄β”€β”΄β”€β”€ -// β”‚ | FL β”‚ SB = SL + AL β”‚ ◄─ usize -// β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β•¨β”€β”€β”€β”€β”€β”˜ -// 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"; - -// @ts-ignore: decorator -@inline -const SL_BITS: u32 = 5; - -// @ts-ignore: decorator -@inline -const SL_SIZE: usize = 1 << SL_BITS; - -// @ts-ignore: decorator -@inline -const SB_BITS: usize = (SL_BITS + AL_BITS); - -// @ts-ignore: decorator -@inline -const SB_SIZE: usize = 1 << SB_BITS; - -// @ts-ignore: decorator -@inline -const FL_BITS: u32 = (sizeof() == sizeof() - ? 30 // ^= up to 1GB per block - : 32 // ^= up to 4GB per block -) - SB_BITS; - -// ╒════════════════ Block structure layout (32-bit) ══════════════╕ -// 3 2 1 -// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits -// β”œβ”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”Όβ”€β”Όβ”€β”€ -// β”‚ size β”‚Lβ”‚Fβ”‚ ◄─┐ info -// β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•§β•β•§β•β•‘ β”‚ ┐ -// β”‚ if free: β—„ prev β”‚ ◄── usize -// β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ -// β”‚ if free: next β–Ί β”‚ ◄── -// β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ -// β”‚ ... unused free space >= 0 ... β”‚ β”‚ = 0 -// β”œ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ β”‚ -// β”‚ if free: jump β–² β”‚ β—„β”€β”˜ -// β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ MIN SIZE β”˜ -// F: FREE, L: LEFT_FREE - -/** Tag indicating that this block is free. */ -// @ts-ignore: decorator -@inline -const FREE: usize = 1 << 0; - -/** Tag indicating that this block's left block is free. */ -// @ts-ignore: decorator -@inline -const LEFT_FREE: usize = 1 << 1; - -/** Mask to obtain all tags. */ -// @ts-ignore: decorator -@inline -const TAGS: usize = FREE | LEFT_FREE; - -/** Block structure. */ -@unmanaged class Block { - - /** Info field holding this block's size and tags. */ - info: usize; - /** Class id. */ // TODO - // classId: u32; // - /** Size of the payload. */ // - // payloadSize: u32; // - /** Reference count. */ // - // refCount: u32; // - - /** Size of the always present header fields. User data starts here. */ - @inline - static readonly HEADER_SIZE: usize = (offsetof("prev") + AL_MASK) & ~AL_MASK; - - /** Previous free block, if any. Only valid if free. */ - prev: Block | null; - /** Next free block, if any. Only valid if free. */ - next: Block | null; - - /** Minimum size of a block, excluding {@link Block#info}. */ - @inline - static readonly MIN_SIZE: usize = (3 * sizeof() + AL_MASK) & ~AL_MASK;// prev + next + jump - - /** Maximum size of a used block, excluding {@link Block#info}. */ - @inline - static readonly MAX_SIZE: usize = 1 << (FL_BITS + SB_BITS); - - /** Gets this block's left (free) block in memory. */ - get left(): Block { - assert(this.info & LEFT_FREE); // left must be free or it doesn't contain 'jump' - return assert( - load(changetype(this) - sizeof()) - ); // can't be null - } - - /** Gets this block's right block in memory. */ - get right(): Block { - assert(this.info & ~TAGS); // can't skip beyond the tail block (the only valid empty block) - return assert( - changetype( - changetype(this) + Block.HEADER_SIZE + (this.info & ~TAGS) - ) - ); // can't be null - } -} - -// ╒════════════════ Root structure layout (32-bit) ═══════════════╕ -// 3 2 1 -// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits -// β”œβ”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”€ ┐ -// β”‚ 0 | flMap Sβ”‚ ◄────┐ -// β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•‘ β”‚ -// β”‚ slMap[0] S β”‚ ◄─┐ β”‚ -// β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ -// β”‚ slMap[1] β”‚ ◄── β”‚ -// β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ u32 β”‚ -// β”‚ ... β”‚ ◄── β”‚ -// β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ -// β”‚ slMap[22] P β”‚ β—„β”€β”˜ β”‚ -// β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•‘ usize -// β”‚ head[0] β”‚ ◄───── -// β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ -// β”‚ ... β”‚ ◄───── -// β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ -// β”‚ head[736] β”‚ ◄───── -// β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•‘ β”‚ -// β”‚ tailRef β”‚ β—„β”€β”€β”€β”€β”˜ -// β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ SIZE β”˜ -// S: Small blocks map, P: Possibly padded if 64-bit - -// assert((1 << SL_BITS) <= 32); // second level must fit into 32 bits - -/** Root structure. */ -@unmanaged class Root { - - /** First level bitmap. */ - flMap: usize = 0; - - /** Start offset of second level maps. */ - @inline - private static readonly SL_START: usize = sizeof(); - - // Using *one* SL map per *FL bit* - - /** Gets the second level map for the specified first level. */ - getSLMap(fl: usize): u32 { - assert(fl < FL_BITS); // fl out of range - return load(changetype(this) + fl * 4, Root.SL_START); - } - - /** Sets the second level map for the specified first level. */ - setSLMap(fl: usize, value: u32): void { - assert(fl < FL_BITS); // fl out of range - store(changetype(this) + fl * 4, value, Root.SL_START); - } - - /** End offset of second level maps. */ - @inline - private static readonly SL_END: usize = Root.SL_START + FL_BITS * 4; - - // Using *number bits per SL* heads per *FL bit* - - /** Start offset of FL/SL heads. */ - @inline - private static readonly HL_START: usize = (Root.SL_END + AL_MASK) & ~AL_MASK; - - /** Gets the head of the specified first and second level index. */ - getHead(fl: usize, sl: u32): Block | null { - assert(fl < FL_BITS && sl < SL_SIZE); // fl/sl out of range - return changetype(load( - changetype(this) + (fl * SL_SIZE + sl) * sizeof() - , Root.HL_START)); - } - - /** Sets the head of the specified first and second level index. */ - setHead(fl: usize, sl: u32, value: Block | null): void { - assert(fl < FL_BITS && sl < SL_SIZE); // fl/sl out of range - store( - changetype(this) + (fl * SL_SIZE + sl) * sizeof(), - changetype(value), - Root.HL_START - ); - } - - /** End offset of FL/SL heads. */ - @inline - private static readonly HL_END: usize = Root.HL_START + FL_BITS * SL_SIZE * sizeof(); - - get tailRef(): usize { return load(changetype(this), Root.HL_END); } - set tailRef(value: usize) { store(changetype(this), value, Root.HL_END); } - - /** Total size of the {@link Root} structure. */ - @inline - static readonly SIZE: usize = Root.HL_END + sizeof(); - - /** Inserts a previously used block back into the free list. */ - insert(block: Block): void { - // check as much as possible here to prevent invalid free blocks - assert(block); // cannot be null - var blockInfo = block.info; - assert(blockInfo & FREE); // must be free - - var right = block.right; // asserts != null - var rightInfo = right.info; - - // merge with right block if also free - if (rightInfo & FREE) { - this.remove(right); - block.info = (blockInfo += Block.HEADER_SIZE + (rightInfo & ~TAGS)); - right = block.right; - rightInfo = right.info; - // jump is set below - } - - // merge with left block if also free - if (blockInfo & LEFT_FREE) { - let left = block.left; // asserts != null - let leftInfo = left.info; - assert(leftInfo & FREE); // must be free according to right tags - this.remove(left); - left.info = (leftInfo += Block.HEADER_SIZE + (blockInfo & ~TAGS)); - block = left; - blockInfo = leftInfo; - // jump is set below - } - - right.info = rightInfo | LEFT_FREE; - this.setJump(block, right); - // right is no longer used now, hence rightInfo is not synced - - var size = blockInfo & ~TAGS; - assert(size >= Block.MIN_SIZE && size < Block.MAX_SIZE); // must be valid - - // mapping_insert - var fl: usize, sl: u32; - if (size < SB_SIZE) { - fl = 0; - sl = (size / AL_SIZE); - } else { - fl = fls(size); - sl = ((size >> (fl - SL_BITS)) ^ (1 << SL_BITS)); - fl -= SB_BITS - 1; - } - - // perform insertion - var head = this.getHead(fl, sl); - block.prev = null; - block.next = head; - if (head) head.prev = block; - this.setHead(fl, sl, block); - - // update first and second level maps - this.flMap |= (1 << fl); - this.setSLMap(fl, this.getSLMap(fl) | (1 << sl)); - } - - /** - * Removes a free block from FL/SL maps. Does not alter left/jump because it - * is likely that splitting is performed afterwards, invalidating any changes - * again. - */ - private remove(block: Block): void { - var blockInfo = block.info; - assert(blockInfo & FREE); // must be free - var size = blockInfo & ~TAGS; - assert(size >= Block.MIN_SIZE && size < Block.MAX_SIZE); // must be valid - - // mapping_insert - var fl: usize, sl: u32; - if (size < SB_SIZE) { - fl = 0; - sl = (size / AL_SIZE); - } else { - fl = fls(size); - sl = ((size >> (fl - SL_BITS)) ^ (1 << SL_BITS)); - fl -= SB_BITS - 1; - } - - // link previous and next free block - var prev = block.prev; - var next = block.next; - if (prev) prev.next = next; - if (next) next.prev = prev; - - // update head if we are removing it - if (block == this.getHead(fl, sl)) { - this.setHead(fl, sl, next); - - // clear second level map if head is empty now - if (!next) { - let slMap = this.getSLMap(fl); - this.setSLMap(fl, slMap &= ~(1 << sl)); - - // clear first level map if second level is empty now - if (!slMap) this.flMap &= ~(1 << fl); - } - } - } - - /** Searches for a free block of at least the specified size. */ - search(size: usize): Block | null { - // size was already asserted by caller - - // mapping_search - var fl: usize, sl: u32; - if (size < SB_SIZE) { - fl = 0; - sl = (size / AL_SIZE); - } else { - // (*) size += (1 << (fls(size) - SL_BITS)) - 1; - fl = fls(size); - sl = ((size >> (fl - SL_BITS)) ^ (1 << SL_BITS)); - fl -= SB_BITS - 1; - // (*) instead of rounding up, use next second level list for better fit - if (sl < SL_SIZE - 1) ++sl; - else ++fl, sl = 0; - } - - // search second level - var slMap = this.getSLMap(fl) & (~0 << sl); - var head: Block | null; - if (!slMap) { - // search next larger first level - let flMap = this.flMap & (~0 << (fl + 1)); - if (!flMap) { - head = null; - } else { - fl = ffs(flMap); - slMap = assert(this.getSLMap(fl)); // can't be zero if fl points here - head = this.getHead(fl, ffs(slMap)); - } - } else { - head = this.getHead(fl, ffs(slMap)); - } - return head; - } - - /** Links a free left with its right block in memory. */ - private setJump(left: Block, right: Block): void { - assert( - (left.info & FREE) != 0 && // must be free to contain 'jump' - left.right == right && // right block must match - (right.info & LEFT_FREE) != 0 // free status must match - ); - store( - changetype(right) - sizeof() - , left); // last word in left block's (free) data region - } - - /** - * Uses the specified free block, removing it from internal maps and - * splitting it if possible, and returns its data pointer. - */ - use(block: Block, size: usize): usize { - // size was already asserted by caller - - var blockInfo = block.info; - assert( - (blockInfo & FREE) != 0 && // must be free - !(size & AL_MASK) // size must be aligned so the new block is - ); - this.remove(block); - - // split if the block can hold another MIN_SIZE block - var remaining = (blockInfo & ~TAGS) - size; - if (remaining >= Block.HEADER_SIZE + Block.MIN_SIZE) { - block.info = size | (blockInfo & LEFT_FREE); // also discards FREE - - let spare = changetype( - changetype(block) + Block.HEADER_SIZE + size - ); - spare.info = (remaining - Block.HEADER_SIZE) | FREE; // not LEFT_FREE - this.insert(spare); // also sets jump - - // otherwise tag block as no longer FREE and right as no longer LEFT_FREE - } else { - block.info = blockInfo & ~FREE; - let right = block.right; // asserts != null - right.info &= ~LEFT_FREE; - } - - return changetype(block) + Block.HEADER_SIZE; - } - - /** Adds more memory to the pool. */ - addMemory(start: usize, end: usize): bool { - assert( - start <= end && // must be valid - !(start & AL_MASK) && // must be aligned - !(end & AL_MASK) // must be aligned - ); - - var tailRef = this.tailRef; - var tailInfo: usize = 0; - if (tailRef) { - assert(start >= tailRef + Block.HEADER_SIZE); // starts after tail - - // merge with current tail if adjacent - if (start - Block.HEADER_SIZE == tailRef) { - start -= Block.HEADER_SIZE; - tailInfo = changetype(tailRef).info; - } - - } else { - assert(start >= changetype(this) + Root.SIZE); // starts after root - } - - // check if size is large enough for a free block and the tail block - var size = end - start; - if (size < Block.HEADER_SIZE + Block.MIN_SIZE + Block.HEADER_SIZE) { - return false; - } - - // left size is total minus its own and the zero-length tail's header - var leftSize = size - 2 * Block.HEADER_SIZE; - var left = changetype(start); - left.info = leftSize | FREE | (tailInfo & LEFT_FREE); - left.prev = null; - left.next = null; - - // tail is a zero-length used block - var tail = changetype(start + size - Block.HEADER_SIZE); - tail.info = 0 | LEFT_FREE; - this.tailRef = changetype(tail); - - this.insert(left); // also merges with free left before tail / sets jump - - return true; - } -} - -/** Determines the first (LSB to MSB) set bit's index of a word. */ -function ffs(word: T): T { - assert(word != 0); // word cannot be 0 - return ctz(word); // differs from ffs only for 0 -} - -/** Determines the last (LSB to MSB) set bit's index of a word. */ -function fls(word: T): T { - assert(word != 0); // word cannot be 0 - // @ts-ignore: type - const inv: T = (sizeof() << 3) - 1; - // @ts-ignore: type - return inv - clz(word); -} - -/** Reference to the initialized {@link Root} structure, once initialized. */ -// @ts-ignore: decorator -@lazy -var ROOT: Root = changetype(0); - -/** Allocates a chunk of memory. */ -// @ts-ignore: decorator -@unsafe @global -function __mem_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); // must fit - return root.use(block, size); -} - -/** Frees the chunk of memory at the specified address. */ -// @ts-ignore: decorator -@unsafe @global -function __mem_free(data: usize): void { - if (data) { - assert(!(data & AL_MASK)); // must be aligned - let root = ROOT; - if (root) { - let block = changetype(data - Block.HEADER_SIZE); - let blockInfo = block.info; - assert(!(blockInfo & FREE)); // must be used - block.info = blockInfo | FREE; - root.insert(changetype(data - Block.HEADER_SIZE)); - } - } -} diff --git a/std/assembly/array.ts b/std/assembly/array.ts index a8b86bf1..1811681a 100644 --- a/std/assembly/array.ts +++ b/std/assembly/array.ts @@ -1,22 +1,21 @@ -/// +/// -import { MAX_BYTELENGTH, allocate, reallocate, discard, register, NEWARRAY } from "./util/runtime"; +import { BLOCK_MAXSIZE } from "./rt/common"; import { COMPARATOR, SORT } from "./util/sort"; -import { __runtime_id, __gc_mark_members } from "./runtime"; import { ArrayBuffer, ArrayBufferView } from "./arraybuffer"; import { itoa, dtoa, itoa_stream, dtoa_stream, MAX_DOUBLE_LENGTH } from "./util/number"; -import { isArray as builtin_isArray } from "./builtins"; +import { idof, isArray as builtin_isArray } from "./builtins"; import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH, E_EMPTYARRAY, E_HOLEYARRAY } from "./util/error"; /** Ensures that the given array has _at least_ the specified capacity. */ function ensureCapacity(array: ArrayBufferView, minCapacity: i32, alignLog2: u32): void { if (minCapacity > array.dataLength >>> alignLog2) { - if (minCapacity > (MAX_BYTELENGTH >>> alignLog2)) throw new RangeError(E_INVALIDLENGTH); + if (minCapacity > (BLOCK_MAXSIZE >>> alignLog2)) throw new RangeError(E_INVALIDLENGTH); let oldData = array.data; let newByteLength = minCapacity << alignLog2; - let newData = reallocate(changetype(oldData), newByteLength); // registers on move + let newData = __realloc(changetype(oldData), newByteLength); // registers on move if (newData !== changetype(oldData)) { - array.data = changetype(newData); // links + array.data = changetype(newData); // retains array.dataStart = newData; } array.dataLength = newByteLength; @@ -41,11 +40,11 @@ export class Array extends ArrayBufferView { } static create(capacity: i32 = 0): Array { - if (capacity > MAX_BYTELENGTH >>> alignof()) throw new RangeError(E_INVALIDLENGTH); - var array = NEWARRAY(capacity); - array.length_ = 0; // safe even if T is a non-nullable reference - memory.fill(array.dataStart, 0, array.dataLength); - return array; + if (capacity > BLOCK_MAXSIZE >>> alignof()) throw new RangeError(E_INVALIDLENGTH); + var array = __allocArray(capacity, alignof(), idof()); + changetype>(array).length_ = 0; // safe even if T is a non-nullable reference + memory.fill(changetype(array).dataStart, 0, changetype(array).dataLength); + return changetype>(array); // retains } constructor(length: i32 = 0) { @@ -119,27 +118,7 @@ export class Array extends ArrayBufferView { @operator("{}=") private __unchecked_set(index: i32, value: T): void { if (isManaged()) { let offset = this.dataStart + (index << alignof()); - let oldValue = load(offset); - if (value !== oldValue) { - store(offset, value); - if (isNullable()) { - if (isDefined(__ref_link)) { - if (isDefined(__ref_unlink)) if (oldValue !== null) __ref_unlink(changetype(oldValue), changetype(this)); - if (value !== null) __ref_link(changetype(value), changetype(this)); - } else if (isDefined(__ref_retain)) { - if (oldValue !== null) __ref_release(changetype(oldValue)); - if (value !== null) __ref_retain(changetype(value)); - } else assert(false); - } else { - if (isDefined(__ref_link)) { - if (isDefined(__ref_unlink)) if (oldValue !== null) __ref_unlink(changetype(oldValue), changetype(this)); - __ref_link(changetype(value), changetype(this)); - } else if (__ref_retain) { - if (oldValue !== null) __ref_release(changetype(oldValue)); - __ref_retain(changetype(value)); - } else assert(false); - } - } + store(offset, __retainRelease(changetype(value), load(offset))); } else { store(this.dataStart + (index << alignof()), value); } @@ -201,27 +180,7 @@ export class Array extends ArrayBufferView { ensureCapacity(this, newLength, alignof()); if (isManaged()) { let offset = this.dataStart + (length << alignof()); - let oldValue = load(offset); - if (oldValue !== value) { - store(offset, value); - if (isNullable()) { - if (isDefined(__ref_link)) { - if (value !== null) __ref_link(changetype(value), changetype(this)); - if (isDefined(__ref_unlink)) if (oldValue !== null) __ref_unlink(changetype(oldValue), changetype(this)); - } else if (__ref_retain) { - if (oldValue !== null) __ref_retain(changetype(value)); - if (value !== null) __ref_release(changetype(oldValue)); - } else assert(false); - } else { - if (isDefined(__ref_link)) { - __ref_link(changetype(value), changetype(this)); - if (isDefined(__ref_unlink)) if (oldValue !== null) __ref_unlink(changetype(oldValue), changetype(this)); - } else if (__ref_retain) { - __ref_retain(changetype(value)); - if (oldValue !== null) __ref_release(changetype(oldValue)); - } else assert(false); - } - } + store(offset, __retainRelease(changetype(value), load(offset))); } else { store(this.dataStart + (length << alignof()), value); } @@ -233,50 +192,28 @@ export class Array extends ArrayBufferView { var thisLen = this.length_; var otherLen = select(0, other.length_, other === null); var outLen = thisLen + otherLen; - if (outLen > MAX_BYTELENGTH >>> alignof()) throw new Error(E_INVALIDLENGTH); - var out = NEWARRAY(outLen); - var outStart = out.dataStart; + if (outLen > BLOCK_MAXSIZE >>> alignof()) throw new Error(E_INVALIDLENGTH); + var out = __allocArray(outLen, alignof(), idof>()); + var outStart = changetype(out).dataStart; var thisSize = thisLen << alignof(); if (isManaged()) { let thisStart = this.dataStart; for (let offset: usize = 0; offset < thisSize; offset += sizeof()) { let ref = load(thisStart + offset); - store(outStart + offset, ref); - if (isNullable()) { - if (ref) { - if (isDefined(__ref_link)) __ref_link(ref, changetype(this)); - else if (isDefined(__ref_retain)) __ref_retain(ref); - else assert(false); - } - } else { - if (isDefined(__ref_link)) __ref_link(ref, changetype(this)); - else if (isDefined(__ref_retain)) __ref_retain(ref); - else assert(false); - } + store(outStart + offset, __retain(ref)); } outStart += thisSize; let otherStart = other.dataStart; let otherSize = otherLen << alignof(); for (let offset: usize = 0; offset < otherSize; offset += sizeof()) { let ref = load(otherStart + offset); - store(outStart + offset, ref); - if (isNullable()) { - if (ref) { - if (isDefined(__ref_link)) __ref_link(ref, changetype(this)); - else if (isDefined(__ref_retain)) __ref_retain(ref); - else assert(false); - } - } else { - if (isDefined(__ref_link)) __ref_link(ref, changetype(this)); - else if (isDefined(__ref_retain)) __ref_retain(ref); - else assert(false); - } + store(outStart + offset, __retain(ref)); } } else { memory.copy(outStart, this.dataStart, thisSize); memory.copy(outStart + thisSize, other.dataStart, otherLen << alignof()); } - return out; + return changetype>(out); } copyWithin(target: i32, start: i32, end: i32 = i32.MAX_VALUE): this { @@ -322,38 +259,27 @@ export class Array extends ArrayBufferView { map(callbackfn: (value: T, index: i32, array: Array) => U): Array { var length = this.length_; - var out = NEWARRAY(length); - var outStart = out.dataStart; + var out = __allocArray(length, alignof(), idof>()); + var outStart = changetype(out).dataStart; for (let index = 0; index < min(length, this.length_); ++index) { - let value = load(this.dataStart + (index << alignof())); + let result = callbackfn(load(this.dataStart + (index << alignof())), index, this); // retains if (isManaged()) { - let ref = changetype(callbackfn(value, index, this)); - store(outStart + (index << alignof()), ref); - if (isNullable()) { - if (ref) { - if (isDefined(__ref_link)) __ref_link(ref, changetype(out)); - else if (isDefined(__ref_retain)) __ref_retain(ref); - else assert(false); - } - } else { - if (isDefined(__ref_link)) __ref_link(ref, changetype(out)); - else if (isDefined(__ref_retain)) __ref_retain(ref); - else assert(false); - } + store(outStart + (index << alignof()), __retain(changetype(result))); } else { - store(outStart + (index << alignof()), callbackfn(value, index, this)); + store(outStart + (index << alignof()), result); } + // releases result } - return out; + return changetype>(out); // retains } filter(callbackfn: (value: T, index: i32, array: Array) => bool): Array { - var result = NEWARRAY(0); + var result = __allocArray(0, alignof(), idof>()); for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) { let value = load(this.dataStart + (index << alignof())); - if (callbackfn(value, index, this)) result.push(value); + if (callbackfn(value, index, this)) changetype>(result).push(value); } - return result; + return changetype>(result); // retains } reduce( @@ -413,19 +339,10 @@ export class Array extends ArrayBufferView { dataStart, (newLength - 1) << alignof() ); - store(dataStart, element); if (isManaged()) { - if (isNullable()) { - if (element !== null) { - if (isDefined(__ref_link)) __ref_link(changetype(element), changetype(this)); - else if (isDefined(__ref_retain)) __ref_retain(changetype(element)); - else assert(false); - } - } else { - if (isDefined(__ref_link)) __ref_link(changetype(element), changetype(this)); - else if (isDefined(__ref_retain)) __ref_retain(changetype(element)); - else assert(false); - } + store(dataStart, __retain(changetype(element))); + } else { + store(dataStart, element); } this.length_ = newLength; return newLength; @@ -436,61 +353,41 @@ export class Array extends ArrayBufferView { begin = begin < 0 ? max(begin + length, 0) : min(begin, length); end = end < 0 ? max(end + length, 0) : min(end , length); length = max(end - begin, 0); - var slice = NEWARRAY(length); - var sliceBase = slice.dataStart; + var slice = __allocArray(length, alignof(), idof>()); + var sliceBase = changetype(slice).dataStart; var thisBase = this.dataStart + (begin << alignof()); if (isManaged()) { let off = 0; let end = length << alignof(); while (off < end) { let ref = load(thisBase + off); - store(sliceBase + off, ref); - if (isNullable()) { - if (ref) { - if (isDefined(__ref_link)) __ref_link(ref, changetype(slice)); - else if (isDefined(__ref_retain)) __ref_retain(ref); - else assert(false); - } - } else { - if (isDefined(__ref_link)) __ref_link(ref, changetype(slice)); - else if (isDefined(__ref_retain)) __ref_retain(ref); - else assert(false); - } + store(sliceBase + off, __retain(ref)); off += sizeof(); } } else { memory.copy(sliceBase, thisBase, length << alignof()); } - return slice; + return changetype>(slice); // retains } splice(start: i32, deleteCount: i32 = i32.MAX_VALUE): Array { var length = this.length_; start = start < 0 ? max(length + start, 0) : min(start, length); deleteCount = max(min(deleteCount, length - start), 0); - var result = NEWARRAY(deleteCount); - var resultStart = result.dataStart; + var result = __allocArray(deleteCount, alignof(), idof>()); + var resultStart = changetype(result).dataStart; var thisStart = this.dataStart; var thisBase = thisStart + (start << alignof()); if (isManaged()) { for (let i = 0; i < deleteCount; ++i) { - let ref = load(thisBase + (i << alignof())); - store(resultStart + (i << alignof()), ref); - if (isDefined(__ref_link)) { - if (isNullable()) { - if (ref) { - if (isDefined(__ref_unlink)) __ref_unlink(ref, changetype(this)); - __ref_link(ref, changetype(result)); - } - } else { - if (isDefined(__ref_unlink)) __ref_unlink(ref, changetype(this)); - __ref_link(ref, changetype(result)); - } - } + store(resultStart + (i << alignof()), + load(thisBase + (i << alignof())) + ); + // element is moved, so refcount doesn't change } } else { memory.copy( - result.dataStart, + resultStart, thisBase, deleteCount << alignof() ); @@ -504,7 +401,7 @@ export class Array extends ArrayBufferView { ); } this.length_ = length - deleteCount; - return result; + return changetype>(result); // retains } reverse(): Array { @@ -563,7 +460,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 = allocate(estLen << 1); + var result = __alloc(estLen << 1, idof()); var offset = 0; var value: bool; for (let i = 0; i < lastIndex; ++i) { @@ -594,11 +491,9 @@ export class Array extends ArrayBufferView { offset += valueLen; if (estLen > offset) { - let trimmed = changetype(result).substring(0, offset); - discard(result); - return trimmed; // registered in .substring + return changetype(result).substring(0, offset); // retains/releases result, retains return } - return changetype(register(result, __runtime_id())); + return changetype(result); // retains } private join_int(separator: string = ","): string { @@ -606,12 +501,12 @@ export class Array extends ArrayBufferView { if (lastIndex < 0) return ""; var dataStart = this.dataStart; // @ts-ignore: type - if (!lastIndex) return changetype(itoa(load(dataStart))); + if (!lastIndex) return changetype(itoa(load(dataStart))); // retains var sepLen = separator.length; const valueLen = (sizeof() <= 4 ? 10 : 20) + i32(isSigned()); var estLen = (valueLen + sepLen) * lastIndex + valueLen; - var result = allocate(estLen << 1); + var result = __alloc(estLen << 1, idof()); var offset = 0; var value: T; for (let i = 0; i < lastIndex; ++i) { @@ -631,11 +526,9 @@ export class Array extends ArrayBufferView { // @ts-ignore: type offset += itoa_stream(result, offset, value); if (estLen > offset) { - let trimmed = changetype(result).substring(0, offset); - discard(result); - return trimmed; // registered in .substring + return changetype(result).substring(0, offset); // retains/releases result, retains return } - return changetype(register(result, __runtime_id())); + return changetype(result); // retains } private join_flt(separator: string = ","): string { @@ -646,13 +539,13 @@ export class Array extends ArrayBufferView { return changetype(dtoa( // @ts-ignore: type load(dataStart)) - ); + ); // retains } const valueLen = MAX_DOUBLE_LENGTH; var sepLen = separator.length; var estLen = (valueLen + sepLen) * lastIndex + valueLen; - var result = allocate(estLen << 1); + var result = __alloc(estLen << 1, idof()); var offset = 0; var value: T; for (let i = 0; i < lastIndex; ++i) { @@ -676,11 +569,9 @@ export class Array extends ArrayBufferView { value ); if (estLen > offset) { - let trimmed = changetype(result).substring(0, offset); - discard(result); - return trimmed; // registered in .substring + return changetype(result).substring(0, offset); // retains/releases result, retains return } - return changetype(register(result, __runtime_id())); + return changetype(result); // retains } private join_str(separator: string = ","): string { @@ -697,7 +588,7 @@ export class Array extends ArrayBufferView { if (value !== null) estLen += value.length; } var offset = 0; - var result = allocate((estLen + sepLen * lastIndex) << 1); + var result = __alloc((estLen + sepLen * lastIndex) << 1, idof()); for (let i = 0; i < lastIndex; ++i) { value = load(dataStart + (i << alignof())); if (value !== null) { @@ -726,7 +617,7 @@ export class Array extends ArrayBufferView { changetype(value).length << 1 ); } - return changetype(register(result, __runtime_id())); + return changetype(result); // retains } private join_arr(separator: string = ","): string { @@ -763,7 +654,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 = allocate(estLen << 1); + var result = __alloc(estLen << 1, idof()); var offset = 0; var value: T; for (let i = 0; i < lastIndex; ++i) { @@ -794,11 +685,9 @@ export class Array extends ArrayBufferView { offset += valueLen; } if (estLen > offset) { - let out = changetype(result).substring(0, offset); - discard(result); - return out; // registered in .substring + return changetype(result).substring(0, offset); // retains/releases result, retains return } - return changetype(register(result, __runtime_id())); + return changetype(result); // retains } toString(): string { @@ -807,8 +696,8 @@ export class Array extends ArrayBufferView { // GC integration - @unsafe private __traverse(): void { - __ref_mark(changetype(this.data)); + @unsafe private __traverse(cookie: u32): void { + __visit(changetype(this.data), cookie); if (isManaged()) { let cur = this.dataStart; let end = cur + this.dataLength; @@ -816,12 +705,12 @@ export class Array extends ArrayBufferView { let val = load(cur); if (isNullable()) { if (val) { - __ref_mark(val); - __gc_mark_members(__runtime_id(), val); + __visit(val, cookie); + __visit_members(val, cookie); } } else { - __ref_mark(val); - __gc_mark_members(__runtime_id(), val); + __visit(val, cookie); + __visit_members(val, cookie); } cur += sizeof(); } diff --git a/std/assembly/arraybuffer.ts b/std/assembly/arraybuffer.ts index fa44cc08..d02feecf 100644 --- a/std/assembly/arraybuffer.ts +++ b/std/assembly/arraybuffer.ts @@ -1,5 +1,7 @@ -import { HEADER, HEADER_SIZE, MAX_BYTELENGTH, allocate, register } from "./util/runtime"; -import { __runtime_id } from "./runtime"; +/// + +import { BLOCK, BLOCK_MAXSIZE, BLOCK_OVERHEAD } from "./rt/common"; +import { idof } from "./builtins"; import { E_INVALIDLENGTH } from "./util/error"; export abstract class ArrayBufferView { @@ -9,9 +11,9 @@ export abstract class ArrayBufferView { @unsafe dataLength: u32; protected constructor(length: i32, alignLog2: i32) { - if (length > MAX_BYTELENGTH >>> alignLog2) throw new RangeError(E_INVALIDLENGTH); + if (length > BLOCK_MAXSIZE >>> alignLog2) throw new RangeError(E_INVALIDLENGTH); var buffer = new ArrayBuffer(length = length << alignLog2); - this.data = buffer; + this.data = buffer; // retains this.dataStart = changetype(buffer); this.dataLength = length; } @@ -51,24 +53,24 @@ export abstract class ArrayBufferView { } constructor(length: i32) { - if (length > MAX_BYTELENGTH) throw new RangeError(E_INVALIDLENGTH); - var buffer = allocate(length); - memory.fill(changetype(buffer), 0, length); - return changetype(register(buffer, __runtime_id())); + if (length > BLOCK_MAXSIZE) throw new RangeError(E_INVALIDLENGTH); + var buffer = __alloc(length, idof()); + memory.fill(buffer, 0, length); + return changetype(buffer); // retains } get byteLength(): i32 { - return changetype
(changetype(this) - HEADER_SIZE).payloadSize; + return changetype(changetype(this) - BLOCK_OVERHEAD).rtSize; } - slice(begin: i32 = 0, end: i32 = MAX_BYTELENGTH): ArrayBuffer { + slice(begin: i32 = 0, end: i32 = BLOCK_MAXSIZE): ArrayBuffer { var length = this.byteLength; begin = begin < 0 ? max(length + begin, 0) : min(begin, length); end = end < 0 ? max(length + end , 0) : min(end , length); var outSize = max(end - begin, 0); - var out = allocate(outSize); + var out = __alloc(outSize, idof()); memory.copy(out, changetype(this) + begin, outSize); - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } toString(): string { diff --git a/std/assembly/builtins.ts b/std/assembly/builtins.ts index 83b3f1f9..c6a667ea 100644 --- a/std/assembly/builtins.ts +++ b/std/assembly/builtins.ts @@ -1740,3 +1740,23 @@ declare function trace( a3?: f64, a4?: f64 ): void; + +// @ts-ignore: decorator +@builtin +export declare const HEAP_BASE: usize; + +// @ts-ignore: decorator +@builtin +export declare const RTTI_BASE: usize; + +// @ts-ignore: decorator +@builtin +export declare function idof(): u32; + +// @ts-ignore: decorator +@builtin @unsafe +export declare function __visit_globals(cookie: u32): void; + +// @ts-ignore: decorator +@builtin @unsafe +export declare function __visit_members(ref: usize, cookie: u32): void; diff --git a/std/assembly/collector/README.md b/std/assembly/collector/README.md deleted file mode 100644 index d6071fca..00000000 --- a/std/assembly/collector/README.md +++ /dev/null @@ -1,113 +0,0 @@ -Garbage collector interface -=========================== - -A garbage collector for AssemblyScript must implement the following common and either the tracing or reference counting interfaces: - -Common ------- - -* **__ref_collect**(): `void`
- Triggers a full garbage collection cycle. Also indicates the presence of a GC. - -Tracing -------- - -* **__ref_register**(ref: `usize`): `void`
- Sets up a new reference. - -* **__ref_link**(ref: `usize`, parentRef: `usize`): `void`
- Links a reference to a parent that is now referencing it. - -* **__ref_unlink**(ref: `usize`, parentRef: `usize`): `void`
- Unlinks a reference from a parent that was referencing it. Implementation is optional. - -* **__ref_mark**(ref: `usize`): `void`
- Marks a reference as being reachable so it doesn't become sweeped. - -Reference counting ------------------- - -* **__ref_register**(ref: `usize`): `void`
- Sets up a new reference. Implementation is optional. - -* **__ref_retain**(ref: `usize`): `void`
- Retains a reference, usually incrementing RC. - -* **__ref_release**(ref: `usize`): `void`
- Releases a reference, usually decrementing RC. - -Typical patterns ----------------- - -Standard library components make use of the interface where managed references are stored or deleted. Common patterns are: - -### General - -```ts -/// - -if (isManaged()) { - // compiled only if T is a managed reference - ... pattern ... -} -``` - -### Insertion - -```ts -if (isNullable()) { - if (ref) { - if (isDefined(__ref_link)) __ref_link(ref, parentRef); - else if (isDefined(__ref_retain)) __ref_retain(ref); - else assert(false); - } -} else { - if (isDefined(__ref_link)) __ref_link(ref, parentRef); - else if (isDefined(__ref_retain)) __ref_retain(ref); - else assert(false); -} -``` - -### Replacement - -```ts -if (ref !== oldRef) { - if (isNullable()) { - if (isDefined(__ref_link)) { - if (isDefined(__ref_unlink)) if (oldRef) __ref_unlink(oldRef, parentRef); - if (ref) __ref_link(ref, parentRef); - } else if (isDefined(__ref_retain)) { - if (oldRef) __ref_release(oldRef); - if (ref) __ref_retain(ref); - } else assert(false); - } else { - if (isDefined(__ref_link)) { - if (isDefined(__ref_unlink)) if (oldRef) __ref_unlink(oldRef, parentRef); // * - __ref_link(ref, parentRef); - } else if (isDefined(__ref_retain)) { - if (oldRef) __ref_release(oldRef); // * - __ref_retain(ref); - } else assert(false); - } -} -``` - -### Deletion - -```ts -if (isNullable()) { - if (ref) { - if (isDefined(__ref_link)) { - if (isDefined(__ref_unlink)) __ref_unlink(ref, parentRef); - } else if (isDefined(__ref_retain)) __ref_release(ref); - else assert(false); - } -} else { - if (isDefined(__ref_link)) { - if (isDefined(__ref_unlink)) __ref_unlink(ref, parentRef); - } else if (isDefined(__ref_retain)) __ref_release(ref); - else assert(false); -} -``` - -(*) Note that some data structures may contain `null` values even though the value type isn't nullable. May be the case when appending a new element to an array for example. diff --git a/std/assembly/collector/dummy.ts b/std/assembly/collector/dummy.ts deleted file mode 100644 index 231e8e90..00000000 --- a/std/assembly/collector/dummy.ts +++ /dev/null @@ -1,37 +0,0 @@ -// A tracing dummy GC. - -// @ts-ignore: decorator -@inline -const TRACE = isDefined(GC_TRACE); - -// @ts-ignore: decorator -@global @unsafe -function __ref_register(ref: usize): void { - if (TRACE) trace("dummy.register", 1, ref); -} - -// @ts-ignore: decorator -@global @unsafe -function __ref_collect(): void { - if (TRACE) trace("dummy.collect"); -} - -// Tracing - -// @ts-ignore: decorator -@global @unsafe -function __ref_link(ref: usize, parentRef: usize): void { - if (TRACE) trace("dummy.link", 2, ref, parentRef); -} - -// @ts-ignore: decorator -@global @unsafe -function __ref_unlink(ref: usize, parentRef: usize): void { - if (TRACE) trace("dummy.unlink", 2, ref, parentRef); -} - -// @ts-ignore: decorator -@global @unsafe -function __ref_mark(ref: usize): void { - if (TRACE) trace("dummy.mark", 1, ref); -} diff --git a/std/assembly/collector/dummyrc.ts b/std/assembly/collector/dummyrc.ts deleted file mode 100644 index 676e8deb..00000000 --- a/std/assembly/collector/dummyrc.ts +++ /dev/null @@ -1,29 +0,0 @@ -// A reference counting dummy GC. - -// @ts-ignore: decorator -@inline -const TRACE = isDefined(GC_TRACE); - -// @ts-ignore: decorator -@global @unsafe -function __ref_register(ref: usize): void { - if (TRACE) trace("dummyrc.register", 1, ref); -} - -// @ts-ignore: decorator -@global @unsafe -function __ref_collect(): void { - if (TRACE) trace("dummyrc.collect"); -} - -// @ts-ignore: decorator -@global @unsafe -function __ref_retain(ref: usize): void { - if (TRACE) trace("dummyrc.retain", 1, ref); -} - -// @ts-ignore: decorator -@global @unsafe -function __ref_release(ref: usize): void { - if (TRACE) trace("dummyrc.release", 1, ref); -} diff --git a/std/assembly/collector/index.d.ts b/std/assembly/collector/index.d.ts deleted file mode 100644 index 74a44d04..00000000 --- a/std/assembly/collector/index.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -// common -declare function __ref_collect(): void; -declare function __ref_register(ref: usize): void; - -// tracing -declare function __ref_link(ref: usize, parentRef: usize): void; -declare function __ref_unlink(ref: usize, parentRef: usize): void; -declare function __ref_mark(ref: usize): void; - -// reference counting -declare function __ref_retain(ref: usize): void; -declare function __ref_release(ref: usize): void; - -// debugging -declare const GC_TRACE: bool; diff --git a/std/assembly/collector/itcm.ts b/std/assembly/collector/itcm.ts deleted file mode 100644 index 83fa90e8..00000000 --- a/std/assembly/collector/itcm.ts +++ /dev/null @@ -1,258 +0,0 @@ -// Largely based on Bach Le's ΞΌgc, see: https://github.com/bullno1/ugc - -// @ts-ignore: decorator -@inline -const TRACE = isDefined(GC_TRACE); - -import { HEADER_SIZE } from "../util/runtime"; -import { __gc_mark_roots, __gc_mark_members } from "../runtime"; - -/** Collector states. */ -const enum State { - /** Not yet initialized. */ - INIT = 0, - /** Currently transitioning from SWEEP to MARK state. */ - IDLE = 1, - /** Currently marking reachable objects. */ - MARK = 2, - /** Currently sweeping unreachable objects. */ - SWEEP = 3 -} - -/** Current collector state. */ -// @ts-ignore: decorator -@lazy -var state = State.INIT; -/** Current white color value. */ -// @ts-ignore: decorator -@lazy -var white = 0; - -// From and to spaces -// @ts-ignore: decorator -@lazy -var fromSpace: ManagedObjectList; -// @ts-ignore: decorator -@lazy -var toSpace: ManagedObjectList; -// @ts-ignore: decorator -@lazy -var iter: ManagedObject; - -// ╒═══════════════ Managed object layout (32-bit) ════════════════╕ -// 3 2 1 -// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits -// β”œβ”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”Όβ”€β”Όβ”€β”΄β”€β”€ ┐ -// β”‚ next β”‚0β”‚ C β”‚ ◄─┐ = nextWithColor -// β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”΄β”€β”€β”€β”€ β”‚ usize -// β”‚ prev β”‚ β—„β”€β”˜ -// β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ -// β”‚ hookFn β”‚ -// β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•‘ SIZE β”˜ ◄─ user-space reference -// β”‚ ... data ... β”‚ -// β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ -// C: color - -/** Represents a managed object in memory, consisting of a header followed by the object's data. */ -@unmanaged class ManagedObject { - - //
- classId: u32; - payloadSize: u32; - //
- - /** Pointer to the next object with color flags stored in the alignment bits. */ - nextWithColor: usize; - - /** Pointer to the previous object. */ - prev: ManagedObject; - - /** Class-specific hook function called with the user-space reference. */ - get hookFn(): (ref: usize) => void { - return changetype<(ref: usize) => void>(this.classId); - } - - /** Gets the pointer to the next object. */ - get next(): ManagedObject { - return changetype(this.nextWithColor & ~3); - } - - /** Sets the pointer to the next object. */ - set next(obj: ManagedObject) { - this.nextWithColor = changetype(obj) | (this.nextWithColor & 3); - } - - /** Gets this object's color. */ - get color(): i32 { - return this.nextWithColor & 3; - } - - /** Sets this object's color. */ - set color(color: i32) { - this.nextWithColor = (this.nextWithColor & ~3) | color; - } - - /** Unlinks this object from its list. */ - unlink(): void { - var next = this.next; - var prev = this.prev; - if (TRACE) trace(" unlink [pref, ref, next]", 3, objToRef(prev), objToRef(this), objToRef(next)); - next.prev = prev; - prev.next = next; - } - - /** Marks this object as gray, that is reachable with unscanned children. */ - makeGray(): void { - if (TRACE) trace(" makeGray", 1, objToRef(this)); - const gray = 2; - if (this == iter) iter = this.prev; - this.unlink(); - toSpace.push(this); - this.nextWithColor = (this.nextWithColor & ~3) | gray; - } -} - -/** A list of managed objects. Used for the from and to spaces. */ -@unmanaged class ManagedObjectList extends ManagedObject { - - /** Inserts an object. */ - push(obj: ManagedObject): void { - var prev = this.prev; - if (TRACE) trace(" push [prev, ref, next]", 3, objToRef(prev), objToRef(obj), objToRef(this)); - obj.next = this; - obj.prev = prev; - prev.next = obj; - this.prev = obj; - } - - /** Clears this list. */ - clear(): void { - if (TRACE) trace(" clear", 1, objToRef(this)); - this.nextWithColor = changetype(this); - this.prev = this; - } -} - -function maybeInit(): void { - if (state == State.INIT) { - if (TRACE) trace("itcm~init"); - fromSpace = changetype(memory.allocate(HEADER_SIZE)); - if (TRACE) trace(" fromSpace =", 1, objToRef(fromSpace)); - fromSpace.classId = -1; // would error - fromSpace.payloadSize = 0; - fromSpace.clear(); - toSpace = changetype(memory.allocate(HEADER_SIZE)); - if (TRACE) trace(" toSpace =", 1, objToRef(toSpace)); - toSpace.classId = -1; // would error - toSpace.payloadSize = 0; - toSpace.clear(); - iter = toSpace; - state = State.IDLE; - if (TRACE) trace("itcm~state = IDLE"); - } -} - -/** Performs a single step according to the current state. */ -function step(): void { - var obj: ManagedObject; - switch (state) { - case State.INIT: unreachable(); - case State.IDLE: { - if (TRACE) trace("itcm~step/IDLE"); - __gc_mark_roots(); - state = State.MARK; - if (TRACE) trace("itcm~state = MARK"); - break; - } - case State.MARK: { - obj = iter.next; - if (obj !== toSpace) { - if (TRACE) trace("itcm~step/MARK", 1, objToRef(obj)); - iter = obj; - obj.color = i32(!white); - __gc_mark_members(obj.classId, objToRef(obj)); - } else { - __gc_mark_roots(); - if (TRACE) trace("itcm~step/MARK finish"); - obj = iter.next; - if (obj === toSpace) { - let from = fromSpace; - fromSpace = toSpace; - toSpace = from; - white = i32(!white); - iter = from.next; - state = State.SWEEP; - if (TRACE) trace("itcm~state = SWEEP"); - } - } - break; - } - case State.SWEEP: { - obj = iter; - if (obj !== toSpace) { - if (TRACE) trace("itcm~step/SWEEP free", 1, objToRef(obj)); - iter = obj.next; - if (changetype(obj) >= HEAP_BASE) memory.free(changetype(obj)); - } else { - if (TRACE) trace("itcm~step/SWEEP finish"); - toSpace.clear(); - state = State.IDLE; - if (TRACE) trace("itcm~state = IDLE"); - } - break; - } - } -} - -// @ts-ignore: decorator -@inline -function refToObj(ref: usize): ManagedObject { - return changetype(ref - HEADER_SIZE); -} - -// @ts-ignore: decorator -@inline -function objToRef(obj: ManagedObject): usize { - return changetype(obj) + HEADER_SIZE; -} - -// Garbage collector interface - -// @ts-ignore: decorator -@global @unsafe -export function __ref_collect(): void { - if (TRACE) trace("itcm.collect"); - maybeInit(); - // finish the current state - while (state != State.IDLE) step(); - // perform a full cycle - do step(); while (state != State.IDLE); -} - -// @ts-ignore: decorator -@global @unsafe -export function __ref_register(ref: usize): void { - if (TRACE) trace("itcm.register", 1, ref); - maybeInit(); - var obj = refToObj(ref); - obj.color = white; - fromSpace.push(obj); // sets gc-reserved header fields -} - -// @ts-ignore: decorator -@global @unsafe -export function __ref_link(ref: usize, parentRef: usize): void { - if (TRACE) trace("itcm.link", 2, ref, parentRef); - maybeInit(); - var parent = refToObj(parentRef); - if (parent.color == i32(!white) && refToObj(ref).color == white) parent.makeGray(); -} - -// @ts-ignore: decorator -@global @unsafe -export function __ref_mark(ref: usize): void { - if (TRACE) trace("itcm.mark", 1, ref); - maybeInit(); - var obj = refToObj(ref); - if (obj.color == white) obj.makeGray(); -} diff --git a/std/assembly/collector/pure.ts b/std/assembly/collector/pure.ts deleted file mode 100644 index 8b977fcd..00000000 --- a/std/assembly/collector/pure.ts +++ /dev/null @@ -1,269 +0,0 @@ -// A Pure Reference Counting Garbage Collector -// -// After the paper by D. Bacon et al., 2001, IBM T.J. Watson Research Center -// https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon03Pure.pdf - -import { HEADER_SIZE } from "../util/runtime"; - -ERROR("not implemented"); - -/* tslint:disable */ - -// TODO: new builtins -declare function ITERATECHILDREN(s: Header, fn: (t: Header) => void): void; -declare function ISACYCLIC(s: Header): bool; - -/** Object Colorings for Cycle Collection */ -const enum Color { - /** In use or free. */ - BLACK = 0, - /** Possible member of cycle. */ - GRAY = 1, - /** Member of garbage cycle. */ - WHITE = 2, - /** Possible root of cycle. */ - PURPLE = 3, - /** Acyclic. */ - GREEN = 4 -} - -// TODO: this is a placeholder -> map this to HEADER -class Header { - rc: u32; - color: Color; - buffered: bool; -} - -// When reference counts are decremented, we place potential roots of cyclic garbage into a buffer -// called Roots. Periodically, we process this buffer and look for cycles by subtracting internal -// reference counts. - -var rootsBuffer: usize = 0; -var rootsOffset: usize = 0; // insertion offset -var rootsLength: usize = 0; // insertion limit - -function appendRoot(s: Header): void { - if (rootsOffset >= rootsLength) { - // grow for now - let newLength = rootsLength ? 2 * rootsLength : 256 * sizeof(); - let newBuffer = memory.allocate(newLength); - memory.copy(newBuffer, rootsBuffer, rootsOffset); - memory.free(rootsBuffer); - rootsBuffer = newBuffer; - rootsLength = newLength; - } - store(rootsBuffer + rootsOffset, s); - rootsOffset += sizeof(); -} - -function systemFree(s: Header): void { - memory.free(changetype(s)); -} - -// When a reference to a node S is created, the reference count of T is incremented and it is -// colored black, since any object whose reference count was just incremented can not be garbage. - -function increment(s: Header): void { - s.rc += 1; - s.color = ISACYCLIC(s) ? Color.GREEN : Color.BLACK; // TODO: is this about correct? -} - -// When a reference to a node S is deleted, the reference count is decremented. If the reference -// count reaches zero, the procedure Release is invoked to free the garbage node. If the reference -// count does not reach zero, the node is considered as a possible root of a cycle. - -function decrement(s: Header): void { - s.rc -= 1; - if (s.color == Color.GREEN) { // if (ISACYCLIC()) { ... } - if (!s.rc) systemFree(s); - // TODO: is this correct? here, if `decrement` was generic (propagate from UNLINK) - // the green condition could be eliminated both here and in increment (just using black). - // acyclic types also don't need ITERATECHILDREN then as these really just inc/dec/free. - } else { - if (!s.rc) release(s); - else possibleRoot(s); - } -} - -// When the reference count of a node reaches zero, the contained pointers are deleted, the object -// is colored black, and unless it has been buffered, it is freed. If it has been buffered, it is -// in the Roots buffer and will be freed later (in the procedure MarkRoots). - -function release(s: Header): void { - ITERATECHILDREN(s, t => decrement(t)); // TODO: skip if acyclic ? - s.color = Color.BLACK; - if (!s.buffered) systemFree(s); -} - -// When the reference count of S is decremented but does not reach zero, it is considered as a -// possible root of a garbage cycle. If its color is already purple, then it is already a candidate -// root; if not, its color is set to purple. Then the buffered flag is checked to see if it has -// been purple since we last performed a cycle collection. If it is not buffered, it is added to -// the buffer of possible roots. - -function possibleRoot(s: Header): void { - if (s.color != Color.PURPLE) { - s.color = Color.PURPLE; - if (!s.buffered) { - s.buffered = true; - appendRoot(s); - } - } -} - -// When the root buffer is full, or when some other condition, such as low memory occurs, the -// actual cycle collection operation is invoked. This operation has three phases: MarkRoots, which -// removes internal reference counts; ScanRoots, which restores reference counts when they are -// non-zero; and finally CollectRoots, which actually collects the cyclic garbage. - -function collectCycles(): void { - markRoots(); - scanRoots(); - collectRoots(); -} - -// The marking phase looks at all the nodes S whose pointers have been stored in the Roots buffer -// since the last cycle collection. If the color of the node is purple (indicating a possible root -// of a garbage cycle) and the reference count has not become zero, then MarkGray(S) is invoked to -// perform a depth-first search in which the reached nodes are colored gray and internal reference -// counts are subtracted. Otherwise, the node is removed from the Roots buffer, the buffered flag -// is cleared, and if the reference count is zero the object is freed. - -function markRoots(): void { - var readOffset = rootsBuffer; - var writeOffset = readOffset; - var readLimit = readOffset + rootsOffset; - while (readOffset < readLimit) { - let s = load
(readOffset); - if (s.color == Color.PURPLE && s.rc > 0) { - markGray(s); - store
(writeOffset, s); - writeOffset += sizeof(); - } else { - s.buffered = false; - // remove from roots - if (s.color == Color.BLACK && !s.rc) systemFree(s); - } - readOffset += sizeof(); - } - rootsOffset = writeOffset - rootsBuffer; -} - -// For each node S that was considered by MarkGray(S), this procedure invokes Scan(S) to either -// color the garbage subgraph white or re-color the live subgraph black. - -function scanRoots(): void { - var readOffset = rootsBuffer; - var readLimit = readOffset + rootsOffset; - while (readOffset < readLimit) { - scan(load
(readOffset)); - readOffset += sizeof(); - } -} - -// After the ScanRoots phase of the CollectCycles procedure, any remaining white nodes will be -// cyclic garbage and will be reachable from the Roots buffer. This prodecure invokes CollectWhite -// for each node in the Roots buffer to collect the garbage; all nodes in the root buffer are -// removed and their buffered flag is cleared. - -function collectRoots(): void { - var readOffset = rootsBuffer; - var readLimit = readOffset + rootsOffset; - while (readOffset < readLimit) { - let s = load
(readOffset); - // remove from roots - s.buffered = false; - collectWhite(s); - } - rootsOffset = 0; -} - -// This procedure performs a simple depth-first traversal of the graph beginning at S, marking -// visited nodes gray and removing internal reference counts as it goes. - -function markGray(s: Header): void { - if (s.color != Color.GRAY) { - s.color = Color.GRAY; - ITERATECHILDREN(s, t => { - t.rc -= 1; - markGray(t); - }); - } -} - -// If this procedure finds a gray object whose reference count is greater than one, then that -// object and everything reachable from it are live data; it will therefore call ScanBlack(S) in -// order to re-color the reachable subgraph and restore the reference counts subtracted by -// MarkGray. However, if the color of an object is gray and its reference count is zero, then it is -// colored white, and Scan is invoked upon its chldren. Note that an object may be colored white -// and then re-colored black if it is reachable from some subsequently discovered live node. - -function scan(s: Header): void { - if (s.color == Color.GRAY) { - if (s.rc > 0) scanBlack(s); - else { - s.color = Color.WHITE; - ITERATECHILDREN(s, t => scan(t)); - } - } - -} - -// This procedure performs the inverse operation of MarkGray, visiting the nodes, changing the -// color of objects back to black, and restoring their reference counts. - -function scanBlack(s: Header): void { - s.color = Color.BLACK; - ITERATECHILDREN(s, t => { - t.rc += 1; - if (t.color != Color.BLACK) scanBlack(t); - }); -} - -// This procedure recursively frees all white objects, re-coloring them black as it goes. If a -// white object is buffered, it is not freed; it will be freed later when it is found in the Roots -// buffer. - -function collectWhite(s: Header): void { - if (s.color == Color.WHITE && !s.buffered) { - s.color = Color.BLACK; - ITERATECHILDREN(s, t => collectWhite(t)); - systemFree(s); - } -} - -// Garbage collector interface - -// @ts-ignore: decorator -@global @unsafe -function __ref_collect(): void { - collectCycles(); -} - -// @ts-ignore: decorator -@global @unsafe -function __ref_retain(ref: usize): void { - increment(changetype
(ref - HEADER_SIZE)); -} - -// @ts-ignore: decorator -@global @unsafe -function __ref_release(ref: usize): void { - decrement(changetype
(ref - HEADER_SIZE)) -} - -// TODO: - -// A significant constant-factor improvement can be obtained for cycle collection by observing that -// some objects are inherently acyclic. We speculate that they will comprise the majorits of -// objects in many applications. Therefore, if we can avoid cycle collection for inherently acyclic -// object, we will significantly reduce the overhead of cycle collection as a whole. [...] -// -// Acyclic classes may contain: -// - scalars; -// - references to classes that are both acyclic and final; and -// - arrays of either of the above. -// -// Our implementation marks objects whose class is acyclic with the special color green. Green -// objects are ignored by the cycle collection algorithm, except that when a dead cycle refers to -// green objects, they are collected along with the dead cycle. diff --git a/std/assembly/dataview.ts b/std/assembly/dataview.ts index 875f8307..48e1ed80 100644 --- a/std/assembly/dataview.ts +++ b/std/assembly/dataview.ts @@ -1,4 +1,4 @@ -import { MAX_BYTELENGTH } from "./util/runtime"; +import { BLOCK_MAXSIZE } from "./rt/common"; import { ArrayBuffer } from "./arraybuffer"; import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH } from "./util/error"; @@ -16,10 +16,10 @@ export class DataView { byteLength: i32 = buffer.byteLength ) { if ( - i32(byteLength > MAX_BYTELENGTH) | + i32(byteLength > BLOCK_MAXSIZE) | i32(byteOffset + byteLength > buffer.byteLength) ) throw new RangeError(E_INVALIDLENGTH); - this.data = buffer; // links + this.data = buffer; // retains var dataStart = changetype(buffer) + byteOffset; this.dataStart = dataStart; this.dataLength = byteLength; diff --git a/std/assembly/fixedarray.ts b/std/assembly/fixedarray.ts index 1c166ce8..039d83d6 100644 --- a/std/assembly/fixedarray.ts +++ b/std/assembly/fixedarray.ts @@ -1,5 +1,7 @@ -import { HEADER, HEADER_SIZE, MAX_BYTELENGTH, allocate, register } from "./util/runtime"; -import { __runtime_id, __gc_mark_members } from "./runtime"; +/// + +import { BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "./rt/common"; +import { idof } from "./builtins"; import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH, E_HOLEYARRAY } from "./util/error"; // NOTE: DO NOT USE YET! @@ -11,20 +13,20 @@ export class FixedArray { [key: number]: T; constructor(length: i32) { - if (length > MAX_BYTELENGTH >>> alignof()) throw new RangeError(E_INVALIDLENGTH); + if (length > BLOCK_MAXSIZE >>> alignof()) throw new RangeError(E_INVALIDLENGTH); if (isReference()) { if (!isNullable()) { if (length) throw new Error(E_HOLEYARRAY); } } var outSize = length << alignof(); - var out = allocate(outSize); + var out = __alloc(outSize, idof>()); memory.fill(out, 0, outSize); - return changetype>(register(out, __runtime_id>())); + return changetype>(out); // retains } get length(): i32 { - return changetype
(changetype(this) - HEADER_SIZE).payloadSize >>> alignof(); + return changetype(changetype(this) - BLOCK_OVERHEAD).rtSize >>> alignof(); } @operator("[]") private __get(index: i32): T { @@ -44,26 +46,10 @@ export class FixedArray { @operator("{}=") private __unchecked_set(index: i32, value: T): void { if (isManaged()) { let offset = changetype(this) + (index << alignof()); - let oldValue = load(offset); - if (value !== oldValue) { - store(offset, value); - if (oldValue !== null) { - if (isDefined(__ref_link)) { - if (isDefined(__ref_unlink)) __ref_unlink(changetype(oldValue), changetype(this)); - } else if (isDefined(__ref_retain)) __ref_release(changetype(oldValue)); - else assert(false); - } - if (isNullable()) { - if (value !== null) { - if (isDefined(__ref_link)) __ref_link(changetype(value), changetype(this)); - else if (isDefined(__ref_retain)) __ref_retain(changetype(value)); - else assert(false); - } - } else { - if (isDefined(__ref_link)) __ref_link(changetype(value), changetype(this)); - else if (isDefined(__ref_retain)) __ref_retain(changetype(value)); - else assert(false); - } + let oldValue = load(offset); + if (changetype(value) != oldValue) { + store(offset, __retain(changetype(value))); + __release(changetype(oldValue)); } } else { store(changetype(this) + (index << alignof()), value); @@ -72,20 +58,20 @@ export class FixedArray { // GC integration - @unsafe private __traverse(): void { + @unsafe private __traverse(cookie: u32): void { if (isManaged()) { let cur = changetype(this); - let end = cur + changetype
(changetype(this) - HEADER_SIZE).payloadSize; + let end = cur + changetype(changetype(this) - BLOCK_OVERHEAD).rtSize; while (cur < end) { let val = load(cur); if (isNullable()) { if (val) { - __ref_mark(val); - __gc_mark_members(__runtime_id(), val); + __visit(val, cookie); + __visit_members(val, cookie); } } else { - __ref_mark(val); - __gc_mark_members(__runtime_id(), val); + __visit(val, cookie); + __visit_members(val, cookie); } cur += sizeof(); } diff --git a/std/assembly/map.ts b/std/assembly/map.ts index 4d52a3d8..d87b97a7 100644 --- a/std/assembly/map.ts +++ b/std/assembly/map.ts @@ -1,7 +1,6 @@ -/// +/// import { HASH } from "./util/hash"; -import { __runtime_id, __gc_mark_members } from "./runtime"; // A deterministic hash map based on CloseTable from https://github.com/jorendorff/dht @@ -83,7 +82,7 @@ export class Map { } private find(key: K, hashCode: u32): MapEntry | null { - var entry = load>( + var entry = load>( // unmanaged! changetype(this.buckets) + (hashCode & this.bucketsMask) * BUCKET_SIZE ); while (entry) { @@ -106,35 +105,9 @@ export class Map { var hashCode = HASH(key); var entry = this.find(key, hashCode); // unmanaged! if (entry) { - if (isManaged()) { - let oldValue = entry.value; - if (value !== oldValue) { - entry.value = value; - if (isNullable()) { - if (oldValue !== null) { - if (isDefined(__ref_link)) { - if (isDefined(__ref_unlink)) __ref_unlink(changetype(oldValue), changetype(this)); - } else if (isDefined(__ref_retain)) __ref_release(changetype(oldValue)); - else assert(false); - } - if (value !== null) { - if (isDefined(__ref_link)) __ref_link(changetype(value), changetype(this)); - else if (isDefined(__ref_retain)) __ref_retain(changetype(value)); - else assert(false); - } - } else { - if (isDefined(__ref_link)) { - if (isDefined(__ref_unlink)) __ref_unlink(changetype(oldValue), changetype(this)); - __ref_link(changetype(value), changetype(this)); - } else if (isDefined(__ref_retain)) { - __ref_release(changetype(oldValue)); - __ref_retain(changetype(value)); - } else assert(false); - } - } - } else { - entry.value = value; - } + entry.value = isManaged() + ? changetype(__retainRelease(changetype(value), changetype(entry.value))) + : value; } else { // check if rehashing is necessary if (this.entriesOffset == this.entriesCapacity) { @@ -147,35 +120,13 @@ export class Map { // append new entry let entries = this.entries; entry = changetype>(changetype(entries) + this.entriesOffset++ * ENTRY_SIZE()); - entry.key = key; - entry.value = value; // link with the map - if (isManaged()) { - if (isNullable()) { - if (key !== null) { - if (isDefined(__ref_link)) __ref_link(changetype(key), changetype(this)); - else if (isDefined(__ref_retain)) __ref_retain(changetype(key)); - else assert(false); - } - } else { - if (isDefined(__ref_link)) __ref_link(changetype(key), changetype(this)); - else if (isDefined(__ref_retain)) __ref_retain(changetype(key)); - else assert(false); - } - } - if (isManaged()) { - if (isNullable()) { - if (value !== null) { - if (isDefined(__ref_link)) __ref_link(changetype(value), changetype(this)); - else if (isDefined(__ref_retain)) __ref_retain(changetype(value)); - else assert(false); - } - } else { - if (isDefined(__ref_link)) __ref_link(changetype(value), changetype(this)); - else if (isDefined(__ref_retain)) __ref_retain(changetype(value)); - else assert(false); - } - } + entry.key = isManaged() + ? changetype(__retain(changetype(key))) + : key; + entry.value = isManaged() + ? changetype(__retain(changetype(value))) + : value; ++this.entriesCount; // link with previous entry in bucket let bucketPtrBase = changetype(this.buckets) + (hashCode & this.bucketsMask) * BUCKET_SIZE; @@ -187,38 +138,8 @@ export class Map { delete(key: K): bool { var entry = this.find(key, HASH(key)); if (!entry) return false; - if (isManaged()) { - let oldKey = entry.key; - if (isNullable()) { - if (oldKey !== null) { - if (isDefined(__ref_link)) { - if (isDefined(__ref_unlink)) __ref_unlink(changetype(oldKey), changetype(this)); - } else if (isDefined(__ref_retain)) __ref_release(changetype(oldKey)); - else assert(false); - } - } else { - if (isDefined(__ref_link)) { - if (isDefined(__ref_unlink)) __ref_unlink(changetype(oldKey), changetype(this)); - } else if (isDefined(__ref_retain)) __ref_release(changetype(oldKey)); - else assert(false); - } - } - if (isManaged()) { - let oldValue = entry.key; - if (isNullable()) { - if (oldValue !== null) { - if (isDefined(__ref_link)) { - if (isDefined(__ref_unlink)) __ref_unlink(changetype(oldValue), changetype(this)); - } else if (isDefined(__ref_retain)) __ref_release(changetype(oldValue)); - else assert(false); - } - } else { - if (isDefined(__ref_link)) { - if (isDefined(__ref_unlink)) __ref_unlink(changetype(oldValue), changetype(this)); - } else if (isDefined(__ref_retain)) __ref_release(changetype(oldValue)); - else assert(false); - } - } + if (isManaged()) __release(changetype(entry.key)); + if (isManaged()) __release(changetype(entry.value)); entry.taggedNext |= EMPTY; --this.entriesCount; // check if rehashing is appropriate @@ -268,10 +189,10 @@ export class Map { // GC integration - @unsafe private __traverse(): void { - __ref_mark(changetype(this.buckets)); + @unsafe private __traverse(cookie: u32): void { + __visit(changetype(this.buckets), cookie); var entries = this.entries; - __ref_mark(changetype(entries)); + __visit(changetype(entries), cookie); if (isManaged() || isManaged()) { let cur = changetype(entries); let end = cur + this.entriesOffset * ENTRY_SIZE(); @@ -282,24 +203,24 @@ export class Map { let val = changetype(entry.key); if (isNullable()) { if (val) { - __ref_mark(val); - __gc_mark_members(__runtime_id(), val); + __visit(val, cookie); + __visit_members(val, cookie); } } else { - __ref_mark(val); - __gc_mark_members(__runtime_id(), val); + __visit(val, cookie); + __visit_members(val, cookie); } } if (isManaged()) { let val = changetype(entry.value); if (isNullable()) { if (val) { - __ref_mark(val); - __gc_mark_members(__runtime_id(), val); + __visit(val, cookie); + __visit_members(val, cookie); } } else { - __ref_mark(val); - __gc_mark_members(__runtime_id(), val); + __visit(val, cookie); + __visit_members(val, cookie); } } } diff --git a/std/assembly/memory.ts b/std/assembly/memory.ts index 6865c362..8337f95c 100644 --- a/std/assembly/memory.ts +++ b/std/assembly/memory.ts @@ -1,20 +1,9 @@ -/// - import { memcmp, memmove, memset } from "./util/memory"; import { E_NOTIMPLEMENTED } from "./util/error"; -// @ts-ignore: decorator -@builtin -export declare const HEAP_BASE: usize; - /** Memory manager interface. */ export namespace memory { - /** Whether the memory managed interface is implemented. */ - // @ts-ignore: decorator - @lazy - export const implemented: bool = isDefined(__mem_allocate); - /** Gets the size of the memory in pages. */ // @ts-ignore: decorator @builtin @@ -53,30 +42,6 @@ export namespace memory { throw new Error(E_NOTIMPLEMENTED); } - /** Dynamically allocates a section of memory and returns its address. */ - // @ts-ignore: decorator - @unsafe - export function allocate(size: usize): usize { - if (isDefined(__mem_allocate)) return __mem_allocate(size); - else throw new Error(E_NOTIMPLEMENTED); - } - - /** Dynamically frees a section of memory by the previously allocated address. */ - // @ts-ignore: decorator - @unsafe - export function free(ptr: usize): void { - if (isDefined(__mem_free)) __mem_free(ptr); - else throw new Error(E_NOTIMPLEMENTED); - } - - /** Resets the memory to its initial state. Arena allocator only. */ - // @ts-ignore: decorator - @unsafe - export function reset(): void { - if (isDefined(__mem_reset)) __mem_reset(); - else throw new Error(E_NOTIMPLEMENTED); - } - /** Repeats a section of memory at a specific address. */ // @ts-ignore: decorator @unsafe diff --git a/std/assembly/rt/README.md b/std/assembly/rt/README.md index 6e501d92..f7d48b25 100644 --- a/std/assembly/rt/README.md +++ b/std/assembly/rt/README.md @@ -8,29 +8,68 @@ It is based on [the TLSF memory manager](./tlsf.ts) and [a pure reference counti Interface --------- -* **__rt_allocate**(size: `usize`, id: `u32` = 0): `usize`
+* **__alloc**(size: `usize`, id: `u32` = 0): `usize`
Dynamically allocates a chunk of memory of at least the specified size and returns its address. Alignment is guaranteed to be 16 bytes to fit up to v128 values naturally. -* **__rt_reallocate**(ref: `usize`, size: `usize`): `usize`
+* **__realloc**(ref: `usize`, size: `usize`): `usize`
Dynamically changes the size of a chunk of memory, possibly moving it to a new address. -* **__rt_free**(ref: `usize`): `void`
+* **__free**(ref: `usize`): `void`
Frees a dynamically allocated chunk of memory by its address. -* **__rt_retain**(ref: `usize`): `void`
- Retains a reference. +* **__retain**(ref: `usize`): `void`
+ Retains a reference to an instance of a reference type. The instance doesn't become collected as long as there's at least one retained reference. -* **__rt_release**(ref: `usize`): `void`
- Releases a reference. +* **__release**(ref: `usize`): `void`
+ Releases a reference to an instance of a reference type. The instance is considered for collection once all references to it have been released. -* **__rt_collect**(): `void`
- Forces a full garbage collection cycle. +* **__collect**(): `void`
+ Forces a full garbage collection cycle. By default this means that reference cycles are resolved and possibly collected. -* **__rt_typeinfo**(id: `u32`): `void`
- Obtains the runtime type information for objects of the kind represented by the specified id. +* **__visit**(ref: `usize`, cookie: `u32`): `void`
+ Concrete visitor implementation called during traversal. Cookie can be used to indicate one of multiple operations. + +Built-ins +--------- + +The following functions are generated by the compiler based on compile-time information that wouldn't be available or inefficient to provide otherwise. + +* **__info**(id: `u32`): `void`
+ Obtains the runtime type information for objects with the specified runtime id. Runtime type information is a set of flags indicating whether a reference type is managed, an array or similar, and what the relevant alignments when creating an instance are etc. + +* **__visit_globals**(cookie: `u32`)
+ Calls `__visit` on each global that is of a reference type. Not used anymore (originally provided to support tracing GCs) but still here for possible future use. + +* **__visit_members**(ref: `usize`, cookie: `u32`)
+ Calls `__visit` on each member of the instance pointed to by `ref` that is of a reference type. Stub ---- -The fully functional yet minimal [stub implementation](./stub.ts) provides dynamic memory allocation only but doesn't include sophisticated support to deallocate objects. Useful for prototyping or very short-lived programs with hardly any memory footprint. +A fully functional yet minimal (as in code size) [stub implementation](./index-stub.ts) that provides dynamic memory allocation but no deallocation. Useful for prototyping or very short-lived programs with hardly any memory footprint. The [none implementation](./index-none.ts) is the same as the stub implementation without any runtime exports. + +Integration notes +----------------- + +Working with the runtime internals within standard library code can be tricky and requires knowledge of where the compiler will insert runtime calls automatically. For example, whenever a value of a reference type is assigned to a local, a global or a field, the compiler *might* insert a `__retain` call, respectively whenever such a value becomes unassigned from one, *might* insert a `__release` call. When a value is handled as an `usize` (i.e. when it comes from `__alloc` or is `changetype`ed), no such insertion happens (afterwards), but as soon as a `changetype`ed (again), the side-effects introduced by automatic insertion must be understood. + +A `__retain` call is inserted when a value of a reference type +* is assigned to a local, global or a field **if** the value is not already the exact same value as stored before +* is an argument to a function call, including `this` (i.e. `str.indexOf` retains `str`) +* is returned from a function (i.e. no need to manually `__retain` if explicitly `changetype`d) + +A `__release` call is inserted when a value of a reference type +* becomes unassigned from a local, global or a field due to assigning a new value **if** the value is not already the exact same value as stored before +* is popped together with its local from the current scope, i.e. a local declared with `let` in a block, or otherwise at the end of a function + +If not taken into account properly +* a memory leak will occur when `__retain`ed more often than intended +* a double-free will occur when `__release`d more often than intended + +Also note that a `load(x)` with a reference type acts like a `changetype(load(x))` and does not `__retain` unless the result is assigned to a local. + +Some best practices are: +* Use the fresh `__alloc`ed reference in `usize` form where possible, e.g. when just copying raw bytes is necessary, and `changetype` it once on return. +* When providing such a `usize` reference to a function, if the value isn't needed anymore afterwards, just `changetype` it on the call which will `__retain` and `__release` it automatically, including freeing it if wasn't retained before, or, if still needed afterwards, assign the `changetype`d reference to a local first and provide the local as the argument, hence keeping the reference alive as long as the local or any subsequent target is. +* If it's not avoidable to `changetype` to the actual reference type, do it inline in an expression and avoid assigning to a local. diff --git a/std/assembly/rt/common.ts b/std/assembly/rt/common.ts index 97af07d6..a1115d5b 100644 --- a/std/assembly/rt/common.ts +++ b/std/assembly/rt/common.ts @@ -12,10 +12,22 @@ // @ts-ignore: decorator @inline export const DEBUG = true; -/** Common block structure. */ -@unmanaged export class CommonBlock { +// ╒════════════════ Common block layout (32-bit) ═════════════════╕ +// 3 2 1 +// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits +// β”œβ”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”΄β”€β”€ +// β”‚ MM info β”‚ -16 +// β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +// β”‚ GC info β”‚ -12 +// β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +// β”‚ runtime id β”‚ -8 +// β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +// β”‚ runtime size β”‚ -4 +// β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•‘ +// β”‚ ... β”‚ ref +@unmanaged export class BLOCK { /** Memory manager info. */ - mmInfo: usize; // WASM64 might need adaption + mmInfo: usize; // WASM64 needs adaption /** Garbage collector info. */ gcInfo: u32; /** Runtime class id. */ @@ -24,16 +36,53 @@ rtSize: u32; } -/////////////////////////////////// Type information interface //////////////////////////////////// - -import { RTTI_BASE } from "../runtime"; -import { RTTIData } from "../common/rtti"; +// @ts-ignore: decorator +@inline export const BLOCK_OVERHEAD = (offsetof() + AL_MASK) & ~AL_MASK; // @ts-ignore: decorator -@global @unsafe -function __rt_typeinfo(id: u32): u32 { - var ptr: usize = RTTI_BASE; +@inline export const BLOCK_MAXSIZE: usize = (1 << 30) - BLOCK_OVERHEAD; + +/////////////////////////////////// Type information interface //////////////////////////////////// + +import { RTTI_BASE } from "builtins"; +import { RTTIData, RTTIFlags } from "common/rtti"; + +// @ts-ignore: decorator +@unsafe @global +export function __typeinfo(id: u32): RTTIFlags { + var ptr = RTTI_BASE; return !id || id > load(ptr) ? unreachable() : changetype(ptr + id * offsetof()).flags; } + +// @ts-ignore: decorator +@unsafe @global +export function __instanceof(ref: usize, superId: u32): bool { // keyword + var id = changetype(ref - BLOCK_OVERHEAD).rtId; + var ptr = RTTI_BASE; + if (id && id <= load(ptr)) { + do if (id == superId) return true; + while (id = changetype(ptr + id * offsetof()).base); + } + return false; +} + +///////////////////////////////////////////// Helpers ///////////////////////////////////////////// + +import { idof } from "builtins"; +import { ArrayBufferView } from "arraybuffer"; + +// @ts-ignore: decorator +@unsafe @global +export function __allocArray(length: i32, alignLog2: usize, id: u32, data: usize = 0): usize { + var array = __alloc(offsetof(), id); + var bufferSize = length << alignLog2; + var buffer = __alloc(bufferSize, idof()); + changetype(array).data = changetype(buffer); // TODO/RT: retains + changetype(array).dataStart = buffer; + changetype(array).dataLength = bufferSize; + store(changetype(array), length, offsetof("length_")); + if (data) memory.copy(buffer, data, bufferSize); + return array; +} diff --git a/std/assembly/rt/index-full.ts b/std/assembly/rt/index-full.ts new file mode 100644 index 00000000..cba6fca6 --- /dev/null +++ b/std/assembly/rt/index-full.ts @@ -0,0 +1,3 @@ +export { __alloc, __realloc, __free } from "./tlsf"; +export { __retain, __release, __collect } from "./purerc"; +export { __instanceof, __typeinfo } from "./common"; diff --git a/std/assembly/rt/index-none.ts b/std/assembly/rt/index-none.ts new file mode 100644 index 00000000..f3b0d9ad --- /dev/null +++ b/std/assembly/rt/index-none.ts @@ -0,0 +1 @@ +import "rt/index-stub"; diff --git a/std/assembly/rt/stub.ts b/std/assembly/rt/index-stub.ts similarity index 55% rename from std/assembly/rt/stub.ts rename to std/assembly/rt/index-stub.ts index 0244d6cd..132db8c2 100644 --- a/std/assembly/rt/stub.ts +++ b/std/assembly/rt/index-stub.ts @@ -1,12 +1,4 @@ -import { AL_MASK, CommonBlock } from "./common"; - -// @ts-ignore: decorator -@inline -const BLOCK_OVERHEAD = (offsetof() + AL_MASK) & ~AL_MASK; - -// @ts-ignore: decorator -@inline -const BLOCK_MAXSIZE: usize = (1 << 30) - BLOCK_OVERHEAD; // match TLSF +import { AL_MASK, BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "rt/common"; // @ts-ignore: decorator @lazy @@ -16,11 +8,9 @@ var startOffset: usize = (HEAP_BASE + AL_MASK) & ~AL_MASK; @lazy var offset: usize = startOffset; -//////////////////////////////////// Memory manager interface ///////////////////////////////////// - // @ts-ignore: decorator @unsafe @global -export function __rt_allocate(size: usize, id: u32): usize { +export function __alloc(size: usize, id: u32): usize { if (size > BLOCK_MAXSIZE) unreachable(); var ptr = offset + BLOCK_OVERHEAD; var newPtr = (ptr + max(size, 1) + AL_MASK) & ~AL_MASK; @@ -33,7 +23,7 @@ export function __rt_allocate(size: usize, id: u32): usize { } } offset = newPtr; - var block = changetype(ptr - BLOCK_OVERHEAD); + var block = changetype(ptr - BLOCK_OVERHEAD); block.rtId = id; block.rtSize = size; return ptr; @@ -41,11 +31,11 @@ export function __rt_allocate(size: usize, id: u32): usize { // @ts-ignore: decorator @unsafe @global -export function __rt_reallocate(ref: usize, size: usize): usize { - var block = changetype(ref - BLOCK_OVERHEAD); +export function __realloc(ref: usize, size: usize): usize { + var block = changetype(ref - BLOCK_OVERHEAD); var oldSize = block.rtSize; if (size > oldSize) { - let newRef = __rt_allocate(size, block.rtId); + let newRef = __alloc(size, block.rtId); memory.copy(newRef, ref, oldSize); ref = newRef; } else { @@ -56,30 +46,40 @@ export function __rt_reallocate(ref: usize, size: usize): usize { // @ts-ignore: decorator @unsafe @global -export function __rt_free(ref: usize): void { +export function __free(ref: usize): void { } // @ts-ignore: decorator -@unsafe @global -export function __rt_reset(): void { // special - offset = startOffset; -} - -/////////////////////////////////// Garbage collector interface /////////////////////////////////// +// @unsafe @global +// export function __reset(): void { // special +// offset = startOffset; +// } // @ts-ignore: decorator @global @unsafe -export function __rt_retain(ref: usize): void { +export function __retain(ref: usize): usize { + return ref; } // @ts-ignore: decorator @global @unsafe -export function __rt_release(ref: usize): void { +export function __release(ref: usize): void { } // @ts-ignore: decorator @global @unsafe -export function __rt_collect(): void { +function __visit(ref: usize, cookie: u32): void { } -export { __rt_typeinfo }; +// @ts-ignore: decorator +@global @unsafe +function __retainRelease(ref: usize, oldRef: usize): usize { + return ref; +} + +// @ts-ignore: decorator +@global @unsafe +export function __collect(): void { +} + +export { __instanceof, __typeinfo } from "rt/common"; diff --git a/std/assembly/rt/index.d.ts b/std/assembly/rt/index.d.ts new file mode 100644 index 00000000..7944eab5 --- /dev/null +++ b/std/assembly/rt/index.d.ts @@ -0,0 +1,13 @@ +declare function __alloc(size: usize, id: u32): usize; +declare function __realloc(ref: usize, size: usize): usize; +declare function __free(ref: usize): void; +declare function __retain(ref: usize): void; +declare function __release(ref: usize): void; +declare function __retainRelease(ref: usize, oldRef: usize): usize; +declare function __collect(): void; +declare function __typeinfo(id: u32): u32; +declare function __instanceof(ref: usize, superId: u32): bool; +declare function __visit(ref: usize, cookie: i32): void; +declare function __visit_globals(cookie: u32): void; +declare function __visit_members(ref: usize, cookie: u32): void; +declare function __allocArray(length: i32, alignLog2: usize, id: u32, data?: usize): usize; diff --git a/std/assembly/rt/index.ts b/std/assembly/rt/index.ts deleted file mode 100644 index f9baadc7..00000000 --- a/std/assembly/rt/index.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { AL_MASK, DEBUG } from "./common"; - -//////////////////////////////////// Memory manager interface ///////////////////////////////////// - -import { ROOT, Block, BLOCK_OVERHEAD, initializeRoot, allocateBlock, reallocateBlock, freeBlock } from "./tlsf"; - -// @ts-ignore: decorator -@global @unsafe -export function __rt_allocate(size: usize, id: u32): usize { - var root = ROOT; - if (!root) { - initializeRoot(); - root = ROOT; - } - var block = allocateBlock(root, size); - block.rtId = id; - return changetype(block) + BLOCK_OVERHEAD; -} - -// @ts-ignore: decorator -@global @unsafe -export function __rt_reallocate(ref: usize, size: usize): usize { - if (DEBUG) assert(ROOT); // must be initialized - assert(ref != 0 && !(ref & AL_MASK)); // must exist and be aligned - return changetype(reallocateBlock(ROOT, changetype(ref - BLOCK_OVERHEAD), size)) + BLOCK_OVERHEAD; -} - -// @ts-ignore: decorator -@global @unsafe -export function __rt_free(ref: usize): void { - if (DEBUG) assert(ROOT); // must be initialized - assert(ref != 0 && !(ref & AL_MASK)); // must exist and be aligned - freeBlock(ROOT, changetype(ref - BLOCK_OVERHEAD)); -} - -/////////////////////////////////// Garbage collector interface /////////////////////////////////// - -import { increment, decrement, collectCycles } from "./pure"; - -// @ts-ignore: decorator -@global @unsafe -export function __rt_retain(ref: usize): void { - if (ref) increment(changetype(ref - BLOCK_OVERHEAD)); -} - -// @ts-ignore: decorator -@global @unsafe -export function __rt_release(ref: usize): void { - if (ref) decrement(changetype(ref - BLOCK_OVERHEAD)); -} - -// @ts-ignore: decorator -@global @unsafe -export function __rt_collect(): void { - collectCycles(); -} - -export { __rt_typeinfo }; diff --git a/std/assembly/rt/pure.ts b/std/assembly/rt/purerc.ts similarity index 83% rename from std/assembly/rt/pure.ts rename to std/assembly/rt/purerc.ts index 80c90dcf..0dfd7d06 100644 --- a/std/assembly/rt/pure.ts +++ b/std/assembly/rt/purerc.ts @@ -1,14 +1,10 @@ -import { DEBUG } from "./common"; -import { Block, freeBlock, ROOT } from "./tlsf"; +import { DEBUG, BLOCK_OVERHEAD, BLOCK } from "rt/common"; +import { Block, freeBlock, ROOT } from "rt/tlsf"; +import { RTTIFlags } from "common/rtti"; /////////////////////////// A Pure Reference Counting Garbage Collector /////////////////////////// // see: https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon03Pure.pdf -// TODO: make visitors eat cookies so we can compile direct calls into a switch -function __rt_visit_members(s: Block, cookie: i32): void { unreachable(); } -function __rt_flags(classId: u32): u32 { return unreachable(); } -const ACYCLIC_FLAG: u32 = 0; - // ╒══════════════════════ GC Info structure ══════════════════════╕ // β”‚ 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β”‚ @@ -66,7 +62,9 @@ const ACYCLIC_FLAG: u32 = 0; // @ts-ignore: decorator @global -function __rt_visit(s: Block, cookie: i32): void { +function __visit(ref: usize, cookie: i32): void { + if (ref < HEAP_BASE) return; + var s = changetype(ref - BLOCK_OVERHEAD); switch (cookie) { case VISIT_DECREMENT: { decrement(s); @@ -100,18 +98,18 @@ function __rt_visit(s: Block, cookie: i32): void { } /** Increments the reference count of the specified block by one.*/ -export function increment(s: Block): void { +function increment(s: Block): void { var info = s.gcInfo; assert((info & ~REFCOUNT_MASK) == ((info + 1) & ~REFCOUNT_MASK)); // overflow s.gcInfo = info + 1; } /** Decrements the reference count of the specified block by one, possibly freeing it. */ -export function decrement(s: Block): void { +function decrement(s: Block): void { var info = s.gcInfo; var rc = info & REFCOUNT_MASK; if (rc == 1) { - __rt_visit_members(s, VISIT_DECREMENT); + __visit_members(changetype(s) + BLOCK_OVERHEAD, VISIT_DECREMENT); if (!(info & BUFFERED_MASK)) { freeBlock(ROOT, s); } else { @@ -119,7 +117,7 @@ export function decrement(s: Block): void { } } else { if (DEBUG) assert(rc > 0); - if (!(__rt_flags(s.rtId) & ACYCLIC_FLAG)) { + if (!(__typeinfo(s.rtId) & RTTIFlags.ACYCLIC)) { s.gcInfo = BUFFERED_MASK | COLOR_PURPLE | (rc - 1); if (!(info & BUFFERED_MASK)) { appendRoot(s); @@ -164,7 +162,9 @@ function growRoots(): void { } /** Collects cyclic garbage. */ -export function collectCycles(): void { +// @ts-ignore: decorator +@global @unsafe +export function __collect(): void { // markRoots var roots = ROOTS; @@ -205,7 +205,7 @@ function markGray(s: Block): void { var info = s.gcInfo; if ((info & COLOR_MASK) != COLOR_GRAY) { s.gcInfo = (info & ~COLOR_MASK) | COLOR_GRAY; - __rt_visit_members(s, VISIT_MARKGRAY); + __visit_members(changetype(s) + BLOCK_OVERHEAD, VISIT_MARKGRAY); } } @@ -217,7 +217,7 @@ function scan(s: Block): void { scanBlack(s); } else { s.gcInfo = (info & ~COLOR_MASK) | COLOR_WHITE; - __rt_visit_members(s, VISIT_SCAN); + __visit_members(changetype(s) + BLOCK_OVERHEAD, VISIT_SCAN); } } } @@ -225,7 +225,7 @@ function scan(s: Block): void { /** Marks a block as black (in use) if it was found to be reachable during the collection phase. */ function scanBlack(s: Block): void { s.gcInfo = (s.gcInfo & ~COLOR_MASK) | COLOR_BLACK; - __rt_visit_members(s, VISIT_SCANBLACK); + __visit_members(changetype(s) + BLOCK_OVERHEAD, VISIT_SCANBLACK); } /** Collects all white (member of a garbage cycle) nodes when completing the collection phase. */ @@ -233,7 +233,31 @@ function collectWhite(s: Block): void { var info = s.gcInfo; if ((info & COLOR_MASK) == COLOR_WHITE && !(info & BUFFERED_MASK)) { // s.gcInfo = (info & ~COLOR_MASK) | COLOR_BLACK; - __rt_visit_members(s, VISIT_COLLECTWHITE); + __visit_members(changetype(s) + BLOCK_OVERHEAD, VISIT_COLLECTWHITE); } freeBlock(ROOT, s); } + +// @ts-ignore: decorator +@global @unsafe +export function __retain(ref: usize): usize { + if (ref > HEAP_BASE) increment(changetype(ref - BLOCK_OVERHEAD)); + return ref; +} + +// @ts-ignore: decorator +@global @unsafe +export function __release(ref: usize): void { + if (ref > HEAP_BASE) decrement(changetype(ref - BLOCK_OVERHEAD)); +} + +// @ts-ignore: decorator +@global @unsafe +export function __retainRelease(ref: usize, oldRef: usize): usize { + if (ref != oldRef) { + let heapBase = HEAP_BASE; + if (ref > heapBase) increment(changetype(ref - BLOCK_OVERHEAD)); + if (oldRef > heapBase) decrement(changetype(oldRef - BLOCK_OVERHEAD)); + } + return ref; +} diff --git a/std/assembly/rt/tlsf.ts b/std/assembly/rt/tlsf.ts index 8915ded0..e9adfcf2 100644 --- a/std/assembly/rt/tlsf.ts +++ b/std/assembly/rt/tlsf.ts @@ -1,4 +1,4 @@ -import { AL_BITS, AL_SIZE, AL_MASK, DEBUG, CommonBlock } from "./common"; +import { AL_BITS, AL_MASK, DEBUG, BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "rt/common"; /////////////////////// The TLSF (Two-Level Segregate Fit) memory allocator /////////////////////// // see: http://www.gii.upv.es/tlsf/ @@ -69,7 +69,7 @@ import { AL_BITS, AL_SIZE, AL_MASK, DEBUG, CommonBlock } from "./common"; // β”‚ if free: back β–² β”‚ β—„β”€β”˜ // β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ payload β”˜ >= MIN SIZE // F: FREE, L: LEFTFREE -@unmanaged export class Block extends CommonBlock { +@unmanaged export class Block extends BLOCK { /** Previous free block, if any. Only valid if free, otherwise part of payload. */ prev: Block | null; @@ -79,15 +79,13 @@ import { AL_BITS, AL_SIZE, AL_MASK, DEBUG, CommonBlock } from "./common"; // If the block is free, there is a 'back'reference at its end pointing at its start. } -// Block constants. Overhead is always present, no matter if free or used. Also, a block must have -// a minimum size of three pointers so it can hold `prev`, `next` and `back` if free. +// Block constants. A block must have a minimum size of three pointers so it can hold `prev`, +// `next` and `back` if free. // @ts-ignore: decorator -@inline export const BLOCK_OVERHEAD: usize = (offsetof("prev") + AL_MASK) & ~AL_MASK; +@inline const BLOCK_MINSIZE: usize = (3 * sizeof() + AL_MASK) & ~AL_MASK; // prev + next + back // @ts-ignore: decorator -@inline const BLOCK_MINSIZE: usize = (3 * sizeof() + AL_MASK) & ~AL_MASK;// prev + next + back -// @ts-ignore: decorator -@inline const BLOCK_MAXSIZE: usize = 1 << (FL_BITS + SB_BITS - 1); // exclusive +// @inline const BLOCK_MAXSIZE: usize = 1 << (FL_BITS + SB_BITS - 1); // exclusive, lives in common.ts /** Gets the left block of a block. Only valid if the left block is free. */ // @ts-ignore: decorator @@ -532,3 +530,32 @@ export function freeBlock(root: Root, block: Block): void { block.mmInfo = blockInfo | FREE; insertBlock(root, block); } + +// @ts-ignore: decorator +@global @unsafe +export function __alloc(size: usize, id: u32): usize { + var root = ROOT; + if (!root) { + initializeRoot(); + root = ROOT; + } + var block = allocateBlock(root, size); + block.rtId = id; + return changetype(block) + BLOCK_OVERHEAD; +} + +// @ts-ignore: decorator +@global @unsafe +export function __realloc(ref: usize, size: usize): usize { + if (DEBUG) assert(ROOT); // must be initialized + assert(ref != 0 && !(ref & AL_MASK)); // must exist and be aligned + return changetype(reallocateBlock(ROOT, changetype(ref - BLOCK_OVERHEAD), size)) + BLOCK_OVERHEAD; +} + +// @ts-ignore: decorator +@global @unsafe +export function __free(ref: usize): void { + if (DEBUG) assert(ROOT); // must be initialized + assert(ref != 0 && !(ref & AL_MASK)); // must exist and be aligned + freeBlock(ROOT, changetype(ref - BLOCK_OVERHEAD)); +} diff --git a/std/assembly/runtime.ts b/std/assembly/runtime.ts deleted file mode 100644 index 6adfb1b2..00000000 --- a/std/assembly/runtime.ts +++ /dev/null @@ -1,148 +0,0 @@ -// The runtime provides common functionality that links runtime interfaces for memory management -// and garbage collection to the standard library, making sure it all plays well together. - -import { HEADER, HEADER_SIZE, allocate, register } from "./util/runtime"; -import { E_NOTIMPLEMENTED } from "./util/error"; -import { ArrayBufferView } from "./arraybuffer"; -import { RTTIFlags, RTTIData } from "./common/rtti"; - -// @ts-ignore: decorator -@builtin -export declare const RTTI_BASE: usize; - -/** Gets the computed unique id of a class type. */ -// @ts-ignore: decorator -@builtin -export declare function __runtime_id(): u32; - -/** Marks root objects when a tracing GC is present. */ -// @ts-ignore: decorator -@unsafe @builtin -export declare function __gc_mark_roots(): void; - -/** Marks class members when a tracing GC is present. */ -// @ts-ignore: decorator -@unsafe @builtin -export declare function __gc_mark_members(classId: u32, ref: usize): void; - -/** Runtime implementation. */ -@unmanaged -export class runtime { - private constructor() { return unreachable(); } - - /** Determines whether a managed object is considered to be an instance of the class represented by the specified runtime id. */ - static instanceof(ref: usize, superId: u32): bool { // keyword - var id = changetype
(ref - HEADER_SIZE).classId; - var ptr = RTTI_BASE; - if (id && id <= load(ptr)) { - do if (id == superId) return true; - while (id = changetype(ptr + id * offsetof()).base); - } - return false; - } -} -export namespace runtime { - - /** Gets the runtime flags of the managed type represented by the specified runtime id. */ - export function flags(id: u32): RTTIFlags { - var ptr = RTTI_BASE; - return !id || id > load(ptr) - ? unreachable() - : changetype(ptr + id * offsetof()).flags; - } - - /** Allocates and registers, but doesn't initialize the data of, a new managed object of the specified kind. */ - // @ts-ignore: decorator - @unsafe - export function newObject(payloadSize: u32, id: u32): usize { - return register(allocate(payloadSize), id); - } - - /** Allocates and registers, but doesn't initialize the data of, a new `String` of the specified length. */ - // @ts-ignore: decorator - @unsafe - export function newString(length: i32): usize { - return newObject(length << 1, __runtime_id()); - } - - /** Allocates and registers, but doesn't initialize the data of, a new `ArrayBuffer` of the specified byteLength. */ - // @ts-ignore: decorator - @unsafe - export function newArrayBuffer(byteLength: i32): usize { - return newObject(byteLength, __runtime_id()); - } - - /** Allocates and registers a new `Array` of the specified kind using the given backing buffer. */ - // @ts-ignore: decorator - @unsafe - export function newArray(id: u32, buffer: usize): usize { - var flags = runtime.flags(id); // traps if invalid - var alignLog2 = (flags / RTTIFlags.VALUE_ALIGN_0) & 31; - var byteLength: i32; - if (!buffer) buffer = newArrayBuffer(byteLength = 0); - else byteLength = changetype(buffer).byteLength; - var array = newObject(id, offsetof()); - changetype(array).data = changetype(buffer); // links - changetype(array).dataStart = buffer; - changetype(array).dataLength = byteLength; - store(changetype(array), byteLength >>> alignLog2, offsetof("length_")); - if (flags & RTTIFlags.VALUE_MANAGED) { - let cur = buffer; - let end = cur + byteLength; - while (cur < end) { - let ref = load(cur); - if (ref) { - if (isDefined(__ref_link)) __ref_link(ref, array); - else if (isDefined(__ref_retain)) __ref_retain(ref); - else assert(false); - } - cur += sizeof(); - } - } - return array; - } - - /** Retains a managed object externally, making sure that it doesn't become collected. */ - // @ts-ignore: decorator - @unsafe - export function retain(ref: usize): void { - if (isDefined(__ref_collect)) { - if (isDefined(__ref_link)) __ref_link(ref, changetype(ROOT)); - else if (isDefined(__ref_retain)) __ref_retain(ref); - } - } - - /** Releases a managed object externally, allowing it to become collected. */ - // @ts-ignore: decorator - @unsafe - export function release(ref: usize): void { - if (isDefined(__ref_collect)) { - if (isDefined(__ref_unlink)) __ref_unlink(ref, changetype(ROOT)); - else if (isDefined(__ref_release)) __ref_release(ref); - } - } - - /** Performs a full garbage collection cycle. */ - // @ts-ignore: decorator - @unsafe - export function collect(): void { - // FIXME: annotated unsafe because calling it in the middle of a function collects inner - // references prematurely with a tracing GC, which is pretty bad actually. - - // function explode(): Ref { - // var ref = new Ref(); - // gc.collect(); // collects ref - // return ref; - // } - - if (isDefined(__ref_collect)) __ref_collect(); - else throw new Error(E_NOTIMPLEMENTED); - } -} - -class Root {} - -/** A root object to retain managed objects on externally. */ -// @ts-ignore -@lazy -var ROOT = new Root(); diff --git a/std/assembly/runtime/README.md b/std/assembly/runtime/README.md deleted file mode 100644 index 22d4f4ba..00000000 --- a/std/assembly/runtime/README.md +++ /dev/null @@ -1,38 +0,0 @@ -AssemblyScript runtimes -======================= - -None ----- - -``` -$> asc ... --runtime none -``` - -[No runtime](./none.ts) features at all. Useful for building low-level modules that do not require language features like managed classes, or if you'd like to compose your own runtime by including a custom memory allocator and garbage collector. - -* No memory allocator -* No garbage collector - -Trace ------ - -``` -$> asc ... -``` - -The [trace runtime](./trace.ts) adds support for dynamic memory management and garbage collection to your program. - -* [TLSF memory allocator](../allocator/tlsf.ts) -* [ITCM garbage collector](../collector/itcm.ts) - -Arena ------ - -``` -$> asc ... --runtime arena -``` - -The [arena runtime](./arena.ts) is just enough to make most language features work, but doesn't have sophisticated support for freeing memory. Useful when prototyping or for simple one-shot modules in that it produces very small modules with minimal overhead. - -* [Arena memory allocator](../allocator/arena.ts) with `memory.reset()` -* No garbage collector diff --git a/std/assembly/runtime/arena.ts b/std/assembly/runtime/arena.ts deleted file mode 100644 index 4d869414..00000000 --- a/std/assembly/runtime/arena.ts +++ /dev/null @@ -1,3 +0,0 @@ -import "allocator/arena"; - -export { runtime as $ }; diff --git a/std/assembly/runtime/none.ts b/std/assembly/runtime/none.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/std/assembly/runtime/trace.ts b/std/assembly/runtime/trace.ts deleted file mode 100644 index 29be1ab6..00000000 --- a/std/assembly/runtime/trace.ts +++ /dev/null @@ -1,4 +0,0 @@ -import "allocator/tlsf"; -import "collector/itcm"; - -export { runtime as $ }; diff --git a/std/assembly/set.ts b/std/assembly/set.ts index 367e3b3d..cda13835 100644 --- a/std/assembly/set.ts +++ b/std/assembly/set.ts @@ -1,7 +1,6 @@ -/// +/// import { HASH } from "./util/hash"; -import { __runtime_id, __gc_mark_members } from "./runtime"; // A deterministic hash set based on CloseTable from https://github.com/jorendorff/dht @@ -79,7 +78,7 @@ export class Set { } private find(key: K, hashCode: u32): SetEntry | null { - var entry = load>( + var entry = load>( // unmanaged! changetype(this.buckets) + (hashCode & this.bucketsMask) * BUCKET_SIZE ); while (entry) { @@ -106,23 +105,10 @@ export class Set { ); } // append new entry - let entries = this.entries; - entry = changetype>(changetype(entries) + this.entriesOffset++ * ENTRY_SIZE()); - entry.key = key; - // link with the set - if (isManaged()) { - if (isNullable()) { - if (key !== null) { - if (isDefined(__ref_link)) __ref_link(changetype(key), changetype(this)); - else if (isDefined(__ref_retain)) __ref_retain(changetype(key)); - else assert(false); - } - } else { - if (isDefined(__ref_link)) __ref_link(changetype(key), changetype(this)); - else if (isDefined(__ref_retain)) __ref_retain(changetype(key)); - else assert(false); - } - } + entry = changetype>(changetype(this.entries) + this.entriesOffset++ * ENTRY_SIZE()); + entry.key = isManaged() + ? changetype(__retain(changetype(key))) + : key; ++this.entriesCount; // link with previous entry in bucket let bucketPtrBase = changetype(this.buckets) + (hashCode & this.bucketsMask) * BUCKET_SIZE; @@ -132,24 +118,9 @@ export class Set { } delete(key: K): bool { - var entry = this.find(key, HASH(key)); + var entry = this.find(key, HASH(key)); // unmanaged! if (!entry) return false; - if (isManaged()) { - key = entry.key; // exact, e.g. string - if (isNullable()) { - if (key !== null) { - if (isDefined(__ref_link)) { - if (isDefined(__ref_unlink)) __ref_unlink(changetype(key), changetype(this)); - } else if (isDefined(__ref_retain)) __ref_release(changetype(key)); - else assert(false); - } - } else { - if (isDefined(__ref_link)) { - if (isDefined(__ref_unlink)) __ref_unlink(changetype(key), changetype(this)); - } else if (isDefined(__ref_retain)) __ref_release(changetype(key)); - else assert(false); - } - } + if (isManaged()) __release(changetype(entry.key)); // exact 'key' entry.taggedNext |= EMPTY; --this.entriesCount; // check if rehashing is appropriate @@ -172,9 +143,9 @@ export class Set { var oldEnd = oldPtr + this.entriesOffset * ENTRY_SIZE(); var newPtr = changetype(newEntries); while (oldPtr != oldEnd) { - let oldEntry = changetype>(oldPtr); + let oldEntry = changetype>(oldPtr); // unmanaged! if (!(oldEntry.taggedNext & EMPTY)) { - let newEntry = changetype>(newPtr); + let newEntry = changetype>(newPtr); // unmanaged! newEntry.key = oldEntry.key; let newBucketIndex = HASH(oldEntry.key) & newBucketsMask; let newBucketPtrBase = changetype(newBuckets) + newBucketIndex * BUCKET_SIZE; @@ -198,10 +169,10 @@ export class Set { // GC integration - @unsafe private __traverse(): void { - __ref_mark(changetype(this.buckets)); + @unsafe private __traverse(cookie: u32): void { + __visit(changetype(this.buckets), cookie); var entries = this.entries; - __ref_mark(changetype(entries)); + __visit(changetype(entries), cookie); if (isManaged()) { let cur = changetype(entries); let end = cur + this.entriesOffset * ENTRY_SIZE(); @@ -211,12 +182,12 @@ export class Set { let val = changetype(entry.key); if (isNullable()) { if (val) { - __ref_mark(val); - __gc_mark_members(__runtime_id(), val); + __visit(val, cookie); + __visit_members(val, cookie); } } else { - __ref_mark(val); - __gc_mark_members(__runtime_id(), val); + __visit(val, cookie); + __visit_members(val, cookie); } } cur += ENTRY_SIZE(); diff --git a/std/assembly/string.ts b/std/assembly/string.ts index 98c8898a..f100de23 100644 --- a/std/assembly/string.ts +++ b/std/assembly/string.ts @@ -1,27 +1,26 @@ -/// +/// -import { MAX_SIZE_32 } from "./util/allocator"; -import { HEADER, HEADER_SIZE, allocate, register, NEWARRAY } from "./util/runtime"; +import { BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "./rt/common"; import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./util/string"; import { E_INVALIDLENGTH } from "./util/error"; -import { __runtime_id } from "./runtime"; import { ArrayBufferView } from "./arraybuffer"; +import { idof } from "./builtins"; @sealed export abstract class String { - @lazy static readonly MAX_LENGTH: i32 = (MAX_SIZE_32 - HEADER_SIZE) >> alignof(); + @lazy static readonly MAX_LENGTH: i32 = BLOCK_MAXSIZE >>> alignof(); // TODO Add and handle second argument - static fromCharCode(code: i32): String { - var out = allocate(2); + static fromCharCode(code: i32): string { + var out = __alloc(2, idof()); store(out, code); - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } - static fromCodePoint(code: i32): String { + static fromCodePoint(code: i32): string { assert(code <= 0x10FFFF); var sur = code > 0xFFFF; - var out = allocate((i32(sur) + 1) << 1); + var out = __alloc((i32(sur) + 1) << 1, idof()); if (!sur) { store(out, code); } else { @@ -30,25 +29,19 @@ import { ArrayBufferView } from "./arraybuffer"; let lo: u32 = (code & 0x3FF) + 0xDC00; store(out, (hi << 16) | lo); } - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } - // @ts-ignore: decorator - // @unsafe - // constructor(length: i32) { - // return changetype(register(allocate(length << 1), __runtime_id())); - // } - get length(): i32 { - return changetype
(changetype(this) - HEADER_SIZE).payloadSize >> 1; + return changetype(changetype(this) - BLOCK_OVERHEAD).rtSize >> 1; } @operator("[]") charAt(pos: i32): String { assert(this !== null); if (pos >= this.length) return changetype(""); - var out = allocate(2); + var out = __alloc(2, idof()); store(out, load(changetype(this) + (pos << 1))); - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } charCodeAt(pos: i32): i32 { @@ -75,10 +68,10 @@ import { ArrayBufferView } from "./arraybuffer"; var otherSize: isize = other.length << 1; var outSize: usize = thisSize + otherSize; if (outSize == 0) return changetype(""); - var out = allocate(outSize); + var out = __alloc(outSize, idof()); memory.copy(out, changetype(this), thisSize); memory.copy(out + thisSize, changetype(other), otherSize); - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } endsWith(searchString: String, endPosition: i32 = String.MAX_LENGTH): bool { @@ -194,9 +187,9 @@ import { ArrayBufferView } from "./arraybuffer"; if (intStart < 0) intStart = max(size + intStart, 0); var resultLength = min(max(end, 0), size - intStart); if (resultLength <= 0) return changetype(""); - var out = allocate(resultLength << 1); + var out = __alloc(resultLength << 1, idof()); memory.copy(out, changetype(this) + intStart, resultLength); - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } substring(start: i32, end: i32 = i32.MAX_VALUE): String { @@ -209,9 +202,9 @@ import { ArrayBufferView } from "./arraybuffer"; len = toPos - fromPos; if (!len) return changetype(""); if (!fromPos && toPos == this.length << 1) return this; - var out = allocate(len); + var out = __alloc(len, idof()); memory.copy(out, changetype(this) + fromPos, len); - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } trim(): String { @@ -237,9 +230,9 @@ import { ArrayBufferView } from "./arraybuffer"; } if (!size) return changetype(""); if (!start && size == length << 1) return this; - var out = allocate(size); + var out = __alloc(size, idof()); memory.copy(out, changetype(this) + offset, size); - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } @inline @@ -267,9 +260,9 @@ import { ArrayBufferView } from "./arraybuffer"; if (!offset) return this; size -= offset; if (!size) return changetype(""); - var out = allocate(size); + var out = __alloc(size, idof()); memory.copy(out, changetype(this) + offset, size); - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } trimEnd(): String { @@ -286,9 +279,9 @@ import { ArrayBufferView } from "./arraybuffer"; } if (!size) return changetype(""); if (size == originalSize) return this; - var out = allocate(size); + var out = __alloc(size, idof()); memory.copy(out, changetype(this), size); - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } padStart(targetLength: i32, padString: string = " "): String { @@ -298,7 +291,7 @@ import { ArrayBufferView } from "./arraybuffer"; var padSize = padString.length << 1; if (targetSize < thisSize || !padSize) return this; var prependSize = targetSize - thisSize; - var out = allocate(targetSize); + var out = __alloc(targetSize, idof()); if (prependSize > padSize) { let repeatCount = (prependSize - 2) / padSize; let restBase = repeatCount * padSize; @@ -309,7 +302,7 @@ import { ArrayBufferView } from "./arraybuffer"; memory.copy(out, changetype(padString), prependSize); } memory.copy(out + prependSize, changetype(this), thisSize); - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } padEnd(targetLength: i32, padString: string = " "): String { @@ -319,7 +312,7 @@ import { ArrayBufferView } from "./arraybuffer"; var padSize = padString.length << 1; if (targetSize < thisSize || !padSize) return this; var appendSize = targetSize - thisSize; - var out = allocate(targetSize); + var out = __alloc(targetSize, idof()); memory.copy(out, changetype(this), thisSize); if (appendSize > padSize) { let repeatCount = (appendSize - 2) / padSize; @@ -330,7 +323,7 @@ import { ArrayBufferView } from "./arraybuffer"; } else { memory.copy(out + thisSize, changetype(padString), appendSize); } - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } repeat(count: i32 = 0): String { @@ -344,9 +337,9 @@ import { ArrayBufferView } from "./arraybuffer"; if (count == 0 || !length) return changetype(""); if (count == 1) return this; - var out = allocate((length * count) << 1); + var out = __alloc((length * count) << 1, idof()); memory.repeat(out, changetype(this), length << 1, count); - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } slice(beginIndex: i32, endIndex: i32 = i32.MAX_VALUE): String { @@ -355,68 +348,64 @@ import { ArrayBufferView } from "./arraybuffer"; var end = endIndex < 0 ? max(endIndex + len, 0) : min(endIndex, len); len = end - begin; if (len <= 0) return changetype(""); - var out = allocate(len << 1); + var out = __alloc(len << 1, idof()); memory.copy(out, changetype(this) + (begin << 1), len << 1); - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } split(separator: String | null = null, limit: i32 = i32.MAX_VALUE): String[] { assert(this !== null); - if (!limit) return NEWARRAY(0); + if (!limit) return changetype>(__allocArray(0, alignof(), idof>())); // retains if (separator === null) return [this]; var length: isize = this.length; var sepLen: isize = separator.length; if (limit < 0) limit = i32.MAX_VALUE; if (!sepLen) { - if (!length) return NEWARRAY(0); + if (!length) return changetype>(__allocArray(0, alignof(), idof>())); // retains // split by chars length = min(length, limit); - let result = NEWARRAY(length); + let result = __allocArray(length, alignof(), idof>()); let resultStart = changetype(result).dataStart; for (let i: isize = 0; i < length; ++i) { - let charStr = allocate(2); + let charStr = __alloc(2, idof()); store(charStr, load(changetype(this) + (i << 1))); store(resultStart + (i << alignof()), charStr); // result[i] = charStr - register(charStr, __runtime_id()); - if (isManaged()) { - if (isDefined(__ref_link)) __ref_link(changetype(charStr), changetype(result)); - if (isDefined(__ref_retain)) __ref_retain(changetype(charStr)); - } + if (isManaged()) __retain(charStr); } - return result; + return changetype>(result); // retains } else if (!length) { - let result = NEWARRAY(1); - store(changetype(result).dataStart, ""); // no need to register/link - return result; + let result = __allocArray(1, alignof(), idof>()); + store(changetype(result).dataStart, changetype("")); // static "" + return changetype>(result); // retains } - var result = NEWARRAY(0); + var result = changetype>(__allocArray(0, alignof(), idof>())); // retains var end = 0, start = 0, i = 0; while ((end = this.indexOf(separator, start)) != -1) { let len = end - start; if (len > 0) { - let out = allocate(len << 1); + let out = __alloc(len << 1, idof()); memory.copy(out, changetype(this) + (start << 1), len << 1); - result.push(changetype(register(out, __runtime_id()))); + result.push(changetype(out)); } else { result.push(changetype("")); } - if (++i == limit) return result; + if (++i == limit) return changetype>(result); // retains start = end + sepLen; } - if (!start) { - let result = NEWARRAY(1); - unchecked(result[0] = this); - return result; + if (!start) { // also means: loop above didn't do anything + result.push(this); + return changetype>(result); // retains } var len = length - start; if (len > 0) { - let out = allocate(len << 1); + let out = __alloc(len << 1, idof()); memory.copy(out, changetype(this) + (start << 1), len << 1); - result.push(changetype(register(out, __runtime_id()))); + result.push(changetype(out)); // retains } else { - result.push(changetype("")); + result.push(changetype("")); // static "" } - return result; + return changetype>(result); // retains + // releases result } toString(): String { @@ -484,10 +473,10 @@ import { ArrayBufferView } from "./arraybuffer"; } } assert(ptrPos == len); - var out = allocate(bufPos); - memory.copy(changetype(out), buf, bufPos); + var out = __alloc(bufPos, idof()); + memory.copy(out, buf, bufPos); memory.free(buf); - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } toUTF8(): usize { @@ -553,7 +542,7 @@ export function parseFloat(str: String): f64 { var len: i32 = str.length; if (!len) return NaN; - var ptr = changetype(str) /* + HEAD -> offset */; + var ptr = changetype(str); var code = load(ptr); // determine sign diff --git a/std/assembly/typedarray.ts b/std/assembly/typedarray.ts index eed65f0e..f99648fd 100644 --- a/std/assembly/typedarray.ts +++ b/std/assembly/typedarray.ts @@ -1,7 +1,6 @@ -import { allocate, register } from "./util/runtime"; import { COMPARATOR, SORT as SORT_IMPL } from "./util/sort"; import { E_INDEXOUTOFRANGE } from "./util/error"; -import { __runtime_id } from "./runtime"; +import { idof } from "./builtins"; import { ArrayBufferView } from "./arraybuffer"; export class Int8Array extends ArrayBufferView { @@ -962,13 +961,11 @@ function SUBARRAY( else begin = min(begin, length); if (end < 0) end = max(length + end, begin); else end = max(min(end, length), begin); - var out = allocate(offsetof()); - var data = array.data; - var dataStart = array.dataStart; - changetype(out).data = data; // links - changetype(out).dataStart = dataStart + (begin << alignof()); + var out = __alloc(offsetof(), idof()); + changetype(out).data = array.data; // retains + changetype(out).dataStart = array.dataStart + (begin << alignof()); changetype(out).dataLength = (end - begin) << alignof(); - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } // @ts-ignore: decorator diff --git a/std/assembly/util/allocator.ts b/std/assembly/util/allocator.ts deleted file mode 100644 index c0c2fdbe..00000000 --- a/std/assembly/util/allocator.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** Number of alignment bits. */ -// @ts-ignore: decorator -@inline -export const AL_BITS: u32 = 3; - -/** Number of possible alignment values. */ -// @ts-ignore: decorator -@inline -export const AL_SIZE: usize = 1 << AL_BITS; - -/** Mask to obtain just the alignment bits. */ -// @ts-ignore: decorator -@inline -export const AL_MASK: usize = AL_SIZE - 1; - -/** Maximum 32-bit allocation size. */ -// @ts-ignore: decorator -@inline -export const MAX_SIZE_32: usize = 1 << 30; // 1GB diff --git a/std/assembly/util/number.ts b/std/assembly/util/number.ts index 61d9143e..6f2d3074 100644 --- a/std/assembly/util/number.ts +++ b/std/assembly/util/number.ts @@ -1,6 +1,7 @@ -import { allocate, register, discard } from "./runtime"; +/// + +import { idof } from "../builtins"; import { CharCode } from "./string"; -import { __runtime_id } from "../runtime"; import { ArrayBufferView } from "../arraybuffer"; // @ts-ignore: decorator @@ -264,10 +265,10 @@ export function utoa32(value: u32): String { if (!value) return "0"; var decimals = decimalCount32(value); - var out = allocate(decimals << 1); + var out = __alloc(decimals << 1, idof()); utoa32_core(changetype(out), value, decimals); - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } export function itoa32(value: i32): String { @@ -277,12 +278,12 @@ export function itoa32(value: i32): String { if (sign) value = -value; var decimals = decimalCount32(value) + u32(sign); - var out = allocate(decimals << 1); + var out = __alloc(decimals << 1, idof()); utoa32_core(changetype(out), value, decimals); if (sign) store(changetype(out), CharCode.MINUS); - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } export function utoa64(value: u64): String { @@ -292,14 +293,14 @@ export function utoa64(value: u64): String { if (value <= u32.MAX_VALUE) { let val32 = value; let decimals = decimalCount32(val32); - out = allocate(decimals << 1); + out = __alloc(decimals << 1, idof()); utoa32_core(out, val32, decimals); } else { let decimals = decimalCount64(value); - out = allocate(decimals << 1); + out = __alloc(decimals << 1, idof()); utoa64_core(changetype(out), value, decimals); } - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } export function itoa64(value: i64): String { @@ -312,16 +313,16 @@ export function itoa64(value: i64): String { if (value <= u32.MAX_VALUE) { let val32 = value; let decimals = decimalCount32(val32) + u32(sign); - out = allocate(decimals << 1); + out = __alloc(decimals << 1, idof()); utoa32_core(changetype(out), val32, decimals); } else { let decimals = decimalCount64(value) + u32(sign); - out = allocate(decimals << 1); + out = __alloc(decimals << 1, idof()); utoa64_core(changetype(out), value, decimals); } if (sign) store(changetype(out), CharCode.MINUS); - return changetype(register(out, __runtime_id())); + return changetype(out); // retains } export function itoa(value: T): String { @@ -626,11 +627,12 @@ export function dtoa(value: f64): String { if (isNaN(value)) return "NaN"; return select("-Infinity", "Infinity", value < 0); } - var temp = allocate(MAX_DOUBLE_LENGTH << 1); + var temp = __alloc(MAX_DOUBLE_LENGTH << 1, idof()); var length = dtoa_core(temp, value); - var result = changetype(temp).substring(0, length); // registers - discard(temp); - return result; + if (length < MAX_DOUBLE_LENGTH) { + return changetype(temp).substring(0, length); // retains/releases `temp`, retains return + } + return changetype(temp); // retains } export function itoa_stream(buffer: usize, offset: usize, value: T): u32 { @@ -692,8 +694,7 @@ export function dtoa_stream(buffer: usize, offset: usize, value: f64): u32 { } else { let sign = i32(value < 0); let len = 8 + sign; - let source = changetype(select("-Infinity", "Infinity", sign)); - memory.copy(buffer, source, len << 1); + memory.copy(buffer, changetype(select("-Infinity", "Infinity", sign)), len << 1); return len; } } diff --git a/std/assembly/util/runtime.ts b/std/assembly/util/runtime.ts deleted file mode 100644 index b3512c0c..00000000 --- a/std/assembly/util/runtime.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { AL_MASK, MAX_SIZE_32 } from "./allocator"; -import { __runtime_id } from "../runtime"; -import { Array } from "../array"; -import { ArrayBufferView } from "../arraybuffer"; - -/** - * The common runtime object header prepended to all managed objects. Has a size of 16 bytes in - * WASM32 and contains a classId (e.g. for instanceof checks), the allocation size (e.g. for - * .byteLength and .length computation) and additional reserved fields to be used by GC. If no - * GC is present, the HEADER is cut into half excluding the reserved fields, as indicated by - * HEADER_SIZE. -*/ -@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. */ - reserved1: usize; // itcm: tagged next - /** Reserved field for use by GC. */ - reserved2: usize; // itcm: prev -} - -/** Common runtime header size. */ -// @ts-ignore: decorator -@lazy -export const HEADER_SIZE: usize = (offsetof
() + AL_MASK) & ~AL_MASK; - -/** Common runtime header magic. Used to assert registered/unregistered status. */ -// @ts-ignore: decorator -@lazy -export const HEADER_MAGIC: u32 = 0xA55E4B17; - -/** Maximum byte length of any buffer-like object. */ -// @ts-ignore -@lazy -export const MAX_BYTELENGTH: i32 = MAX_SIZE_32 - HEADER_SIZE; - -/** 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 the memory necessary to represent a managed object of the specified size. */ -// @ts-ignore: decorator -@unsafe -export function allocate(payloadSize: usize): usize { - var header = changetype
(memory.allocate(adjust(payloadSize))); - header.classId = HEADER_MAGIC; - header.payloadSize = payloadSize; - if (isDefined(__ref_collect)) { - header.reserved1 = 0; - header.reserved2 = 0; - } - return changetype(header) + HEADER_SIZE; -} - -/** Reallocates the memory of a managed object that turned out to be too small or too large. */ -// @ts-ignore: decorator -@unsafe -export function reallocate(ref: usize, newPayloadSize: usize): 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.classId; - if (isDefined(__ref_collect)) { - newHeader.reserved1 = 0; - newHeader.reserved2 = 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 (isDefined(__ref_collect)) { - // if previously registered, register again - // @ts-ignore: stub - __ref_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; -} - -/** Discards the memory of a managed object that hasn't been registered yet. */ -// @ts-ignore: decorator -@unsafe -export function discard(ref: usize): void { - if (!ASC_NO_ASSERT) { - assert(ref > HEAP_BASE); // must be a heap object - let header = changetype
(ref - HEADER_SIZE); - assert(header.classId == HEADER_MAGIC); - memory.free(changetype(header)); - } else { - memory.free(changetype(ref - HEADER_SIZE)); - } -} - -/** Registers a managed object of the kind represented by the specified runtime id. */ -// @ts-ignore: decorator -@unsafe -export function register(ref: usize, id: u32): usize { - if (!ASC_NO_ASSERT) { - assert(ref > HEAP_BASE); // must be a heap object - let header = changetype
(ref - HEADER_SIZE); - assert(header.classId == HEADER_MAGIC); - header.classId = id; - } else { - changetype
(ref - HEADER_SIZE).classId = id; - } - if (isDefined(__ref_register)) __ref_register(ref); - return ref; -} - -// @ts-ignore: decorator -@unsafe -export function makeArray(length: i32, alignLog2: usize, id: u32, data: usize = 0): usize { - var array = register(allocate(offsetof()), id); - var bufferSize = length << alignLog2; - var buffer = register(allocate(bufferSize), __runtime_id()); - changetype(array).data = changetype(buffer); // links - changetype(array).dataStart = buffer; - changetype(array).dataLength = bufferSize; - store(changetype(array), length, offsetof("length_")); - if (data) memory.copy(buffer, data, bufferSize); - return array; -} - -// @ts-ignore: decorator -@inline -export function NEWARRAY(length: i32): Array { - return changetype>(makeArray(length, alignof(), __runtime_id>(), 0)); -} diff --git a/tests/runtime/index.html b/tests/runtime/index.html index f7f8b46c..4d509b46 100644 --- a/tests/runtime/index.html +++ b/tests/runtime/index.html @@ -28,8 +28,8 @@ fetch("untouched.wasm").then(result => ).then(result => { exports = result.instance.exports; U32 = new Uint32Array(exports.memory.buffer); - var first = exports.__rt_allocate(255); - exports.__rt_free(first); + var first = exports.__alloc(255); + exports.__free(first); ROOT = first - 17; while (!U32[ROOT >> 2]) --ROOT; // find tail ROOT -= (1 + FL_BITS + HL_SIZE) << 2; @@ -113,7 +113,7 @@ function update() { } function allocate(size) { - var ptr = exports.__rt_allocate(size); + var ptr = exports.__alloc(size); if (!ptr) { alert("should not happen"); return; @@ -127,7 +127,7 @@ function allocate(size) { var er = document.createElement("button"); er.innerText = "realloc"; er.onclick = function() { - ptr = exports.__rt_reallocate(ptr, es.value >>> 0); + ptr = exports.__realloc(ptr, es.value >>> 0); update(); }; el.appendChild(er); @@ -136,7 +136,7 @@ function allocate(size) { ef.className = "free"; el.appendChild(ef); ef.onclick = function() { - exports.__rt_free(ptr); + exports.__free(ptr); document.getElementById("segs").removeChild(el); update(); }; diff --git a/tests/runtime/optimized.wat b/tests/runtime/optimized.wat index 2a9ad398..99ef07fe 100644 --- a/tests/runtime/optimized.wat +++ b/tests/runtime/optimized.wat @@ -20,13 +20,13 @@ (global $~lib/rt/pure/ROOTS (mut i32) (i32.const 0)) (export "memory" (memory $0)) (export "main" (func $assembly/index/main)) - (export "__rt_allocate" (func $~lib/rt/index/__rt_allocate)) - (export "__rt_reallocate" (func $~lib/rt/index/__rt_reallocate)) - (export "__rt_free" (func $~lib/rt/index/__rt_free)) - (export "__rt_retain" (func $~lib/rt/index/__rt_retain)) - (export "__rt_release" (func $~lib/rt/index/__rt_release)) - (export "__rt_collect" (func $~lib/rt/index/__rt_collect)) - (export "__rt_typeinfo" (func $~lib/rt/common/__rt_typeinfo)) + (export "__alloc" (func $~lib/rt/index/__alloc)) + (export "__realloc" (func $~lib/rt/index/__realloc)) + (export "__free" (func $~lib/rt/index/__free)) + (export "__retain" (func $~lib/rt/index/__retain)) + (export "__release" (func $~lib/rt/index/__release)) + (export "__collect" (func $~lib/rt/index/__collect)) + (export "__info" (func $~lib/rt/common/__info)) (func $assembly/index/main (; 1 ;) (type $FUNCSIG$v) global.get $~lib/started i32.eqz @@ -778,7 +778,7 @@ call $~lib/rt/tlsf/prepareBlock local.get $2 ) - (func $~lib/rt/index/__rt_allocate (; 11 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) + (func $~lib/rt/index/__alloc (; 11 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) (local $2 i32) global.get $~lib/rt/tlsf/ROOT local.tee $2 @@ -1073,7 +1073,7 @@ call $~lib/rt/tlsf/insertBlock local.get $3 ) - (func $~lib/rt/index/__rt_reallocate (; 14 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) + (func $~lib/rt/index/__realloc (; 14 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) global.get $~lib/rt/tlsf/ROOT local.get $0 i32.const 16 @@ -1094,14 +1094,14 @@ local.get $1 call $~lib/rt/tlsf/insertBlock ) - (func $~lib/rt/index/__rt_free (; 16 ;) (type $FUNCSIG$vi) (param $0 i32) + (func $~lib/rt/index/__free (; 16 ;) (type $FUNCSIG$vi) (param $0 i32) global.get $~lib/rt/tlsf/ROOT local.get $0 i32.const 16 i32.sub call $~lib/rt/tlsf/freeBlock ) - (func $~lib/rt/index/__rt_retain (; 17 ;) (type $FUNCSIG$vi) (param $0 i32) + (func $~lib/rt/index/__retain (; 17 ;) (type $FUNCSIG$vi) (param $0 i32) local.get $0 if local.get $0 @@ -1115,7 +1115,7 @@ i32.store offset=4 end ) - (func $~lib/rt/index/__rt_release (; 18 ;) (type $FUNCSIG$vi) (param $0 i32) + (func $~lib/rt/index/__release (; 18 ;) (type $FUNCSIG$vi) (param $0 i32) local.get $0 if local.get $0 @@ -1333,10 +1333,10 @@ local.get $5 global.set $~lib/rt/pure/CUR ) - (func $~lib/rt/index/__rt_collect (; 23 ;) (type $FUNCSIG$v) + (func $~lib/rt/index/__collect (; 23 ;) (type $FUNCSIG$v) call $~lib/rt/pure/collectCycles ) - (func $~lib/rt/common/__rt_typeinfo (; 24 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/rt/common/__info (; 24 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) (local $1 i32) i32.const 104 local.set $1 diff --git a/tests/runtime/untouched.wasm b/tests/runtime/untouched.wasm index b20dc4e80dd26e6b321803ee9e77799b86bb20d2..52eab661bba6d60bde9d86ab833be8f691d19277 100644 GIT binary patch delta 124 zcmdm_I$3qX;_w(ww%pX*{Gv)G2A15!%sd80_W1b3oSgh*25!#y_@Yz@n~yC%KCLJ< zl|g_5Bv=AcDFl|!Nlh$HWsn52lk;UlK8}&oc!d(l2it6elWi%6)wWZ z2^L8!N=;=D;D*YUfb