Initial new rt integration

This commit is contained in:
dcode 2019-05-12 13:50:28 +02:00
parent dd2bdd0383
commit ba1a0c2369
52 changed files with 1066 additions and 2985 deletions

View File

@ -392,11 +392,11 @@ exports.main = function main(argv, options, callback) {
// Include runtime template // Include runtime template
{ {
let templateName = String(args.runtime); let templateName = String(args.runtime);
let templateText = exports.libraryFiles["runtime/" + templateName]; let templateText = exports.libraryFiles["rt/index-" + templateName];
if (templateText == null) { if (templateText == null) {
templateText = readFile(templateName + ".ts", baseDir); templateText = readFile(templateName + ".ts", baseDir);
if (templateText == null) { if (templateText == null) {
return callback(Error("Runtime template '" + templateName + " not found.")); return callback(Error("Runtime template '" + templateName + "' not found."));
} }
} }
stats.parseCount++; stats.parseCount++;

View File

@ -83,15 +83,14 @@
}, },
"runtime": { "runtime": {
"description": [ "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]", " full Default runtime based on TLSF and reference counting.",
" trace TLSF memory allocator and ITCM garbage collector.", " stub Minimal stub implementation without free/GC support.",
" arena Just the arena memory allocator. No free/GC.",
"" ""
], ],
"type": "s", "type": "s",
"default": "none" "default": "full"
}, },
"debug": { "debug": {
"description": "Enables debug information in emitted binaries.", "description": "Enables debug information in emitted binaries.",

View File

@ -42,9 +42,6 @@
"no-default-export": { "no-default-export": {
"severity": "error" "severity": "error"
}, },
"no-duplicate-imports": {
"severity": "error"
},
"no-duplicate-super": { "no-duplicate-super": {
"severity": "error" "severity": "error"
}, },

View File

@ -1,23 +1,31 @@
"use strict"; "use strict";
const hasBigInt64 = typeof BigUint64Array !== "undefined"; /** Size of the runtime header, in bytes. */
const thisPtr = Symbol(); 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. */ /** Gets a string from an U32 and an U16 view on a memory. */
function getStringImpl(U32, U16, ptr) { function getStringImpl(U32, U16, ptr) {
var dataLength = U32[ptr >>> 2]; var size32 = U32[(ptr + PAYLOADLENGTH_OFFSET) >>> 2];
var dataOffset = (ptr + 4) >>> 1; var offset16 = ptr >>> 1;
var dataRemain = dataLength; var remain32 = size32;
var parts = []; var parts = [];
const chunkSize = 1024; const chunkSize = 1024;
while (dataRemain > chunkSize) { while (remain32 > chunkSize) {
let last = U16[dataOffset + chunkSize - 1]; let last = U16[offset16 + chunkSize - 1];
let size = last >= 0xD800 && last < 0xDC00 ? chunkSize - 1 : chunkSize; 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)); 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. */ /** Prepares the base module prior to instantiation. */
@ -54,8 +62,7 @@ function postInstantiate(baseModule, instance) {
var memory_fill = rawExports["memory.fill"]; var memory_fill = rawExports["memory.fill"];
var memory_free = rawExports["memory.free"]; var memory_free = rawExports["memory.free"];
var table = rawExports.table; 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 // Provide views for all sorts of basic values
var buffer, I8, U8, I16, U16, I32, U32, F32, F64, I64, U64; var buffer, I8, U8, I16, U16, I32, U32, F32, F64, I64, U64;
@ -71,7 +78,7 @@ function postInstantiate(baseModule, instance) {
U16 = new Uint16Array(buffer); U16 = new Uint16Array(buffer);
I32 = new Int32Array(buffer); I32 = new Int32Array(buffer);
U32 = new Uint32Array(buffer); U32 = new Uint32Array(buffer);
if (hasBigInt64) { if (SUPPORTS_BIGINT) {
I64 = new BigInt64Array(buffer); I64 = new BigInt64Array(buffer);
U64 = new BigUint64Array(buffer); U64 = new BigUint64Array(buffer);
} }
@ -271,7 +278,7 @@ function demangle(exports, baseModule) {
}; };
ctor.prototype = {}; ctor.prototype = {};
ctor.wrap = function(thisValue) { 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 => if (classElem) Object.getOwnPropertyNames(classElem).forEach(name =>
Object.defineProperty(ctor, name, Object.getOwnPropertyDescriptor(classElem, 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 getter = exports[internalName.replace("set:", "get:")];
let setter = exports[internalName.replace("get:", "set:")]; let setter = exports[internalName.replace("get:", "set:")];
Object.defineProperty(curr, name, { Object.defineProperty(curr, name, {
get: function() { return getter(this[thisPtr]); }, get: function() { return getter(this[THIS]); },
set: function(value) { setter(this[thisPtr], value); }, set: function(value) { setter(this[THIS], value); },
enumerable: true enumerable: true
}); });
} }
@ -297,7 +304,7 @@ function demangle(exports, baseModule) {
Object.defineProperty(curr, name, { Object.defineProperty(curr, name, {
value: function (...args) { value: function (...args) {
setargc(args.length); setargc(args.length);
return elem(this[thisPtr], ...args); return elem(this[THIS], ...args);
} }
}); });
} }

View File

@ -461,13 +461,19 @@ export namespace BuiltinSymbols {
export const v8x16_shuffle = "~lib/builtins/v8x16.shuffle"; 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 // std/diagnostics.ts
export const ERROR = "~lib/diagnostics/ERROR"; export const ERROR = "~lib/diagnostics/ERROR";
export const WARNING = "~lib/diagnostics/WARNING"; export const WARNING = "~lib/diagnostics/WARNING";
export const INFO = "~lib/diagnostics/INFO"; export const INFO = "~lib/diagnostics/INFO";
// std/memory.ts // std/memory.ts
export const HEAP_BASE = "~lib/memory/HEAP_BASE";
export const memory_size = "~lib/memory/memory.size"; export const memory_size = "~lib/memory/memory.size";
export const memory_grow = "~lib/memory/memory.grow"; export const memory_grow = "~lib/memory/memory.grow";
export const memory_copy = "~lib/memory/memory.copy"; export const memory_copy = "~lib/memory/memory.copy";
@ -477,8 +483,6 @@ export namespace BuiltinSymbols {
export const memory_reset = "~lib/memory/memory.reset"; export const memory_reset = "~lib/memory/memory.reset";
// std/runtime.ts // 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_instanceof = "~lib/runtime/runtime.instanceof";
export const runtime_flags = "~lib/runtime/runtime.flags"; export const runtime_flags = "~lib/runtime/runtime.flags";
export const runtime_allocate = "~lib/util/runtime/allocate"; 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_register = "~lib/util/runtime/register";
export const runtime_discard = "~lib/util/runtime/discard"; export const runtime_discard = "~lib/util/runtime/discard";
export const runtime_makeArray = "~lib/util/runtime/makeArray"; 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 // std/typedarray.ts
export const Int8Array = "~lib/typedarray/Int8Array"; export const Int8Array = "~lib/typedarray/Int8Array";
@ -3630,7 +3632,7 @@ export function compileCall(
// === Internal runtime ======================================================================= // === Internal runtime =======================================================================
case BuiltinSymbols.runtime_id: { case BuiltinSymbols.idof: {
let type = evaluateConstantType(compiler, typeArguments, operands, reportNode); let type = evaluateConstantType(compiler, typeArguments, operands, reportNode);
compiler.currentType = Type.u32; compiler.currentType = Type.u32;
if (!type) return module.createUnreachable(); if (!type) return module.createUnreachable();
@ -3644,31 +3646,32 @@ export function compileCall(
} }
return module.createI32(classReference.id); return module.createI32(classReference.id);
} }
case BuiltinSymbols.gc_mark_roots: { case BuiltinSymbols.visit_globals: {
if ( if (
checkTypeAbsent(typeArguments, reportNode, prototype) | checkTypeAbsent(typeArguments, reportNode, prototype) |
checkArgsRequired(operands, 0, reportNode, compiler) checkArgsRequired(operands, 1, reportNode, compiler) // cookie
) {
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)
) { ) {
compiler.currentType = Type.void; compiler.currentType = Type.void;
return module.createUnreachable(); return module.createUnreachable();
} }
let arg0 = compiler.compileExpression(operands[0], Type.u32, ConversionKind.IMPLICIT, WrapMode.NONE); 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.needsVisitGlobals = true;
compiler.needsGcMark = true;
compiler.currentType = Type.void; 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. */ /** Compiles the `visit_globals` function. */
export function compileMarkRoots(compiler: Compiler): void { export function compileVisitGlobals(compiler: Compiler): void {
var module = compiler.module; var module = compiler.module;
var exprs = new Array<ExpressionRef>(); var exprs = new Array<ExpressionRef>();
var typeRef = compiler.ensureFunctionType(null, Type.void); var typeRef = compiler.ensureFunctionType([ Type.u32 ], Type.void); // cookie
var nativeSizeType = compiler.options.nativeSizeType; 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()) { for (let element of compiler.program.elementsByName.values()) {
if (element.kind != ElementKind.GLOBAL) continue; if (element.kind != ElementKind.GLOBAL) continue;
@ -4074,7 +4077,7 @@ export function compileMarkRoots(compiler: Compiler): void {
let value = global.constantIntegerValue; let value = global.constantIntegerValue;
if (i64_low(value) || i64_high(value)) { if (i64_low(value) || i64_high(value)) {
exprs.push( exprs.push(
module.createCall(markRef.internalName, [ module.createCall(visitInstance.internalName, [
compiler.options.isWasm64 compiler.options.isWasm64
? module.createI64(i64_low(value), i64_high(value)) ? module.createI64(i64_low(value), i64_high(value))
: module.createI32(i64_low(value)) : module.createI32(i64_low(value))
@ -4084,35 +4087,35 @@ export function compileMarkRoots(compiler: Compiler): void {
} else { } else {
exprs.push( exprs.push(
module.createIf( module.createIf(
module.createTeeLocal( module.createTeeLocal(1,
0,
module.createGetGlobal(global.internalName, nativeSizeType) module.createGetGlobal(global.internalName, nativeSizeType)
), ),
module.createCall(markRef.internalName, [ module.createCall(visitInstance.internalName, [
module.createGetLocal(0, nativeSizeType) module.createGetLocal(1, nativeSizeType), // tempRef != null
module.createGetLocal(0, NativeType.I32) // cookie
], NativeType.None) ], NativeType.None)
) )
); );
} }
} }
} }
module.addFunction(BuiltinSymbols.gc_mark_roots, typeRef, [ nativeSizeType ], module.addFunction(BuiltinSymbols.visit_globals, typeRef, [ nativeSizeType ],
exprs.length exprs.length
? module.createBlock(null, exprs) ? module.createBlock(null, exprs)
: module.createNop() : module.createNop()
); );
} }
/** Compiles the `__gc_mark_members` function. */ /** Compiles the `visit_members` function. */
export function compileMarkMembers(compiler: Compiler): void { export function compileVisitMembers(compiler: Compiler): void {
var program = compiler.program; var program = compiler.program;
var module = compiler.module; var module = compiler.module;
var usizeType = program.options.usizeType; var usizeType = program.options.usizeType;
var nativeSizeType = usizeType.toNativeType(); var nativeSizeType = usizeType.toNativeType();
var nativeSizeSize = usizeType.byteSize; 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 managedClasses = program.managedClasses;
var markRef = assert(program.markRef); var visitInstance = assert(program.visitInstance);
var names: string[] = [ "invalid" ]; // classId=0 is invalid var names: string[] = [ "invalid" ]; // classId=0 is invalid
var blocks = new Array<ExpressionRef[]>(); var blocks = new Array<ExpressionRef[]>();
var lastId = 0; var lastId = 0;
@ -4136,7 +4139,7 @@ export function compileMarkMembers(compiler: Compiler): void {
} }
blocks.push([ blocks.push([
module.createCall(traverseFunc.internalName, [ module.createCall(traverseFunc.internalName, [
module.createGetLocal(1, nativeSizeType) module.createGetLocal(0, nativeSizeType)
], NativeType.None), ], NativeType.None),
module.createReturn() module.createReturn()
]); ]);
@ -4160,19 +4163,16 @@ export function compileMarkMembers(compiler: Compiler): void {
// if ($2 = value) FIELDCLASS~traverse($2) // if ($2 = value) FIELDCLASS~traverse($2)
module.createIf( module.createIf(
module.createTeeLocal(2, module.createTeeLocal(2,
module.createLoad( module.createLoad(nativeSizeSize, false,
nativeSizeSize, module.createGetLocal(0, nativeSizeType),
false, nativeSizeType, fieldOffset
module.createGetLocal(1, nativeSizeType),
nativeSizeType,
fieldOffset
) )
), ),
module.createBlock(null, [ module.createBlock(null, [
module.createCall(markRef.internalName, [ module.createCall(visitInstance.internalName, [
module.createGetLocal(2, nativeSizeType) module.createGetLocal(2, nativeSizeType)
], NativeType.None), ], NativeType.None),
module.createCall(BuiltinSymbols.gc_mark_members, [ module.createCall(BuiltinSymbols.visit_members, [
module.createI32(fieldClassId), module.createI32(fieldClassId),
module.createGetLocal(2, nativeSizeType) module.createGetLocal(2, nativeSizeType)
], NativeType.None) ], NativeType.None)
@ -4191,15 +4191,29 @@ export function compileMarkMembers(compiler: Compiler): void {
var current: ExpressionRef; var current: ExpressionRef;
if (blocks.length) { 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], [ 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) { for (let i = 0, k = blocks.length; i < k; ++i) {
blocks[i].unshift(current); blocks[i].unshift(current);
current = module.createBlock(i == k - 1 ? "invalid" : names[i + 2], blocks[i]); 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 // wrap the function with a terminating unreachable
current = module.createBlock(null, [ current = module.createBlock(null, [
current, current,
@ -4209,7 +4223,7 @@ export function compileMarkMembers(compiler: Compiler): void {
// simplify // simplify
current = module.createUnreachable(); 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 { function typeToRuntimeFlags(type: Type, program: Program): RTTIFlags {

View File

@ -182,13 +182,17 @@ export namespace CommonSymbols {
export const abort = "abort"; export const abort = "abort";
export const pow = "pow"; export const pow = "pow";
export const mod = "mod"; export const mod = "mod";
export const allocate = "allocate"; export const alloc = "__alloc";
export const reallocate = "reallocate"; export const realloc = "__realloc";
export const register = "register"; export const free = "__free";
export const discard = "discard"; export const retain = "__retain";
export const newString = "newString"; export const release = "__release";
export const newArrayBuffer = "newArrayBuffer"; export const retainRelease = "__retainRelease";
export const newArray = "newArray"; export const collect = "__collect";
export const typeinfo = "__typeinfo";
export const instanceof_ = "__instanceof";
export const visit = "__visit";
export const allocArray = "__allocArray";
} }
// shared // shared

View File

@ -7,8 +7,8 @@ import {
BuiltinSymbols, BuiltinSymbols,
compileCall as compileBuiltinCall, compileCall as compileBuiltinCall,
compileAbort, compileAbort,
compileMarkRoots, compileVisitGlobals,
compileMarkMembers, compileVisitMembers,
compileRTTI, compileRTTI,
} from "./builtins"; } from "./builtins";
@ -281,8 +281,10 @@ export class Compiler extends DiagnosticEmitter {
argcSet: FunctionRef = 0; argcSet: FunctionRef = 0;
/** Whether HEAP_BASE is required. */ /** Whether HEAP_BASE is required. */
needsHeap: bool = false; needsHeap: bool = false;
/** Indicates whether the __gc_mark_* functions must be generated. */ /** Indicates whether the __visit_globals function must be generated. */
needsGcMark: bool = false; needsVisitGlobals: bool = false;
/** Indicated whether the __visit_members function must be generated. */
needsVisitMembers: bool = false;
/** Whether RTTI is required. */ /** Whether RTTI is required. */
needsRTTI: bool = false; needsRTTI: bool = false;
@ -359,10 +361,8 @@ export class Compiler extends DiagnosticEmitter {
} }
// compile gc features if utilized // compile gc features if utilized
if (this.needsGcMark) { if (this.needsVisitGlobals) compileVisitGlobals(this);
compileMarkRoots(this); if (this.needsVisitMembers) compileVisitMembers(this);
compileMarkMembers(this);
}
// compile runtime type information // compile runtime type information
module.removeGlobal(BuiltinSymbols.RTTI_BASE); module.removeGlobal(BuiltinSymbols.RTTI_BASE);
@ -593,58 +593,20 @@ export class Compiler extends DiagnosticEmitter {
var nativeType = type.toNativeType(); var nativeType = type.toNativeType();
var usizeType = this.options.usizeType; var usizeType = this.options.usizeType;
var nativeSizeType = usizeType.toNativeType(); var nativeSizeType = usizeType.toNativeType();
var valueExpr: ExpressionRef;
if (type.isManaged(program)) { if (type.isManaged(program)) {
let fn1: Function | null, fn2: Function | null; let retainReleaseInstance = program.retainReleaseInstance;
let body: ExpressionRef[] = []; this.compileFunction(retainReleaseInstance);
if (fn1 = program.linkRef) { // tracing valueExpr = module.createCall(retainReleaseInstance.internalName, [
if (fn2 = program.unlinkRef) { module.createGetLocal(1, nativeType), // newRef
body.push( module.createLoad(type.byteSize, false, // oldRef
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), module.createGetLocal(0, nativeSizeType),
nativeType, field.memoryOffset nativeType, field.memoryOffset
) )
) ], nativeType);
),
module.createBlock(null, body)
)
);
} else { } else {
valueExpr = module.createGetLocal(1, nativeType);
}
module.addFunction( module.addFunction(
name, name,
this.ensureFunctionType([ type ], Type.void, usizeType), this.ensureFunctionType([ type ], Type.void, usizeType),
@ -652,12 +614,11 @@ export class Compiler extends DiagnosticEmitter {
module.createStore( module.createStore(
type.byteSize, type.byteSize,
module.createGetLocal(0, nativeSizeType), module.createGetLocal(0, nativeSizeType),
module.createGetLocal(1, nativeType), valueExpr,
nativeType, nativeType,
field.memoryOffset field.memoryOffset
) )
); );
}
module.addFunctionExport(name, name); module.addFunctionExport(name, name);
} }
@ -969,8 +930,11 @@ export class Compiler extends DiagnosticEmitter {
); );
} }
module.addGlobal(internalName, nativeType, true, type.toNativeZero(module)); module.addGlobal(internalName, nativeType, true, type.toNativeZero(module));
if (type.isManaged(this.program) && this.program.retainRef) { let program = this.program;
initExpr = this.makeInsertRef(initExpr, null, type.is(TypeFlags.NULLABLE)); if (type.isManaged(program)) {
let retainInstance = program.retainInstance;
this.compileFunction(retainInstance);
initExpr = module.createCall(retainInstance.internalName, [ initExpr ], nativeType);
} }
this.currentBody.push( this.currentBody.push(
module.createSetGlobal(internalName, initExpr) module.createSetGlobal(internalName, initExpr)
@ -5230,58 +5194,103 @@ export class Compiler extends DiagnosticEmitter {
return module.createUnreachable(); return module.createUnreachable();
} }
/** Makes an assignment to a local, possibly retaining and releasing affected references and keeping track of wrap and null states. */
makeLocalAssignment( makeLocalAssignment(
local: Local, local: Local,
valueExpr: ExpressionRef, valueExpr: ExpressionRef,
tee: bool, tee: bool,
possiblyNull: bool possiblyNull: bool
): ExpressionRef { ): ExpressionRef {
// TBD: use REPLACE macro to keep track of managed refcounts? or can the compiler evaluate var module = this.module;
// this statically in closed contexts like functions in order to safe the extra work? var program = this.program;
var type = local.type; var type = local.type;
assert(type != Type.void); assert(type != Type.void);
var nativeType = type.toNativeType();
var flow = this.currentFlow; var flow = this.currentFlow;
var localIndex = local.index; var localIndex = local.index;
if (type.is(TypeFlags.SHORT | TypeFlags.INTEGER)) { if (type.is(TypeFlags.SHORT | TypeFlags.INTEGER)) {
if (!flow.canOverflow(valueExpr, type)) flow.setLocalFlag(localIndex, LocalFlags.WRAPPED); if (!flow.canOverflow(valueExpr, type)) flow.setLocalFlag(localIndex, LocalFlags.WRAPPED);
else flow.unsetLocalFlag(localIndex, LocalFlags.WRAPPED); else flow.unsetLocalFlag(localIndex, LocalFlags.WRAPPED);
} }
if (type.is(TypeFlags.NULLABLE)) { if (type.is(TypeFlags.NULLABLE)) {
if (possiblyNull) flow.unsetLocalFlag(localIndex, LocalFlags.NONNULL); if (possiblyNull) flow.unsetLocalFlag(localIndex, LocalFlags.NONNULL);
else flow.setLocalFlag(localIndex, LocalFlags.NONNULL); else flow.setLocalFlag(localIndex, LocalFlags.NONNULL);
} }
if (tee) {
// 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 {
if (tee) { // TEE(local = value)
this.currentType = type; this.currentType = type;
return this.module.createTeeLocal(localIndex, valueExpr); return this.module.createTeeLocal(localIndex, valueExpr);
} else { } else { // local = value
this.currentType = Type.void; this.currentType = Type.void;
return this.module.createSetLocal(localIndex, valueExpr); 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 { makeGlobalAssignment(global: Global, valueExpr: ExpressionRef, tee: bool): ExpressionRef {
var module = this.module; var module = this.module;
var program = this.program;
var type = global.type; var type = global.type;
assert(type != Type.void); assert(type != Type.void);
var nativeType = type.toNativeType(); var nativeType = type.toNativeType();
// MANAGED (reference counting) if (type.isManaged(program)) {
if (type.isManaged(this.program)) { let retainReleaseInstance = program.retainReleaseInstance;
if (this.program.retainRef) { this.compileFunction(retainReleaseInstance);
valueExpr = this.makeReplaceRef( 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, valueExpr,
module.createGetGlobal(global.internalName, nativeType), module.createGetGlobal(global.internalName, nativeType)
null, ], nativeType)
type.is(TypeFlags.NULLABLE)
); );
} }
// UNMANAGED
} else { } else {
valueExpr = this.ensureSmallIntegerWrap(valueExpr, type); // global values must be wrapped valueExpr = this.ensureSmallIntegerWrap(valueExpr, type); // globals must be wrapped
} if (tee) { // (global = (t1 = value)), t1
if (tee) {
let tempValue = this.currentFlow.getAndFreeTempLocal(type, true); let tempValue = this.currentFlow.getAndFreeTempLocal(type, true);
this.currentType = type; this.currentType = type;
return module.createBlock(null, [ return module.createBlock(null, [
@ -5290,15 +5299,19 @@ export class Compiler extends DiagnosticEmitter {
), ),
module.createGetLocal(tempValue.index, nativeType) module.createGetLocal(tempValue.index, nativeType)
], nativeType); ], nativeType);
} else { } else { // global = value
this.currentType = Type.void; this.currentType = Type.void;
return this.module.createSetGlobal(global.internalName, valueExpr); 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 { makeFieldAssignment(field: Field, valueExpr: ExpressionRef, thisExpr: ExpressionRef, tee: bool): ExpressionRef {
var program = this.program;
var module = this.module; var module = this.module;
var program = this.program;
var flow = this.currentFlow; var flow = this.currentFlow;
var fieldType = field.type; var fieldType = field.type;
var nativeFieldType = fieldType.toNativeType(); var nativeFieldType = fieldType.toNativeType();
@ -5306,54 +5319,47 @@ export class Compiler extends DiagnosticEmitter {
var thisType = (<Class>field.parent).type; var thisType = (<Class>field.parent).type;
var nativeThisType = thisType.toNativeType(); var nativeThisType = thisType.toNativeType();
// MANAGED: this.field = replace(value, this.field) if (fieldType.isManaged(program) && thisType.isManaged(program)) {
if (fieldType.isManaged(program)) {
let tempThis = flow.getTempLocal(thisType, false); let tempThis = flow.getTempLocal(thisType, false);
let expr: ExpressionRef; let retainReleaseInstance = program.retainReleaseInstance;
if (tee) { // tee value to a temp local and make it the block's result this.compileFunction(retainReleaseInstance);
let tempValue = flow.getTempLocal(fieldType, !flow.canOverflow(valueExpr, fieldType)); if (tee) { // ((t1 = this).field = __retainRelease(t2 = value, t1.field)), t2
expr = module.createBlock(null, [ let tempValue = flow.getAndFreeTempLocal(fieldType, !flow.canOverflow(valueExpr, fieldType));
flow.freeTempLocal(tempThis);
this.currentType = fieldType;
return module.createBlock(null, [
module.createStore(fieldType.byteSize, module.createStore(fieldType.byteSize,
module.createTeeLocal(tempThis.index, thisExpr), module.createTeeLocal(tempThis.index, thisExpr),
this.makeReplaceRef( module.createCall(retainReleaseInstance.internalName, [
module.createTeeLocal(tempValue.index, valueExpr), module.createTeeLocal(tempValue.index, valueExpr), // newRef
module.createLoad(fieldType.byteSize, fieldType.is(TypeFlags.SIGNED), module.createLoad(fieldType.byteSize, fieldType.is(TypeFlags.SIGNED), // oldRef
module.createGetLocal(tempThis.index, nativeThisType), module.createGetLocal(tempThis.index, nativeThisType),
nativeFieldType, field.memoryOffset nativeFieldType, field.memoryOffset
), )
tempThis, ], nativeFieldType),
fieldType.is(TypeFlags.NULLABLE)
),
nativeFieldType, field.memoryOffset nativeFieldType, field.memoryOffset
), ),
module.createGetLocal(tempValue.index, nativeFieldType) module.createGetLocal(tempValue.index, nativeFieldType)
], nativeFieldType); ], nativeFieldType);
flow.freeTempLocal(tempValue); } else { // (t1 = this).field = __retainRelease(value, t1.field)
this.currentType = fieldType; flow.freeTempLocal(tempThis);
} else { // no need for a temp local this.currentType = Type.void;
expr = module.createStore(fieldType.byteSize, return module.createStore(fieldType.byteSize,
module.createTeeLocal(tempThis.index, thisExpr), module.createTeeLocal(tempThis.index, thisExpr),
this.makeReplaceRef( module.createCall(retainReleaseInstance.internalName, [
valueExpr, valueExpr, // newRef
module.createLoad(fieldType.byteSize, fieldType.is(TypeFlags.SIGNED), module.createLoad(fieldType.byteSize, fieldType.is(TypeFlags.SIGNED), // oldRef
module.createGetLocal(tempThis.index, nativeThisType), module.createGetLocal(tempThis.index, nativeThisType),
nativeFieldType, field.memoryOffset nativeFieldType, field.memoryOffset
), )
tempThis, ], nativeFieldType),
fieldType.is(TypeFlags.NULLABLE)
),
nativeFieldType, field.memoryOffset nativeFieldType, field.memoryOffset
); );
this.currentType = Type.void;
} }
flow.freeTempLocal(tempThis); } else {
return expr; if (tee) { // (this.field = (t1 = value)), t1
}
// UNMANAGED: this.field = value
if (tee) {
this.currentType = fieldType;
let tempValue = flow.getAndFreeTempLocal(fieldType, !flow.canOverflow(valueExpr, fieldType)); let tempValue = flow.getAndFreeTempLocal(fieldType, !flow.canOverflow(valueExpr, fieldType));
this.currentType = fieldType;
return module.createBlock(null, [ return module.createBlock(null, [
module.createStore(fieldType.byteSize, module.createStore(fieldType.byteSize,
thisExpr, thisExpr,
@ -5362,13 +5368,14 @@ export class Compiler extends DiagnosticEmitter {
), ),
module.createGetLocal(tempValue.index, nativeFieldType) module.createGetLocal(tempValue.index, nativeFieldType)
], nativeFieldType); ], nativeFieldType);
} else { } else { // this.field = value
this.currentType = Type.void; this.currentType = Type.void;
return module.createStore(fieldType.byteSize, return module.createStore(fieldType.byteSize,
thisExpr, thisExpr,
valueExpr, valueExpr,
nativeFieldType, field.memoryOffset 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 // otherwise allocate a new array header and make it wrap a copy of the static buffer
} else { } else {
// makeArray(length, alignLog2, classId, staticBuffer) // makeArray(length, alignLog2, classId, staticBuffer)
let expr = this.makeCallDirect(assert(program.makeArrayInstance), [ let expr = this.makeCallDirect(program.allocArrayInstance, [
module.createI32(length), module.createI32(length),
program.options.isWasm64 program.options.isWasm64
? module.createI64(elementType.alignLog2) ? module.createI64(elementType.alignLog2)
@ -7044,12 +7051,11 @@ export class Compiler extends DiagnosticEmitter {
var flow = this.currentFlow; var flow = this.currentFlow;
var tempThis = flow.getTempLocal(arrayType, false); var tempThis = flow.getTempLocal(arrayType, false);
var tempDataStart = flow.getTempLocal(arrayBufferInstance.type); var tempDataStart = flow.getTempLocal(arrayBufferInstance.type);
var newArrayInstance = assert(program.makeArrayInstance);
var stmts = new Array<ExpressionRef>(); var stmts = new Array<ExpressionRef>();
// tempThis = makeArray(length, alignLog2, classId, source = 0) // tempThis = makeArray(length, alignLog2, classId, source = 0)
stmts.push( stmts.push(
module.createSetLocal(tempThis.index, module.createSetLocal(tempThis.index,
this.makeCallDirect(newArrayInstance, [ this.makeCallDirect(program.allocArrayInstance, [
module.createI32(length), module.createI32(length),
program.options.isWasm64 program.options.isWasm64
? module.createI64(elementType.alignLog2) ? module.createI64(elementType.alignLog2)
@ -7080,12 +7086,10 @@ export class Compiler extends DiagnosticEmitter {
? this.compileExpression(valueExpression, elementType, ConversionKind.IMPLICIT, WrapMode.NONE) ? this.compileExpression(valueExpression, elementType, ConversionKind.IMPLICIT, WrapMode.NONE)
: elementType.toNativeZero(module); : elementType.toNativeZero(module);
if (isManaged) { if (isManaged) {
// value = link/retain(value[, tempThis]) // value = __retain(value)
valueExpr = this.makeInsertRef( valueExpr = this.makeCallDirect(program.retainInstance, [
valueExpr, valueExpr
tempThis, ], reportNode);
elementType.is(TypeFlags.NULLABLE)
);
} }
// store<T>(tempData, value, immOffset) // store<T>(tempData, value, immOffset)
stmts.push( stmts.push(
@ -8290,35 +8294,17 @@ export class Compiler extends DiagnosticEmitter {
var module = this.module; var module = this.module;
var options = this.options; var options = this.options;
var classType = classInstance.type; 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<T>())
this.currentType = classType; this.currentType = classType;
return this.makeCallDirect(assert(program.memoryAllocateInstance), [ return this.makeCallDirect(program.allocInstance, [
options.isWasm64 options.isWasm64
? module.createI64(classInstance.currentMemoryOffset) ? module.createI64(classInstance.currentMemoryOffset)
: module.createI32(classInstance.currentMemoryOffset) : module.createI32(classInstance.currentMemoryOffset),
module.createI32(
classInstance.hasDecorator(DecoratorFlags.UNMANAGED)
? 0
: classInstance.id
)
], reportNode); ], reportNode);
} else {
// register(allocate(sizeof<T>()), 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);
}
} }
/** Makes the initializers for a class's fields. */ /** Makes the initializers for a class's fields. */
@ -8383,182 +8369,206 @@ export class Compiler extends DiagnosticEmitter {
return stmts; return stmts;
} }
/** Wraps a reference in a `retain` call. Returns the reference if `tempLocal` is specified. */ // private makeRetainOrRelease(fn: Function, expr: ExpressionRef, possiblyNull: bool, tee: bool, tempIndex: i32 = -1): ExpressionRef {
makeRetain(valueExpr: ExpressionRef, tempIndex: i32 = -1): ExpressionRef { // var module = this.module;
var module = this.module; // var nativeSizeType = this.options.nativeSizeType;
var program = this.program; // if (tee) {
var retainFn = assert(program.retainRef); // if (possiblyNull) {
this.compileFunction(retainFn); // assert(tempIndex >= 0);
if (tempIndex >= 0) { // return module.createBlock(null, [
let nativeSizeType = this.options.nativeSizeType; // module.createIf(
return module.createBlock(null, [ // module.createTeeLocal(tempIndex, expr),
module.createCall(retainFn.internalName, [ // module.createCall(fn.internalName, [
module.createTeeLocal(tempIndex, valueExpr) // module.createGetLocal(tempIndex, nativeSizeType)
], NativeType.None), // ], NativeType.None)
module.createGetLocal(tempIndex, nativeSizeType) // ),
], nativeSizeType); // module.createGetLocal(tempIndex, nativeSizeType)
} else { // ], nativeSizeType);
return module.createCall(retainFn.internalName, [ valueExpr ], NativeType.None); // } 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. */ // /** Wraps an expression of a reference type in a `retain` call. */
makeRelease(valueExpr: ExpressionRef, tempIndex: i32 = -1): ExpressionRef { // makeRetain(expr: ExpressionRef, possiblyNull: bool, tee: bool, tempIndex: i32 = -1): ExpressionRef {
var module = this.module; // return this.makeRetainOrRelease(this.program.retainInstance, expr, possiblyNull, tee, tempIndex);
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 a new and an old reference in a sequence of `retain` and `release` calls. */ // /** Wraps an expression of a reference type in a `release` call. */
makeRetainRelease(newValueExpr: ExpressionRef, oldValueExpr: ExpressionRef, tempIndex: i32 = -1): ExpressionRef { // makeRelease(expr: ExpressionRef, possiblyNull: bool, tee: bool, tempIndex: i32 = -1): ExpressionRef {
// TODO: checking `newValue != oldValue` significantly reduces strain on the roots buffer // return this.makeRetainOrRelease(this.program.releaseInstance, expr, possiblyNull, tee, tempIndex);
// 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)
]);
}
}
/** Prepares the insertion of a reference into an _uninitialized_ parent using the GC interface. */ // /** 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. */
makeInsertRef( // makeRetainRelease(newExpr: ExpressionRef, oldExpr: ExpressionRef, possiblyNull: bool, tempIndexNew: i32, tempIndexOld: i32): ExpressionRef {
valueExpr: ExpressionRef, // var module = this.module;
tempParent: Local | null, // var nativeSizeType = this.options.nativeSizeType;
nullable: bool // return module.createIf(
): ExpressionRef { // module.createBinary(
var module = this.module; // nativeSizeType == NativeType.I32
var program = this.program; // ? BinaryOp.NeI32
var usizeType = this.options.usizeType; // : BinaryOp.NeI64,
var nativeSizeType = this.options.nativeSizeType; // module.createTeeLocal(tempIndexNew, newExpr),
var flow = this.currentFlow; // module.createTeeLocal(tempIndexOld, oldExpr)
var tempValue = flow.getTempLocal(usizeType, false); // ),
var handle: ExpressionRef; // module.createBlock(null, [
var fn: Function | null; // this.makeRetain(module.createGetLocal(tempIndexNew, nativeSizeType), possiblyNull, false, tempIndexNew),
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. */ // ], NativeType.None)
makeReplaceRef( // )
valueExpr: ExpressionRef, // return module.createBlock(null, [
oldValueExpr: ExpressionRef, // this.makeRetain(newExpr, possiblyNull, true, tempIndex),
tempParent: Local | null, // this.makeRelease(oldExpr, possiblyNull, false), // wrong: reuses tempIndex if possiblyNull
nullable: bool
): ExpressionRef { // ], nativeSizeType);
var module = this.module; // }
var program = this.program;
var usizeType = this.options.usizeType; // /** Wraps a new and an old reference in a sequence of `retain` and `release` calls. */
var nativeSizeType = this.options.nativeSizeType; // makeRetainRelease(newValueExpr: ExpressionRef, oldValueExpr: ExpressionRef, tempIndex: i32, possiblyNull: bool = true): ExpressionRef {
var flow = this.currentFlow; // var module = this.module;
var tempValue = flow.getTempLocal(usizeType, false); // var nativeSizeType = this.options.nativeSizeType;
var tempOldValue = flow.getTempLocal(usizeType, false); // return module.createBlock(null, [
var handleOld: ExpressionRef = 0; // this.makeRetain(module.createTeeLocal(tempIndex, newValueExpr), possiblyNull ? tempIndex : -1),
var handleNew: ExpressionRef; // this.makeRelease(oldValueExpr, possiblyNull ? tempIndex : -1),
var fn1: Function | null, fn2: Function | null; // module.createGetLocal(tempIndex, nativeSizeType)
if (fn1 = program.linkRef) { // tracing // ], nativeSizeType);
tempParent = assert(tempParent); // }
if (fn2 = program.unlinkRef) {
handleOld = module.createCall(fn2.internalName, [ // /** Prepares the insertion of a reference into an _uninitialized_ parent using the GC interface. */
module.createGetLocal(tempOldValue.index, nativeSizeType), // makeInsertRef(
module.createGetLocal(tempParent.index, nativeSizeType) // valueExpr: ExpressionRef,
], NativeType.None); // tempParent: Local | null,
} // nullable: bool
handleNew = module.createCall(fn1.internalName, [ // ): ExpressionRef {
module.createGetLocal(tempValue.index, nativeSizeType), // var module = this.module;
module.createGetLocal(tempParent.index, nativeSizeType) // var program = this.program;
], NativeType.None); // var usizeType = this.options.usizeType;
} else if (fn1 = program.retainRef) { // arc // var nativeSizeType = this.options.nativeSizeType;
fn2 = assert(program.releaseRef); // var flow = this.currentFlow;
handleOld = module.createCall(fn2.internalName, [ // var tempValue = flow.getTempLocal(usizeType, false);
module.createGetLocal(tempOldValue.index, nativeSizeType) // var handle: ExpressionRef;
], NativeType.None); // var fn: Function | null;
handleNew = module.createCall(fn1.internalName, [ // if (fn = program.linkRef) { // tracing
module.createGetLocal(tempValue.index, nativeSizeType) // handle = module.createCall(fn.internalName, [
], NativeType.None); // module.createGetLocal(tempValue.index, nativeSizeType),
} else { // module.createGetLocal(assert(tempParent).index, nativeSizeType)
assert(false); // ], NativeType.None);
return module.createUnreachable(); // } else if (fn = program.retainRef) { // arc
} // handle = module.createCall(fn.internalName, [
flow.freeTempLocal(tempValue); // module.createGetLocal(tempValue.index, nativeSizeType)
flow.freeTempLocal(tempOldValue); // ], NativeType.None);
if (!this.compileFunction(fn1)) return module.createUnreachable(); // } else {
if (fn2 && !this.compileFunction(fn2)) return module.createUnreachable(); // assert(false);
// if (value != oldValue) { // return module.createUnreachable();
// if (oldValue !== null) unlink/release(oldValue[, parent]) // }
// [if (value !== null)] link/retain(value[, parent]) // flow.freeTempLocal(tempValue);
// } -> value // if (!this.compileFunction(fn)) return module.createUnreachable();
return module.createIf( // // {
module.createBinary(nativeSizeType == NativeType.I32 ? BinaryOp.NeI32 : BinaryOp.NeI64, // // [if (value !== null)] link/retain(value[, parent])
module.createTeeLocal(tempValue.index, valueExpr), // // } -> value
module.createTeeLocal(tempOldValue.index, oldValueExpr) // return module.createBlock(null, [
), // module.createSetLocal(tempValue.index, valueExpr),
module.createBlock(null, [ // nullable
handleOld // ? module.createIf(
? module.createIf( // module.createGetLocal(tempValue.index, nativeSizeType),
module.createGetLocal(tempOldValue.index, nativeSizeType), // handle
handleOld // )
) // : handle,
: module.createNop(), // module.createGetLocal(tempValue.index, nativeSizeType)
nullable // ], nativeSizeType);
? module.createIf( // }
module.createGetLocal(tempValue.index, nativeSizeType),
handleNew // /** Prepares the replaces a reference hold by an _initialized_ parent using the GC interface. */
) // makeReplaceRef(
: handleNew, // valueExpr: ExpressionRef,
module.createGetLocal(tempValue.index, nativeSizeType) // oldValueExpr: ExpressionRef,
], nativeSizeType), // tempParent: Local | null,
module.createGetLocal(tempValue.index, nativeSizeType) // 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( makeInstanceOfClass(
expr: ExpressionRef, expr: ExpressionRef,

View File

@ -349,58 +349,49 @@ export class Program extends DiagnosticEmitter {
/** Managed classes contained in the program, by id. */ /** Managed classes contained in the program, by id. */
managedClasses: Map<i32,Class> = new Map(); managedClasses: Map<i32,Class> = new Map();
// runtime references // standard references
/** ArrayBufferView reference. */ /** ArrayBufferView reference. */
arrayBufferViewInstance: Class | null = null; arrayBufferViewInstance: Class;
/** ArrayBuffer instance reference. */ /** ArrayBuffer instance reference. */
arrayBufferInstance: Class | null = null; arrayBufferInstance: Class;
/** Array prototype reference. */ /** Array prototype reference. */
arrayPrototype: ClassPrototype | null = null; arrayPrototype: ClassPrototype;
/** Set prototype reference. */ /** Set prototype reference. */
setPrototype: ClassPrototype | null = null; setPrototype: ClassPrototype;
/** Map prototype reference. */ /** Map prototype reference. */
mapPrototype: ClassPrototype | null = null; mapPrototype: ClassPrototype;
/** Fixed array prototype reference. */ /** Fixed array prototype reference. */
fixedArrayPrototype: ClassPrototype | null = null; fixedArrayPrototype: ClassPrototype;
/** String instance reference. */ /** String instance reference. */
stringInstance: Class | null = null; stringInstance: Class;
/** Abort function reference, if present. */ /** Abort function reference, if present. */
abortInstance: Function | null = null; abortInstance: Function;
/** Runtime allocation function. `allocate(payloadSize: usize): usize` */ // runtime references
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;
/** The kind of garbage collector being present. */ /** RT `__alloc(size: usize, id: u32): usize` */
collectorKind: CollectorKind = CollectorKind.NONE; allocInstance: Function;
/** Memory allocation implementation, if present: `__mem_allocate(size: usize): usize` */ /** RT `__realloc(ref: usize, newSize: usize): usize` */
allocateMem: Function | null = null; reallocInstance: Function;
/** Memory free implementation, if present: `__mem_free(ref: usize): void` */ /** RT `__free(ref: usize): void` */
freeMem: Function | null = null; freeInstance: Function;
/** Reference link implementation, if present: `__ref_link(ref: usize, parentRef: usize): void` */ /** RT `__retain(ref: usize): usize` */
linkRef: Function | null = null; retainInstance: Function;
/** Reference unlink implementation, if present: `__ref_unlink(ref: usize, parentRef: usize): void` */ /** RT `__release(ref: usize): void` */
unlinkRef: Function | null = null; releaseInstance: Function;
/** Reference retain implementation, if present: `__ref_retain(ref: usize): void` */ /** RT `__retainRelease(newRef: usize, oldRef: usize): usize` */
retainRef: Function | null = null; retainReleaseInstance: Function;
/** Reference release implementation, if present: `__ref_release(ref: usize): void` */ /** RT `__collect(): void` */
releaseRef: Function | null = null; collectInstance: Function;
/** Reference mark implementation, if present: `__ref_mark(ref: usize): void` */ /** RT `__visit(ref: usize, cookie: u32): void` */
markRef: Function | null = null; 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. */ /** Next class id. */
nextClassId: u32 = 1; nextClassId: u32 = 1;
@ -805,104 +796,26 @@ export class Program extends DiagnosticEmitter {
} }
} }
// register library elements // register stdlib components
{ this.arrayBufferViewInstance = this.requireClass(CommonSymbols.ArrayBufferView);
let element: Element | null; this.arrayBufferInstance = this.requireClass(CommonSymbols.ArrayBuffer);
if (element = this.lookupGlobal(CommonSymbols.ArrayBufferView)) { this.stringInstance = this.requireClass(CommonSymbols.String);
assert(element.kind == ElementKind.CLASS_PROTOTYPE); this.arrayPrototype = <ClassPrototype>this.require(CommonSymbols.Array, ElementKind.CLASS_PROTOTYPE);
this.arrayBufferViewInstance = resolver.resolveClass(<ClassPrototype>element, null); this.fixedArrayPrototype = <ClassPrototype>this.require(CommonSymbols.FixedArray, ElementKind.CLASS_PROTOTYPE);
} this.setPrototype = <ClassPrototype>this.require(CommonSymbols.Set, ElementKind.CLASS_PROTOTYPE);
if (element = this.lookupGlobal(CommonSymbols.ArrayBuffer)) { this.mapPrototype = <ClassPrototype>this.require(CommonSymbols.Map, ElementKind.CLASS_PROTOTYPE);
assert(element.kind == ElementKind.CLASS_PROTOTYPE); this.abortInstance = this.requireFunction(CommonSymbols.abort);
this.arrayBufferInstance = resolver.resolveClass(<ClassPrototype>element, null); this.allocInstance = this.requireFunction(CommonSymbols.alloc);
} this.reallocInstance = this.requireFunction(CommonSymbols.realloc);
if (element = this.lookupGlobal(CommonSymbols.String)) { this.freeInstance = this.requireFunction(CommonSymbols.free);
assert(element.kind == ElementKind.CLASS_PROTOTYPE); this.retainInstance = this.requireFunction(CommonSymbols.retain);
this.stringInstance = resolver.resolveClass(<ClassPrototype>element, null); this.releaseInstance = this.requireFunction(CommonSymbols.release);
} this.retainReleaseInstance = this.requireFunction(CommonSymbols.retainRelease);
if (element = this.lookupGlobal(CommonSymbols.Array)) { this.collectInstance = this.requireFunction(CommonSymbols.collect);
assert(element.kind == ElementKind.CLASS_PROTOTYPE); this.typeinfoInstance = this.requireFunction(CommonSymbols.typeinfo);
this.arrayPrototype = <ClassPrototype>element; this.instanceofInstance = this.requireFunction(CommonSymbols.instanceof_);
} this.visitInstance = this.requireFunction(CommonSymbols.visit);
if (element = this.lookupGlobal(CommonSymbols.FixedArray)) { this.allocArrayInstance = this.requireFunction(CommonSymbols.allocArray);
assert(element.kind == ElementKind.CLASS_PROTOTYPE);
this.fixedArrayPrototype = <ClassPrototype>element;
}
if (element = this.lookupGlobal(CommonSymbols.Set)) {
assert(element.kind == ElementKind.CLASS_PROTOTYPE);
this.setPrototype = <ClassPrototype>element;
}
if (element = this.lookupGlobal(CommonSymbols.Map)) {
assert(element.kind == ElementKind.CLASS_PROTOTYPE);
this.mapPrototype = <ClassPrototype>element;
}
if (element = this.lookupGlobal(CommonSymbols.abort)) {
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
this.abortInstance = this.resolver.resolveFunction(<FunctionPrototype>element, null);
}
if (element = this.lookupGlobal(BuiltinSymbols.runtime_allocate)) {
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
this.allocateInstance = this.resolver.resolveFunction(<FunctionPrototype>element, null);
}
if (element = this.lookupGlobal(BuiltinSymbols.memory_allocate)) {
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
this.memoryAllocateInstance = this.resolver.resolveFunction(<FunctionPrototype>element, null);
}
if (element = this.lookupGlobal(BuiltinSymbols.runtime_reallocate)) {
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
this.reallocateInstance = this.resolver.resolveFunction(<FunctionPrototype>element, null);
}
if (element = this.lookupGlobal(BuiltinSymbols.runtime_discard)) {
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
this.discardInstance = this.resolver.resolveFunction(<FunctionPrototype>element, null);
}
if (element = this.lookupGlobal(BuiltinSymbols.runtime_register)) {
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
this.registerInstance = this.resolver.resolveFunction(<FunctionPrototype>element, null);
}
if (element = this.lookupGlobal(BuiltinSymbols.runtime_makeArray)) {
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
this.makeArrayInstance = this.resolver.resolveFunction(<FunctionPrototype>element, null);
}
if (element = this.lookupGlobal(BuiltinSymbols.runtime_instanceof)) {
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
this.instanceofInstance = this.resolver.resolveFunction(<FunctionPrototype>element, null);
}
if (element = this.lookupGlobal(BuiltinSymbols.runtime_flags)) {
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
this.flagsInstance = this.resolver.resolveFunction(<FunctionPrototype>element, null);
}
// memory allocator interface
if (element = this.lookupGlobal("__mem_allocate")) {
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
this.allocateMem = this.resolver.resolveFunction(<FunctionPrototype>element, null);
element = assert(this.lookupGlobal("__mem_free"));
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
this.freeMem = this.resolver.resolveFunction(<FunctionPrototype>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(<FunctionPrototype>element, null);
if (element = this.lookupGlobal("__ref_unlink")) {
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
this.unlinkRef = this.resolver.resolveFunction(<FunctionPrototype>element, null);
}
element = assert(this.lookupGlobal("__ref_mark"));
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
this.markRef = this.resolver.resolveFunction(<FunctionPrototype>element, null);
this.collectorKind = CollectorKind.TRACING;
} else if (element = this.lookupGlobal("__ref_retain")) {
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
this.retainRef = this.resolver.resolveFunction(<FunctionPrototype>element, null);
element = assert(this.lookupGlobal("__ref_release"));
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
this.releaseRef = this.resolver.resolveFunction(<FunctionPrototype>element, null);
this.collectorKind = CollectorKind.COUNTING;
}
}
}
// mark module exports, i.e. to apply proper wrapping behavior on the boundaries // mark module exports, i.e. to apply proper wrapping behavior on the boundaries
for (let file of this.filesByName.values()) { 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(<ClassPrototype>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(<FunctionPrototype>prototype, null);
if (!resolved) throw new Error("invalid " + name);
return resolved;
}
/** Marks an element and its children as a module export. */ /** Marks an element and its children as a module export. */
private markModuleExport(element: Element): void { private markModuleExport(element: Element): void {
element.set(CommonFlags.MODULE_EXPORT); element.set(CommonFlags.MODULE_EXPORT);

View File

@ -153,12 +153,9 @@ export class Type {
/** Tests if this is a managed type that needs GC hooks. */ /** Tests if this is a managed type that needs GC hooks. */
isManaged(program: Program): bool { isManaged(program: Program): bool {
if (program.collectorKind != CollectorKind.NONE) { var classReference = this.classReference;
let classReference = this.classReference;
return classReference !== null && !classReference.hasDecorator(DecoratorFlags.UNMANAGED); return classReference !== null && !classReference.hasDecorator(DecoratorFlags.UNMANAGED);
} }
return false;
}
/** Tests if this is a class type explicitly annotated as unmanaged. */ /** Tests if this is a class type explicitly annotated as unmanaged. */
get isUnmanaged(): bool { get isUnmanaged(): bool {

View File

@ -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`<br />
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`<br />
Frees a dynamically allocated chunk of memory by its address.
Optional
--------
* **__mem_reset**(ref: `usize`, parentRef: `usize`): `void`<br />
Resets dynamic memory to its initial state. Used by the arena allocator.

View File

@ -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<usize>(size, 1) + AL_MASK) & ~AL_MASK;
var pagesBefore = memory.size();
if (newPtr > <usize>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;
}

View File

@ -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);
}

View File

@ -1,3 +0,0 @@
declare function __mem_allocate(size: usize): usize;
declare function __mem_free(ref: usize): void;
declare function __mem_reset(): void;

View File

@ -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);
}

View File

@ -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 << <usize>SL_BITS;
// @ts-ignore: decorator
@inline
const SB_BITS: usize = <usize>(SL_BITS + AL_BITS);
// @ts-ignore: decorator
@inline
const SB_SIZE: usize = 1 << <usize>SB_BITS;
// @ts-ignore: decorator
@inline
const FL_BITS: u32 = (sizeof<usize>() == sizeof<u32>()
? 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<Block>("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<usize>() + 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<Block>(changetype<usize>(this) - sizeof<usize>())
); // 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<Block>(
changetype<usize>(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<usize>();
// 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<u32>(changetype<usize>(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<u32>(changetype<usize>(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<Block>(load<usize>(
changetype<usize>(this) + (fl * SL_SIZE + <usize>sl) * sizeof<usize>()
, 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<usize>(
changetype<usize>(this) + (fl * SL_SIZE + <usize>sl) * sizeof<usize>(),
changetype<usize>(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<usize>();
get tailRef(): usize { return load<usize>(changetype<usize>(this), Root.HL_END); }
set tailRef(value: usize) { store<usize>(changetype<usize>(this), value, Root.HL_END); }
/** Total size of the {@link Root} structure. */
@inline
static readonly SIZE: usize = Root.HL_END + sizeof<usize>();
/** 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 = <u32>(size / AL_SIZE);
} else {
fl = fls<usize>(size);
sl = <u32>((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 = <u32>(size / AL_SIZE);
} else {
fl = fls<usize>(size);
sl = <u32>((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 = <u32>(size / AL_SIZE);
} else {
// (*) size += (1 << (fls<usize>(size) - SL_BITS)) - 1;
fl = fls<usize>(size);
sl = <u32>((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<usize>(flMap);
slMap = assert(this.getSLMap(fl)); // can't be zero if fl points here
head = this.getHead(fl, ffs<u32>(slMap));
}
} else {
head = this.getHead(fl, ffs<u32>(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<Block>(
changetype<usize>(right) - sizeof<usize>()
, 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<Block>(
changetype<usize>(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<usize>(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<Block>(tailRef).info;
}
} else {
assert(start >= changetype<usize>(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<Block>(start);
left.info = leftSize | FREE | (tailInfo & LEFT_FREE);
left.prev = null;
left.next = null;
// tail is a zero-length used block
var tail = changetype<Block>(start + size - Block.HEADER_SIZE);
tail.info = 0 | LEFT_FREE;
this.tailRef = changetype<usize>(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<T extends number>(word: T): T {
assert(word != 0); // word cannot be 0
return ctz<T>(word); // differs from ffs only for 0
}
/** Determines the last (LSB to MSB) set bit's index of a word. */
function fls<T extends number>(word: T): T {
assert(word != 0); // word cannot be 0
// @ts-ignore: type
const inv: T = (sizeof<T>() << 3) - 1;
// @ts-ignore: type
return inv - clz<T>(word);
}
/** Reference to the initialized {@link Root} structure, once initialized. */
// @ts-ignore: decorator
@lazy
var ROOT: Root = changetype<Root>(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 = <i32>((((rootOffset + Root.SIZE) + 0xffff) & ~0xffff) >>> 16);
if (pagesNeeded > pagesBefore && memory.grow(pagesNeeded - pagesBefore) < 0) unreachable();
ROOT = root = changetype<Root>(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<usize>((size + AL_MASK) & ~AL_MASK, Block.MIN_SIZE);
var block = root.search(size);
if (!block) {
// request more memory
let pagesBefore = memory.size();
let pagesNeeded = <i32>(((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(<usize>pagesBefore << 16, <usize>pagesAfter << 16);
block = assert(root.search(size)); // must be found now
}
assert((block.info & ~TAGS) >= size); // must fit
return root.use(<Block>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<Block>(data - Block.HEADER_SIZE);
let blockInfo = block.info;
assert(!(blockInfo & FREE)); // must be used
block.info = blockInfo | FREE;
root.insert(changetype<Block>(data - Block.HEADER_SIZE));
}
}
}

View File

@ -1,22 +1,21 @@
/// <reference path="./collector/index.d.ts" /> /// <reference path="./rt/index.d.ts" />
import { MAX_BYTELENGTH, allocate, reallocate, discard, register, NEWARRAY } from "./util/runtime"; import { BLOCK_MAXSIZE } from "./rt/common";
import { COMPARATOR, SORT } from "./util/sort"; import { COMPARATOR, SORT } from "./util/sort";
import { __runtime_id, __gc_mark_members } from "./runtime";
import { ArrayBuffer, ArrayBufferView } from "./arraybuffer"; import { ArrayBuffer, ArrayBufferView } from "./arraybuffer";
import { itoa, dtoa, itoa_stream, dtoa_stream, MAX_DOUBLE_LENGTH } from "./util/number"; 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"; import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH, E_EMPTYARRAY, E_HOLEYARRAY } from "./util/error";
/** Ensures that the given array has _at least_ the specified capacity. */ /** Ensures that the given array has _at least_ the specified capacity. */
function ensureCapacity(array: ArrayBufferView, minCapacity: i32, alignLog2: u32): void { function ensureCapacity(array: ArrayBufferView, minCapacity: i32, alignLog2: u32): void {
if (<u32>minCapacity > <u32>array.dataLength >>> alignLog2) { if (<u32>minCapacity > <u32>array.dataLength >>> alignLog2) {
if (<u32>minCapacity > <u32>(MAX_BYTELENGTH >>> alignLog2)) throw new RangeError(E_INVALIDLENGTH); if (<u32>minCapacity > <u32>(BLOCK_MAXSIZE >>> alignLog2)) throw new RangeError(E_INVALIDLENGTH);
let oldData = array.data; let oldData = array.data;
let newByteLength = minCapacity << alignLog2; let newByteLength = minCapacity << alignLog2;
let newData = reallocate(changetype<usize>(oldData), <usize>newByteLength); // registers on move let newData = __realloc(changetype<usize>(oldData), <usize>newByteLength); // registers on move
if (newData !== changetype<usize>(oldData)) { if (newData !== changetype<usize>(oldData)) {
array.data = changetype<ArrayBuffer>(newData); // links array.data = changetype<ArrayBuffer>(newData); // retains
array.dataStart = newData; array.dataStart = newData;
} }
array.dataLength = newByteLength; array.dataLength = newByteLength;
@ -41,11 +40,11 @@ export class Array<T> extends ArrayBufferView {
} }
static create<T>(capacity: i32 = 0): Array<T> { static create<T>(capacity: i32 = 0): Array<T> {
if (<u32>capacity > <u32>MAX_BYTELENGTH >>> alignof<T>()) throw new RangeError(E_INVALIDLENGTH); if (<u32>capacity > <u32>BLOCK_MAXSIZE >>> alignof<T>()) throw new RangeError(E_INVALIDLENGTH);
var array = NEWARRAY<T>(capacity); var array = __allocArray(capacity, alignof<T>(), idof<T[]>());
array.length_ = 0; // safe even if T is a non-nullable reference changetype<Array<T>>(array).length_ = 0; // safe even if T is a non-nullable reference
memory.fill(array.dataStart, 0, array.dataLength); memory.fill(changetype<ArrayBufferView>(array).dataStart, 0, changetype<ArrayBufferView>(array).dataLength);
return array; return changetype<Array<T>>(array); // retains
} }
constructor(length: i32 = 0) { constructor(length: i32 = 0) {
@ -119,27 +118,7 @@ export class Array<T> extends ArrayBufferView {
@operator("{}=") private __unchecked_set(index: i32, value: T): void { @operator("{}=") private __unchecked_set(index: i32, value: T): void {
if (isManaged<T>()) { if (isManaged<T>()) {
let offset = this.dataStart + (<usize>index << alignof<T>()); let offset = this.dataStart + (<usize>index << alignof<T>());
let oldValue = load<T>(offset); store<usize>(offset, __retainRelease(changetype<usize>(value), load<usize>(offset)));
if (value !== oldValue) {
store<T>(offset, value);
if (isNullable<T>()) {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) if (oldValue !== null) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
if (value !== null) __ref_link(changetype<usize>(value), changetype<usize>(this));
} else if (isDefined(__ref_retain)) {
if (oldValue !== null) __ref_release(changetype<usize>(oldValue));
if (value !== null) __ref_retain(changetype<usize>(value));
} else assert(false);
} else {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) if (oldValue !== null) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
__ref_link(changetype<usize>(value), changetype<usize>(this));
} else if (__ref_retain) {
if (oldValue !== null) __ref_release(changetype<usize>(oldValue));
__ref_retain(changetype<usize>(value));
} else assert(false);
}
}
} else { } else {
store<T>(this.dataStart + (<usize>index << alignof<T>()), value); store<T>(this.dataStart + (<usize>index << alignof<T>()), value);
} }
@ -201,27 +180,7 @@ export class Array<T> extends ArrayBufferView {
ensureCapacity(this, newLength, alignof<T>()); ensureCapacity(this, newLength, alignof<T>());
if (isManaged<T>()) { if (isManaged<T>()) {
let offset = this.dataStart + (<usize>length << alignof<T>()); let offset = this.dataStart + (<usize>length << alignof<T>());
let oldValue = load<T>(offset); store<usize>(offset, __retainRelease(changetype<usize>(value), load<usize>(offset)));
if (oldValue !== value) {
store<T>(offset, value);
if (isNullable<T>()) {
if (isDefined(__ref_link)) {
if (value !== null) __ref_link(changetype<usize>(value), changetype<usize>(this));
if (isDefined(__ref_unlink)) if (oldValue !== null) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
} else if (__ref_retain) {
if (oldValue !== null) __ref_retain(changetype<usize>(value));
if (value !== null) __ref_release(changetype<usize>(oldValue));
} else assert(false);
} else {
if (isDefined(__ref_link)) {
__ref_link(changetype<usize>(value), changetype<usize>(this));
if (isDefined(__ref_unlink)) if (oldValue !== null) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
} else if (__ref_retain) {
__ref_retain(changetype<usize>(value));
if (oldValue !== null) __ref_release(changetype<usize>(oldValue));
} else assert(false);
}
}
} else { } else {
store<T>(this.dataStart + (<usize>length << alignof<T>()), value); store<T>(this.dataStart + (<usize>length << alignof<T>()), value);
} }
@ -233,50 +192,28 @@ export class Array<T> extends ArrayBufferView {
var thisLen = this.length_; var thisLen = this.length_;
var otherLen = select(0, other.length_, other === null); var otherLen = select(0, other.length_, other === null);
var outLen = thisLen + otherLen; var outLen = thisLen + otherLen;
if (<u32>outLen > <u32>MAX_BYTELENGTH >>> alignof<T>()) throw new Error(E_INVALIDLENGTH); if (<u32>outLen > <u32>BLOCK_MAXSIZE >>> alignof<T>()) throw new Error(E_INVALIDLENGTH);
var out = NEWARRAY<T>(outLen); var out = __allocArray(outLen, alignof<T>(), idof<Array<T>>());
var outStart = out.dataStart; var outStart = changetype<ArrayBufferView>(out).dataStart;
var thisSize = <usize>thisLen << alignof<T>(); var thisSize = <usize>thisLen << alignof<T>();
if (isManaged<T>()) { if (isManaged<T>()) {
let thisStart = this.dataStart; let thisStart = this.dataStart;
for (let offset: usize = 0; offset < thisSize; offset += sizeof<T>()) { for (let offset: usize = 0; offset < thisSize; offset += sizeof<T>()) {
let ref = load<usize>(thisStart + offset); let ref = load<usize>(thisStart + offset);
store<usize>(outStart + offset, ref); store<usize>(outStart + offset, __retain(ref));
if (isNullable<T>()) {
if (ref) {
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
} else {
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
} }
outStart += thisSize; outStart += thisSize;
let otherStart = other.dataStart; let otherStart = other.dataStart;
let otherSize = <usize>otherLen << alignof<T>(); let otherSize = <usize>otherLen << alignof<T>();
for (let offset: usize = 0; offset < otherSize; offset += sizeof<T>()) { for (let offset: usize = 0; offset < otherSize; offset += sizeof<T>()) {
let ref = load<usize>(otherStart + offset); let ref = load<usize>(otherStart + offset);
store<usize>(outStart + offset, ref); store<usize>(outStart + offset, __retain(ref));
if (isNullable<T>()) {
if (ref) {
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
} else {
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
} }
} else { } else {
memory.copy(outStart, this.dataStart, thisSize); memory.copy(outStart, this.dataStart, thisSize);
memory.copy(outStart + thisSize, other.dataStart, <usize>otherLen << alignof<T>()); memory.copy(outStart + thisSize, other.dataStart, <usize>otherLen << alignof<T>());
} }
return out; return changetype<Array<T>>(out);
} }
copyWithin(target: i32, start: i32, end: i32 = i32.MAX_VALUE): this { copyWithin(target: i32, start: i32, end: i32 = i32.MAX_VALUE): this {
@ -322,38 +259,27 @@ export class Array<T> extends ArrayBufferView {
map<U>(callbackfn: (value: T, index: i32, array: Array<T>) => U): Array<U> { map<U>(callbackfn: (value: T, index: i32, array: Array<T>) => U): Array<U> {
var length = this.length_; var length = this.length_;
var out = NEWARRAY<U>(length); var out = __allocArray(length, alignof<U>(), idof<Array<U>>());
var outStart = out.dataStart; var outStart = changetype<ArrayBufferView>(out).dataStart;
for (let index = 0; index < min(length, this.length_); ++index) { for (let index = 0; index < min(length, this.length_); ++index) {
let value = load<T>(this.dataStart + (<usize>index << alignof<T>())); let result = callbackfn(load<T>(this.dataStart + (<usize>index << alignof<T>())), index, this); // retains
if (isManaged<U>()) { if (isManaged<U>()) {
let ref = changetype<usize>(callbackfn(value, index, this)); store<usize>(outStart + (<usize>index << alignof<U>()), __retain(changetype<usize>(result)));
store<usize>(outStart + (<usize>index << alignof<U>()), ref);
if (isNullable<T>()) {
if (ref) {
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(out));
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
} else { } else {
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(out)); store<U>(outStart + (<usize>index << alignof<U>()), result);
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
} }
} else { // releases result
store<U>(outStart + (<usize>index << alignof<U>()), callbackfn(value, index, this));
} }
} return changetype<Array<U>>(out); // retains
return out;
} }
filter(callbackfn: (value: T, index: i32, array: Array<T>) => bool): Array<T> { filter(callbackfn: (value: T, index: i32, array: Array<T>) => bool): Array<T> {
var result = NEWARRAY<T>(0); var result = __allocArray(0, alignof<T>(), idof<Array<T>>());
for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) { for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) {
let value = load<T>(this.dataStart + (<usize>index << alignof<T>())); let value = load<T>(this.dataStart + (<usize>index << alignof<T>()));
if (callbackfn(value, index, this)) result.push(value); if (callbackfn(value, index, this)) changetype<Array<T>>(result).push(value);
} }
return result; return changetype<Array<T>>(result); // retains
} }
reduce<U>( reduce<U>(
@ -413,19 +339,10 @@ export class Array<T> extends ArrayBufferView {
dataStart, dataStart,
<usize>(newLength - 1) << alignof<T>() <usize>(newLength - 1) << alignof<T>()
); );
store<T>(dataStart, element);
if (isManaged<T>()) { if (isManaged<T>()) {
if (isNullable<T>()) { store<usize>(dataStart, __retain(changetype<usize>(element)));
if (element !== null) {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(element), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(element));
else assert(false);
}
} else { } else {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(element), changetype<usize>(this)); store<T>(dataStart, element);
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(element));
else assert(false);
}
} }
this.length_ = newLength; this.length_ = newLength;
return newLength; return newLength;
@ -436,61 +353,41 @@ export class Array<T> extends ArrayBufferView {
begin = begin < 0 ? max(begin + length, 0) : min(begin, length); begin = begin < 0 ? max(begin + length, 0) : min(begin, length);
end = end < 0 ? max(end + length, 0) : min(end , length); end = end < 0 ? max(end + length, 0) : min(end , length);
length = max(end - begin, 0); length = max(end - begin, 0);
var slice = NEWARRAY<T>(length); var slice = __allocArray(length, alignof<T>(), idof<Array<T>>());
var sliceBase = slice.dataStart; var sliceBase = changetype<ArrayBufferView>(slice).dataStart;
var thisBase = this.dataStart + (<usize>begin << alignof<T>()); var thisBase = this.dataStart + (<usize>begin << alignof<T>());
if (isManaged<T>()) { if (isManaged<T>()) {
let off = <usize>0; let off = <usize>0;
let end = <usize>length << alignof<usize>(); let end = <usize>length << alignof<usize>();
while (off < end) { while (off < end) {
let ref = load<usize>(thisBase + off); let ref = load<usize>(thisBase + off);
store<usize>(sliceBase + off, ref); store<usize>(sliceBase + off, __retain(ref));
if (isNullable<T>()) {
if (ref) {
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(slice));
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
} else {
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(slice));
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
off += sizeof<usize>(); off += sizeof<usize>();
} }
} else { } else {
memory.copy(sliceBase, thisBase, length << alignof<T>()); memory.copy(sliceBase, thisBase, length << alignof<T>());
} }
return slice; return changetype<Array<T>>(slice); // retains
} }
splice(start: i32, deleteCount: i32 = i32.MAX_VALUE): Array<T> { splice(start: i32, deleteCount: i32 = i32.MAX_VALUE): Array<T> {
var length = this.length_; var length = this.length_;
start = start < 0 ? max<i32>(length + start, 0) : min<i32>(start, length); start = start < 0 ? max<i32>(length + start, 0) : min<i32>(start, length);
deleteCount = max<i32>(min<i32>(deleteCount, length - start), 0); deleteCount = max<i32>(min<i32>(deleteCount, length - start), 0);
var result = NEWARRAY<T>(deleteCount); var result = __allocArray(deleteCount, alignof<T>(), idof<Array<T>>());
var resultStart = result.dataStart; var resultStart = changetype<ArrayBufferView>(result).dataStart;
var thisStart = this.dataStart; var thisStart = this.dataStart;
var thisBase = thisStart + (<usize>start << alignof<T>()); var thisBase = thisStart + (<usize>start << alignof<T>());
if (isManaged<T>()) { if (isManaged<T>()) {
for (let i = 0; i < deleteCount; ++i) { for (let i = 0; i < deleteCount; ++i) {
let ref = load<usize>(thisBase + (<usize>i << alignof<T>())); store<usize>(resultStart + (<usize>i << alignof<T>()),
store<usize>(resultStart + (<usize>i << alignof<T>()), ref); load<usize>(thisBase + (<usize>i << alignof<T>()))
if (isDefined(__ref_link)) { );
if (isNullable<T>()) { // element is moved, so refcount doesn't change
if (ref) {
if (isDefined(__ref_unlink)) __ref_unlink(ref, changetype<usize>(this));
__ref_link(ref, changetype<usize>(result));
}
} else {
if (isDefined(__ref_unlink)) __ref_unlink(ref, changetype<usize>(this));
__ref_link(ref, changetype<usize>(result));
}
}
} }
} else { } else {
memory.copy( memory.copy(
result.dataStart, resultStart,
thisBase, thisBase,
<usize>deleteCount << alignof<T>() <usize>deleteCount << alignof<T>()
); );
@ -504,7 +401,7 @@ export class Array<T> extends ArrayBufferView {
); );
} }
this.length_ = length - deleteCount; this.length_ = length - deleteCount;
return result; return changetype<Array<T>>(result); // retains
} }
reverse(): Array<T> { reverse(): Array<T> {
@ -563,7 +460,7 @@ export class Array<T> extends ArrayBufferView {
var sepLen = separator.length; var sepLen = separator.length;
var valueLen = 5; // max possible length of element len("false") var valueLen = 5; // max possible length of element len("false")
var estLen = (valueLen + sepLen) * lastIndex + valueLen; var estLen = (valueLen + sepLen) * lastIndex + valueLen;
var result = allocate(estLen << 1); var result = __alloc(estLen << 1, idof<string>());
var offset = 0; var offset = 0;
var value: bool; var value: bool;
for (let i = 0; i < lastIndex; ++i) { for (let i = 0; i < lastIndex; ++i) {
@ -594,11 +491,9 @@ export class Array<T> extends ArrayBufferView {
offset += valueLen; offset += valueLen;
if (estLen > offset) { if (estLen > offset) {
let trimmed = changetype<string>(result).substring(0, offset); return changetype<string>(result).substring(0, offset); // retains/releases result, retains return
discard(result);
return trimmed; // registered in .substring
} }
return changetype<string>(register(result, __runtime_id<string>())); return changetype<string>(result); // retains
} }
private join_int(separator: string = ","): string { private join_int(separator: string = ","): string {
@ -606,12 +501,12 @@ export class Array<T> extends ArrayBufferView {
if (lastIndex < 0) return ""; if (lastIndex < 0) return "";
var dataStart = this.dataStart; var dataStart = this.dataStart;
// @ts-ignore: type // @ts-ignore: type
if (!lastIndex) return changetype<string>(itoa<T>(load<T>(dataStart))); if (!lastIndex) return changetype<string>(itoa<T>(load<T>(dataStart))); // retains
var sepLen = separator.length; var sepLen = separator.length;
const valueLen = (sizeof<T>() <= 4 ? 10 : 20) + i32(isSigned<T>()); const valueLen = (sizeof<T>() <= 4 ? 10 : 20) + i32(isSigned<T>());
var estLen = (valueLen + sepLen) * lastIndex + valueLen; var estLen = (valueLen + sepLen) * lastIndex + valueLen;
var result = allocate(estLen << 1); var result = __alloc(estLen << 1, idof<string>());
var offset = 0; var offset = 0;
var value: T; var value: T;
for (let i = 0; i < lastIndex; ++i) { for (let i = 0; i < lastIndex; ++i) {
@ -631,11 +526,9 @@ export class Array<T> extends ArrayBufferView {
// @ts-ignore: type // @ts-ignore: type
offset += itoa_stream<T>(result, offset, value); offset += itoa_stream<T>(result, offset, value);
if (estLen > offset) { if (estLen > offset) {
let trimmed = changetype<string>(result).substring(0, offset); return changetype<string>(result).substring(0, offset); // retains/releases result, retains return
discard(result);
return trimmed; // registered in .substring
} }
return changetype<string>(register(result, __runtime_id<string>())); return changetype<string>(result); // retains
} }
private join_flt(separator: string = ","): string { private join_flt(separator: string = ","): string {
@ -646,13 +539,13 @@ export class Array<T> extends ArrayBufferView {
return changetype<string>(dtoa( return changetype<string>(dtoa(
// @ts-ignore: type // @ts-ignore: type
load<T>(dataStart)) load<T>(dataStart))
); ); // retains
} }
const valueLen = MAX_DOUBLE_LENGTH; const valueLen = MAX_DOUBLE_LENGTH;
var sepLen = separator.length; var sepLen = separator.length;
var estLen = (valueLen + sepLen) * lastIndex + valueLen; var estLen = (valueLen + sepLen) * lastIndex + valueLen;
var result = allocate(estLen << 1); var result = __alloc(estLen << 1, idof<string>());
var offset = 0; var offset = 0;
var value: T; var value: T;
for (let i = 0; i < lastIndex; ++i) { for (let i = 0; i < lastIndex; ++i) {
@ -676,11 +569,9 @@ export class Array<T> extends ArrayBufferView {
value value
); );
if (estLen > offset) { if (estLen > offset) {
let trimmed = changetype<string>(result).substring(0, offset); return changetype<string>(result).substring(0, offset); // retains/releases result, retains return
discard(result);
return trimmed; // registered in .substring
} }
return changetype<string>(register(result, __runtime_id<string>())); return changetype<string>(result); // retains
} }
private join_str(separator: string = ","): string { private join_str(separator: string = ","): string {
@ -697,7 +588,7 @@ export class Array<T> extends ArrayBufferView {
if (value !== null) estLen += value.length; if (value !== null) estLen += value.length;
} }
var offset = 0; var offset = 0;
var result = allocate((estLen + sepLen * lastIndex) << 1); var result = __alloc((estLen + sepLen * lastIndex) << 1, idof<string>());
for (let i = 0; i < lastIndex; ++i) { for (let i = 0; i < lastIndex; ++i) {
value = load<string>(dataStart + (<usize>i << alignof<T>())); value = load<string>(dataStart + (<usize>i << alignof<T>()));
if (value !== null) { if (value !== null) {
@ -726,7 +617,7 @@ export class Array<T> extends ArrayBufferView {
<usize>changetype<string>(value).length << 1 <usize>changetype<string>(value).length << 1
); );
} }
return changetype<string>(register(result, __runtime_id<string>())); return changetype<string>(result); // retains
} }
private join_arr(separator: string = ","): string { private join_arr(separator: string = ","): string {
@ -763,7 +654,7 @@ export class Array<T> extends ArrayBufferView {
const valueLen = 15; // max possible length of element len("[object Object]") const valueLen = 15; // max possible length of element len("[object Object]")
var sepLen = separator.length; var sepLen = separator.length;
var estLen = (valueLen + sepLen) * lastIndex + valueLen; var estLen = (valueLen + sepLen) * lastIndex + valueLen;
var result = allocate(estLen << 1); var result = __alloc(estLen << 1, idof<string>());
var offset = 0; var offset = 0;
var value: T; var value: T;
for (let i = 0; i < lastIndex; ++i) { for (let i = 0; i < lastIndex; ++i) {
@ -794,11 +685,9 @@ export class Array<T> extends ArrayBufferView {
offset += valueLen; offset += valueLen;
} }
if (estLen > offset) { if (estLen > offset) {
let out = changetype<string>(result).substring(0, offset); return changetype<string>(result).substring(0, offset); // retains/releases result, retains return
discard(result);
return out; // registered in .substring
} }
return changetype<string>(register(result, __runtime_id<string>())); return changetype<string>(result); // retains
} }
toString(): string { toString(): string {
@ -807,8 +696,8 @@ export class Array<T> extends ArrayBufferView {
// GC integration // GC integration
@unsafe private __traverse(): void { @unsafe private __traverse(cookie: u32): void {
__ref_mark(changetype<usize>(this.data)); __visit(changetype<usize>(this.data), cookie);
if (isManaged<T>()) { if (isManaged<T>()) {
let cur = this.dataStart; let cur = this.dataStart;
let end = cur + <usize>this.dataLength; let end = cur + <usize>this.dataLength;
@ -816,12 +705,12 @@ export class Array<T> extends ArrayBufferView {
let val = load<usize>(cur); let val = load<usize>(cur);
if (isNullable<T>()) { if (isNullable<T>()) {
if (val) { if (val) {
__ref_mark(val); __visit(val, cookie);
__gc_mark_members(__runtime_id<T>(), val); __visit_members(val, cookie);
} }
} else { } else {
__ref_mark(val); __visit(val, cookie);
__gc_mark_members(__runtime_id<T>(), val); __visit_members(val, cookie);
} }
cur += sizeof<usize>(); cur += sizeof<usize>();
} }

View File

@ -1,5 +1,7 @@
import { HEADER, HEADER_SIZE, MAX_BYTELENGTH, allocate, register } from "./util/runtime"; /// <reference path="./rt/index.d.ts" />
import { __runtime_id } from "./runtime";
import { BLOCK, BLOCK_MAXSIZE, BLOCK_OVERHEAD } from "./rt/common";
import { idof } from "./builtins";
import { E_INVALIDLENGTH } from "./util/error"; import { E_INVALIDLENGTH } from "./util/error";
export abstract class ArrayBufferView { export abstract class ArrayBufferView {
@ -9,9 +11,9 @@ export abstract class ArrayBufferView {
@unsafe dataLength: u32; @unsafe dataLength: u32;
protected constructor(length: i32, alignLog2: i32) { protected constructor(length: i32, alignLog2: i32) {
if (<u32>length > <u32>MAX_BYTELENGTH >>> alignLog2) throw new RangeError(E_INVALIDLENGTH); if (<u32>length > <u32>BLOCK_MAXSIZE >>> alignLog2) throw new RangeError(E_INVALIDLENGTH);
var buffer = new ArrayBuffer(length = length << alignLog2); var buffer = new ArrayBuffer(length = length << alignLog2);
this.data = buffer; this.data = buffer; // retains
this.dataStart = changetype<usize>(buffer); this.dataStart = changetype<usize>(buffer);
this.dataLength = length; this.dataLength = length;
} }
@ -51,24 +53,24 @@ export abstract class ArrayBufferView {
} }
constructor(length: i32) { constructor(length: i32) {
if (<u32>length > <u32>MAX_BYTELENGTH) throw new RangeError(E_INVALIDLENGTH); if (<u32>length > <u32>BLOCK_MAXSIZE) throw new RangeError(E_INVALIDLENGTH);
var buffer = allocate(<usize>length); var buffer = __alloc(<usize>length, idof<ArrayBuffer>());
memory.fill(changetype<usize>(buffer), 0, <usize>length); memory.fill(buffer, 0, <usize>length);
return changetype<ArrayBuffer>(register(buffer, __runtime_id<ArrayBuffer>())); return changetype<ArrayBuffer>(buffer); // retains
} }
get byteLength(): i32 { get byteLength(): i32 {
return changetype<HEADER>(changetype<usize>(this) - HEADER_SIZE).payloadSize; return changetype<BLOCK>(changetype<usize>(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; var length = this.byteLength;
begin = begin < 0 ? max(length + begin, 0) : min(begin, length); begin = begin < 0 ? max(length + begin, 0) : min(begin, length);
end = end < 0 ? max(length + end , 0) : min(end , length); end = end < 0 ? max(length + end , 0) : min(end , length);
var outSize = <usize>max(end - begin, 0); var outSize = <usize>max(end - begin, 0);
var out = allocate(outSize); var out = __alloc(outSize, idof<ArrayBuffer>());
memory.copy(out, changetype<usize>(this) + <usize>begin, outSize); memory.copy(out, changetype<usize>(this) + <usize>begin, outSize);
return changetype<ArrayBuffer>(register(out, __runtime_id<ArrayBuffer>())); return changetype<ArrayBuffer>(out); // retains
} }
toString(): string { toString(): string {

View File

@ -1740,3 +1740,23 @@ declare function trace(
a3?: f64, a3?: f64,
a4?: f64 a4?: f64
): void; ): 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<T>(): 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;

View File

@ -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`<br />
Triggers a full garbage collection cycle. Also indicates the presence of a GC.
Tracing
-------
* **__ref_register**(ref: `usize`): `void`<br />
Sets up a new reference.
* **__ref_link**(ref: `usize`, parentRef: `usize`): `void`<br />
Links a reference to a parent that is now referencing it.
* **__ref_unlink**(ref: `usize`, parentRef: `usize`): `void`<br />
Unlinks a reference from a parent that was referencing it. Implementation is optional.
* **__ref_mark**(ref: `usize`): `void`<br />
Marks a reference as being reachable so it doesn't become sweeped.
Reference counting
------------------
* **__ref_register**(ref: `usize`): `void`<br />
Sets up a new reference. Implementation is optional.
* **__ref_retain**(ref: `usize`): `void`<br />
Retains a reference, usually incrementing RC.
* **__ref_release**(ref: `usize`): `void`<br />
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
/// <reference path="./collector/index.d.ts" />
if (isManaged<T>()) {
// compiled only if T is a managed reference
... pattern ...
}
```
### Insertion
```ts
if (isNullable<T>()) {
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<T>()) {
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<T>()) {
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.

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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 {
// <HEADER>
classId: u32;
payloadSize: u32;
// </HEADER>
/** 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<ManagedObject>(this.nextWithColor & ~3);
}
/** Sets the pointer to the next object. */
set next(obj: ManagedObject) {
this.nextWithColor = changetype<usize>(obj) | (this.nextWithColor & 3);
}
/** Gets this object's color. */
get color(): i32 {
return this.nextWithColor & 3;
}
/** Sets this object's color. */
set color(color: i32) {
this.nextWithColor = (this.nextWithColor & ~3) | color;
}
/** Unlinks this object from its list. */
unlink(): void {
var next = this.next;
var prev = this.prev;
if (TRACE) trace(" unlink [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<usize>(this);
this.prev = this;
}
}
function maybeInit(): void {
if (state == State.INIT) {
if (TRACE) trace("itcm~init");
fromSpace = changetype<ManagedObjectList>(memory.allocate(HEADER_SIZE));
if (TRACE) trace(" fromSpace =", 1, objToRef(fromSpace));
fromSpace.classId = -1; // would error
fromSpace.payloadSize = 0;
fromSpace.clear();
toSpace = changetype<ManagedObjectList>(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<usize>(obj) >= HEAP_BASE) memory.free(changetype<usize>(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<ManagedObject>(ref - HEADER_SIZE);
}
// @ts-ignore: decorator
@inline
function objToRef(obj: ManagedObject): usize {
return changetype<usize>(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();
}

View File

@ -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<usize>();
let newBuffer = memory.allocate(newLength);
memory.copy(newBuffer, rootsBuffer, rootsOffset);
memory.free(rootsBuffer);
rootsBuffer = newBuffer;
rootsLength = newLength;
}
store<usize>(rootsBuffer + rootsOffset, s);
rootsOffset += sizeof<usize>();
}
function systemFree(s: Header): void {
memory.free(changetype<usize>(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<T>()) { ... }
if (!s.rc) systemFree(s);
// TODO: is this correct? here, if `decrement` was generic (propagate from UNLINK<T,TParent>)
// 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<Header>(readOffset);
if (s.color == Color.PURPLE && s.rc > 0) {
markGray(s);
store<Header>(writeOffset, s);
writeOffset += sizeof<usize>();
} else {
s.buffered = false;
// remove from roots
if (s.color == Color.BLACK && !s.rc) systemFree(s);
}
readOffset += sizeof<usize>();
}
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<Header>(readOffset));
readOffset += sizeof<usize>();
}
}
// 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<Header>(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<Header>(ref - HEADER_SIZE));
}
// @ts-ignore: decorator
@global @unsafe
function __ref_release(ref: usize): void {
decrement(changetype<Header>(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.

View File

@ -1,4 +1,4 @@
import { MAX_BYTELENGTH } from "./util/runtime"; import { BLOCK_MAXSIZE } from "./rt/common";
import { ArrayBuffer } from "./arraybuffer"; import { ArrayBuffer } from "./arraybuffer";
import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH } from "./util/error"; import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH } from "./util/error";
@ -16,10 +16,10 @@ export class DataView {
byteLength: i32 = buffer.byteLength byteLength: i32 = buffer.byteLength
) { ) {
if ( if (
i32(<u32>byteLength > <u32>MAX_BYTELENGTH) | i32(<u32>byteLength > <u32>BLOCK_MAXSIZE) |
i32(<u32>byteOffset + byteLength > <u32>buffer.byteLength) i32(<u32>byteOffset + byteLength > <u32>buffer.byteLength)
) throw new RangeError(E_INVALIDLENGTH); ) throw new RangeError(E_INVALIDLENGTH);
this.data = buffer; // links this.data = buffer; // retains
var dataStart = changetype<usize>(buffer) + <usize>byteOffset; var dataStart = changetype<usize>(buffer) + <usize>byteOffset;
this.dataStart = dataStart; this.dataStart = dataStart;
this.dataLength = byteLength; this.dataLength = byteLength;

View File

@ -1,5 +1,7 @@
import { HEADER, HEADER_SIZE, MAX_BYTELENGTH, allocate, register } from "./util/runtime"; /// <reference path="./rt/index.d.ts" />
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"; import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH, E_HOLEYARRAY } from "./util/error";
// NOTE: DO NOT USE YET! // NOTE: DO NOT USE YET!
@ -11,20 +13,20 @@ export class FixedArray<T> {
[key: number]: T; [key: number]: T;
constructor(length: i32) { constructor(length: i32) {
if (<u32>length > <u32>MAX_BYTELENGTH >>> alignof<T>()) throw new RangeError(E_INVALIDLENGTH); if (<u32>length > <u32>BLOCK_MAXSIZE >>> alignof<T>()) throw new RangeError(E_INVALIDLENGTH);
if (isReference<T>()) { if (isReference<T>()) {
if (!isNullable<T>()) { if (!isNullable<T>()) {
if (length) throw new Error(E_HOLEYARRAY); if (length) throw new Error(E_HOLEYARRAY);
} }
} }
var outSize = <usize>length << alignof<T>(); var outSize = <usize>length << alignof<T>();
var out = allocate(outSize); var out = __alloc(outSize, idof<FixedArray<T>>());
memory.fill(out, 0, outSize); memory.fill(out, 0, outSize);
return changetype<FixedArray<T>>(register(out, __runtime_id<FixedArray<T>>())); return changetype<FixedArray<T>>(out); // retains
} }
get length(): i32 { get length(): i32 {
return changetype<HEADER>(changetype<usize>(this) - HEADER_SIZE).payloadSize >>> alignof<T>(); return changetype<BLOCK>(changetype<usize>(this) - BLOCK_OVERHEAD).rtSize >>> alignof<T>();
} }
@operator("[]") private __get(index: i32): T { @operator("[]") private __get(index: i32): T {
@ -44,26 +46,10 @@ export class FixedArray<T> {
@operator("{}=") private __unchecked_set(index: i32, value: T): void { @operator("{}=") private __unchecked_set(index: i32, value: T): void {
if (isManaged<T>()) { if (isManaged<T>()) {
let offset = changetype<usize>(this) + (<usize>index << alignof<T>()); let offset = changetype<usize>(this) + (<usize>index << alignof<T>());
let oldValue = load<T>(offset); let oldValue = load<usize>(offset);
if (value !== oldValue) { if (changetype<usize>(value) != oldValue) {
store<T>(offset, value); store<usize>(offset, __retain(changetype<usize>(value)));
if (oldValue !== null) { __release(changetype<usize>(oldValue));
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldValue));
else assert(false);
}
if (isNullable<T>()) {
if (value !== null) {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(value), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(value));
else assert(false);
}
} else {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(value), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(value));
else assert(false);
}
} }
} else { } else {
store<T>(changetype<usize>(this) + (<usize>index << alignof<T>()), value); store<T>(changetype<usize>(this) + (<usize>index << alignof<T>()), value);
@ -72,20 +58,20 @@ export class FixedArray<T> {
// GC integration // GC integration
@unsafe private __traverse(): void { @unsafe private __traverse(cookie: u32): void {
if (isManaged<T>()) { if (isManaged<T>()) {
let cur = changetype<usize>(this); let cur = changetype<usize>(this);
let end = cur + changetype<HEADER>(changetype<usize>(this) - HEADER_SIZE).payloadSize; let end = cur + changetype<BLOCK>(changetype<usize>(this) - BLOCK_OVERHEAD).rtSize;
while (cur < end) { while (cur < end) {
let val = load<usize>(cur); let val = load<usize>(cur);
if (isNullable<T>()) { if (isNullable<T>()) {
if (val) { if (val) {
__ref_mark(val); __visit(val, cookie);
__gc_mark_members(__runtime_id<T>(), val); __visit_members(val, cookie);
} }
} else { } else {
__ref_mark(val); __visit(val, cookie);
__gc_mark_members(__runtime_id<T>(), val); __visit_members(val, cookie);
} }
cur += sizeof<usize>(); cur += sizeof<usize>();
} }

View File

@ -1,7 +1,6 @@
/// <reference path="./collector/index.d.ts" /> /// <reference path="./rt/index.d.ts" />
import { HASH } from "./util/hash"; 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 // A deterministic hash map based on CloseTable from https://github.com/jorendorff/dht
@ -83,7 +82,7 @@ export class Map<K,V> {
} }
private find(key: K, hashCode: u32): MapEntry<K,V> | null { private find(key: K, hashCode: u32): MapEntry<K,V> | null {
var entry = load<MapEntry<K,V>>( var entry = load<MapEntry<K,V>>( // unmanaged!
changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE
); );
while (entry) { while (entry) {
@ -106,35 +105,9 @@ export class Map<K,V> {
var hashCode = HASH<K>(key); var hashCode = HASH<K>(key);
var entry = this.find(key, hashCode); // unmanaged! var entry = this.find(key, hashCode); // unmanaged!
if (entry) { if (entry) {
if (isManaged<V>()) { entry.value = isManaged<V>()
let oldValue = entry.value; ? changetype<V>(__retainRelease(changetype<usize>(value), changetype<usize>(entry.value)))
if (value !== oldValue) { : value;
entry.value = value;
if (isNullable<V>()) {
if (oldValue !== null) {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldValue));
else assert(false);
}
if (value !== null) {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(value), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(value));
else assert(false);
}
} else {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
__ref_link(changetype<usize>(value), changetype<usize>(this));
} else if (isDefined(__ref_retain)) {
__ref_release(changetype<usize>(oldValue));
__ref_retain(changetype<usize>(value));
} else assert(false);
}
}
} else {
entry.value = value;
}
} else { } else {
// check if rehashing is necessary // check if rehashing is necessary
if (this.entriesOffset == this.entriesCapacity) { if (this.entriesOffset == this.entriesCapacity) {
@ -147,35 +120,13 @@ export class Map<K,V> {
// append new entry // append new entry
let entries = this.entries; let entries = this.entries;
entry = changetype<MapEntry<K,V>>(changetype<usize>(entries) + this.entriesOffset++ * ENTRY_SIZE<K,V>()); entry = changetype<MapEntry<K,V>>(changetype<usize>(entries) + this.entriesOffset++ * ENTRY_SIZE<K,V>());
entry.key = key;
entry.value = value;
// link with the map // link with the map
if (isManaged<K>()) { entry.key = isManaged<K>()
if (isNullable<K>()) { ? changetype<K>(__retain(changetype<usize>(key)))
if (key !== null) { : key;
if (isDefined(__ref_link)) __ref_link(changetype<usize>(key), changetype<usize>(this)); entry.value = isManaged<V>()
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(key)); ? changetype<V>(__retain(changetype<usize>(value)))
else assert(false); : value;
}
} else {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(key), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(key));
else assert(false);
}
}
if (isManaged<V>()) {
if (isNullable<V>()) {
if (value !== null) {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(value), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(value));
else assert(false);
}
} else {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(value), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(value));
else assert(false);
}
}
++this.entriesCount; ++this.entriesCount;
// link with previous entry in bucket // link with previous entry in bucket
let bucketPtrBase = changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE; let bucketPtrBase = changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE;
@ -187,38 +138,8 @@ export class Map<K,V> {
delete(key: K): bool { delete(key: K): bool {
var entry = this.find(key, HASH<K>(key)); var entry = this.find(key, HASH<K>(key));
if (!entry) return false; if (!entry) return false;
if (isManaged<K>()) { if (isManaged<K>()) __release(changetype<usize>(entry.key));
let oldKey = entry.key; if (isManaged<V>()) __release(changetype<usize>(entry.value));
if (isNullable<K>()) {
if (oldKey !== null) {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldKey), changetype<usize>(this));
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldKey));
else assert(false);
}
} else {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldKey), changetype<usize>(this));
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldKey));
else assert(false);
}
}
if (isManaged<V>()) {
let oldValue = entry.key;
if (isNullable<V>()) {
if (oldValue !== null) {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldValue));
else assert(false);
}
} else {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldValue));
else assert(false);
}
}
entry.taggedNext |= EMPTY; entry.taggedNext |= EMPTY;
--this.entriesCount; --this.entriesCount;
// check if rehashing is appropriate // check if rehashing is appropriate
@ -268,10 +189,10 @@ export class Map<K,V> {
// GC integration // GC integration
@unsafe private __traverse(): void { @unsafe private __traverse(cookie: u32): void {
__ref_mark(changetype<usize>(this.buckets)); __visit(changetype<usize>(this.buckets), cookie);
var entries = this.entries; var entries = this.entries;
__ref_mark(changetype<usize>(entries)); __visit(changetype<usize>(entries), cookie);
if (isManaged<K>() || isManaged<V>()) { if (isManaged<K>() || isManaged<V>()) {
let cur = changetype<usize>(entries); let cur = changetype<usize>(entries);
let end = cur + <usize>this.entriesOffset * ENTRY_SIZE<K,V>(); let end = cur + <usize>this.entriesOffset * ENTRY_SIZE<K,V>();
@ -282,24 +203,24 @@ export class Map<K,V> {
let val = changetype<usize>(entry.key); let val = changetype<usize>(entry.key);
if (isNullable<K>()) { if (isNullable<K>()) {
if (val) { if (val) {
__ref_mark(val); __visit(val, cookie);
__gc_mark_members(__runtime_id<K>(), val); __visit_members(val, cookie);
} }
} else { } else {
__ref_mark(val); __visit(val, cookie);
__gc_mark_members(__runtime_id<K>(), val); __visit_members(val, cookie);
} }
} }
if (isManaged<V>()) { if (isManaged<V>()) {
let val = changetype<usize>(entry.value); let val = changetype<usize>(entry.value);
if (isNullable<V>()) { if (isNullable<V>()) {
if (val) { if (val) {
__ref_mark(val); __visit(val, cookie);
__gc_mark_members(__runtime_id<V>(), val); __visit_members(val, cookie);
} }
} else { } else {
__ref_mark(val); __visit(val, cookie);
__gc_mark_members(__runtime_id<V>(), val); __visit_members(val, cookie);
} }
} }
} }

View File

@ -1,20 +1,9 @@
/// <reference path="./allocator/index.d.ts" />
import { memcmp, memmove, memset } from "./util/memory"; import { memcmp, memmove, memset } from "./util/memory";
import { E_NOTIMPLEMENTED } from "./util/error"; import { E_NOTIMPLEMENTED } from "./util/error";
// @ts-ignore: decorator
@builtin
export declare const HEAP_BASE: usize;
/** Memory manager interface. */ /** Memory manager interface. */
export namespace memory { 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. */ /** Gets the size of the memory in pages. */
// @ts-ignore: decorator // @ts-ignore: decorator
@builtin @builtin
@ -53,30 +42,6 @@ export namespace memory {
throw new Error(E_NOTIMPLEMENTED); 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. */ /** Repeats a section of memory at a specific address. */
// @ts-ignore: decorator // @ts-ignore: decorator
@unsafe @unsafe

View File

@ -8,29 +8,68 @@ It is based on [the TLSF memory manager](./tlsf.ts) and [a pure reference counti
Interface Interface
--------- ---------
* **__rt_allocate**(size: `usize`, id: `u32` = 0): `usize`<br /> * **__alloc**(size: `usize`, id: `u32` = 0): `usize`<br />
Dynamically allocates a chunk of memory of at least the specified size and returns its address. 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. Alignment is guaranteed to be 16 bytes to fit up to v128 values naturally.
* **__rt_reallocate**(ref: `usize`, size: `usize`): `usize`<br /> * **__realloc**(ref: `usize`, size: `usize`): `usize`<br />
Dynamically changes the size of a chunk of memory, possibly moving it to a new address. Dynamically changes the size of a chunk of memory, possibly moving it to a new address.
* **__rt_free**(ref: `usize`): `void`<br /> * **__free**(ref: `usize`): `void`<br />
Frees a dynamically allocated chunk of memory by its address. Frees a dynamically allocated chunk of memory by its address.
* **__rt_retain**(ref: `usize`): `void`<br /> * **__retain**(ref: `usize`): `void`<br />
Retains a reference. 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`<br /> * **__release**(ref: `usize`): `void`<br />
Releases a reference. 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`<br /> * **__collect**(): `void`<br />
Forces a full garbage collection cycle. Forces a full garbage collection cycle. By default this means that reference cycles are resolved and possibly collected.
* **__rt_typeinfo**(id: `u32`): `void`<br /> * **__visit**(ref: `usize`, cookie: `u32`): `void`<br />
Obtains the runtime type information for objects of the kind represented by the specified id. 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`<br />
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`)<br />
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`)<br />
Calls `__visit` on each member of the instance pointed to by `ref` that is of a reference type.
Stub 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<usize>`ed), no such insertion happens (afterwards), but as soon as a `changetype<RefType>`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<T>(x)` with a reference type acts like a `changetype<T>(load<usize>(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.

View File

@ -12,10 +12,22 @@
// @ts-ignore: decorator // @ts-ignore: decorator
@inline export const DEBUG = true; @inline export const DEBUG = true;
/** Common block structure. */ // ╒════════════════ Common block layout (32-bit) ═════════════════╕
@unmanaged export class CommonBlock { // 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. */ /** Memory manager info. */
mmInfo: usize; // WASM64 might need adaption mmInfo: usize; // WASM64 needs adaption
/** Garbage collector info. */ /** Garbage collector info. */
gcInfo: u32; gcInfo: u32;
/** Runtime class id. */ /** Runtime class id. */
@ -24,16 +36,53 @@
rtSize: u32; rtSize: u32;
} }
/////////////////////////////////// Type information interface //////////////////////////////////// // @ts-ignore: decorator
@inline export const BLOCK_OVERHEAD = (offsetof<BLOCK>() + AL_MASK) & ~AL_MASK;
import { RTTI_BASE } from "../runtime";
import { RTTIData } from "../common/rtti";
// @ts-ignore: decorator // @ts-ignore: decorator
@global @unsafe @inline export const BLOCK_MAXSIZE: usize = (1 << 30) - BLOCK_OVERHEAD;
function __rt_typeinfo(id: u32): u32 {
var ptr: usize = RTTI_BASE; /////////////////////////////////// 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<u32>(ptr) return !id || id > load<u32>(ptr)
? unreachable() ? unreachable()
: changetype<RTTIData>(ptr + id * offsetof<RTTIData>()).flags; : changetype<RTTIData>(ptr + id * offsetof<RTTIData>()).flags;
} }
// @ts-ignore: decorator
@unsafe @global
export function __instanceof(ref: usize, superId: u32): bool { // keyword
var id = changetype<BLOCK>(ref - BLOCK_OVERHEAD).rtId;
var ptr = RTTI_BASE;
if (id && id <= load<u32>(ptr)) {
do if (id == superId) return true;
while (id = changetype<RTTIData>(ptr + id * offsetof<RTTIData>()).base);
}
return false;
}
///////////////////////////////////////////// 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<i32[]>(), id);
var bufferSize = <usize>length << alignLog2;
var buffer = __alloc(bufferSize, idof<ArrayBuffer>());
changetype<ArrayBufferView>(array).data = changetype<ArrayBuffer>(buffer); // TODO/RT: retains
changetype<ArrayBufferView>(array).dataStart = buffer;
changetype<ArrayBufferView>(array).dataLength = bufferSize;
store<i32>(changetype<usize>(array), length, offsetof<i32[]>("length_"));
if (data) memory.copy(buffer, data, bufferSize);
return array;
}

View File

@ -0,0 +1,3 @@
export { __alloc, __realloc, __free } from "./tlsf";
export { __retain, __release, __collect } from "./purerc";
export { __instanceof, __typeinfo } from "./common";

View File

@ -0,0 +1 @@
import "rt/index-stub";

View File

@ -1,12 +1,4 @@
import { AL_MASK, CommonBlock } from "./common"; import { AL_MASK, BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "rt/common";
// @ts-ignore: decorator
@inline
const BLOCK_OVERHEAD = (offsetof<CommonBlock>() + AL_MASK) & ~AL_MASK;
// @ts-ignore: decorator
@inline
const BLOCK_MAXSIZE: usize = (1 << 30) - BLOCK_OVERHEAD; // match TLSF
// @ts-ignore: decorator // @ts-ignore: decorator
@lazy @lazy
@ -16,11 +8,9 @@ var startOffset: usize = (HEAP_BASE + AL_MASK) & ~AL_MASK;
@lazy @lazy
var offset: usize = startOffset; var offset: usize = startOffset;
//////////////////////////////////// Memory manager interface /////////////////////////////////////
// @ts-ignore: decorator // @ts-ignore: decorator
@unsafe @global @unsafe @global
export function __rt_allocate(size: usize, id: u32): usize { export function __alloc(size: usize, id: u32): usize {
if (size > BLOCK_MAXSIZE) unreachable(); if (size > BLOCK_MAXSIZE) unreachable();
var ptr = offset + BLOCK_OVERHEAD; var ptr = offset + BLOCK_OVERHEAD;
var newPtr = (ptr + max<usize>(size, 1) + AL_MASK) & ~AL_MASK; var newPtr = (ptr + max<usize>(size, 1) + AL_MASK) & ~AL_MASK;
@ -33,7 +23,7 @@ export function __rt_allocate(size: usize, id: u32): usize {
} }
} }
offset = newPtr; offset = newPtr;
var block = changetype<CommonBlock>(ptr - BLOCK_OVERHEAD); var block = changetype<BLOCK>(ptr - BLOCK_OVERHEAD);
block.rtId = id; block.rtId = id;
block.rtSize = size; block.rtSize = size;
return ptr; return ptr;
@ -41,11 +31,11 @@ export function __rt_allocate(size: usize, id: u32): usize {
// @ts-ignore: decorator // @ts-ignore: decorator
@unsafe @global @unsafe @global
export function __rt_reallocate(ref: usize, size: usize): usize { export function __realloc(ref: usize, size: usize): usize {
var block = changetype<CommonBlock>(ref - BLOCK_OVERHEAD); var block = changetype<BLOCK>(ref - BLOCK_OVERHEAD);
var oldSize = <usize>block.rtSize; var oldSize = <usize>block.rtSize;
if (size > oldSize) { if (size > oldSize) {
let newRef = __rt_allocate(size, block.rtId); let newRef = __alloc(size, block.rtId);
memory.copy(newRef, ref, oldSize); memory.copy(newRef, ref, oldSize);
ref = newRef; ref = newRef;
} else { } else {
@ -56,30 +46,40 @@ export function __rt_reallocate(ref: usize, size: usize): usize {
// @ts-ignore: decorator // @ts-ignore: decorator
@unsafe @global @unsafe @global
export function __rt_free(ref: usize): void { export function __free(ref: usize): void {
} }
// @ts-ignore: decorator // @ts-ignore: decorator
@unsafe @global // @unsafe @global
export function __rt_reset(): void { // special // export function __reset(): void { // special
offset = startOffset; // offset = startOffset;
} // }
/////////////////////////////////// Garbage collector interface ///////////////////////////////////
// @ts-ignore: decorator // @ts-ignore: decorator
@global @unsafe @global @unsafe
export function __rt_retain(ref: usize): void { export function __retain(ref: usize): usize {
return ref;
} }
// @ts-ignore: decorator // @ts-ignore: decorator
@global @unsafe @global @unsafe
export function __rt_release(ref: usize): void { export function __release(ref: usize): void {
} }
// @ts-ignore: decorator // @ts-ignore: decorator
@global @unsafe @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";

13
std/assembly/rt/index.d.ts vendored Normal file
View File

@ -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;

View File

@ -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<usize>(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<usize>(reallocateBlock(ROOT, changetype<Block>(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<Block>(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<Block>(ref - BLOCK_OVERHEAD));
}
// @ts-ignore: decorator
@global @unsafe
export function __rt_release(ref: usize): void {
if (ref) decrement(changetype<Block>(ref - BLOCK_OVERHEAD));
}
// @ts-ignore: decorator
@global @unsafe
export function __rt_collect(): void {
collectCycles();
}
export { __rt_typeinfo };

View File

@ -1,14 +1,10 @@
import { DEBUG } from "./common"; import { DEBUG, BLOCK_OVERHEAD, BLOCK } from "rt/common";
import { Block, freeBlock, ROOT } from "./tlsf"; import { Block, freeBlock, ROOT } from "rt/tlsf";
import { RTTIFlags } from "common/rtti";
/////////////////////////// A Pure Reference Counting Garbage Collector /////////////////////////// /////////////////////////// A Pure Reference Counting Garbage Collector ///////////////////////////
// see: https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon03Pure.pdf // 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 ══════════════════════╕ // ╒══════════════════════ GC Info structure ══════════════════════╕
// │ 3 2 1 │ // │ 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│ // │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 // @ts-ignore: decorator
@global @global
function __rt_visit(s: Block, cookie: i32): void { function __visit(ref: usize, cookie: i32): void {
if (ref < HEAP_BASE) return;
var s = changetype<Block>(ref - BLOCK_OVERHEAD);
switch (cookie) { switch (cookie) {
case VISIT_DECREMENT: { case VISIT_DECREMENT: {
decrement(s); decrement(s);
@ -100,18 +98,18 @@ function __rt_visit(s: Block, cookie: i32): void {
} }
/** Increments the reference count of the specified block by one.*/ /** 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; var info = s.gcInfo;
assert((info & ~REFCOUNT_MASK) == ((info + 1) & ~REFCOUNT_MASK)); // overflow assert((info & ~REFCOUNT_MASK) == ((info + 1) & ~REFCOUNT_MASK)); // overflow
s.gcInfo = info + 1; s.gcInfo = info + 1;
} }
/** Decrements the reference count of the specified block by one, possibly freeing it. */ /** 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 info = s.gcInfo;
var rc = info & REFCOUNT_MASK; var rc = info & REFCOUNT_MASK;
if (rc == 1) { if (rc == 1) {
__rt_visit_members(s, VISIT_DECREMENT); __visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_DECREMENT);
if (!(info & BUFFERED_MASK)) { if (!(info & BUFFERED_MASK)) {
freeBlock(ROOT, s); freeBlock(ROOT, s);
} else { } else {
@ -119,7 +117,7 @@ export function decrement(s: Block): void {
} }
} else { } else {
if (DEBUG) assert(rc > 0); 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); s.gcInfo = BUFFERED_MASK | COLOR_PURPLE | (rc - 1);
if (!(info & BUFFERED_MASK)) { if (!(info & BUFFERED_MASK)) {
appendRoot(s); appendRoot(s);
@ -164,7 +162,9 @@ function growRoots(): void {
} }
/** Collects cyclic garbage. */ /** Collects cyclic garbage. */
export function collectCycles(): void { // @ts-ignore: decorator
@global @unsafe
export function __collect(): void {
// markRoots // markRoots
var roots = ROOTS; var roots = ROOTS;
@ -205,7 +205,7 @@ function markGray(s: Block): void {
var info = s.gcInfo; var info = s.gcInfo;
if ((info & COLOR_MASK) != COLOR_GRAY) { if ((info & COLOR_MASK) != COLOR_GRAY) {
s.gcInfo = (info & ~COLOR_MASK) | COLOR_GRAY; s.gcInfo = (info & ~COLOR_MASK) | COLOR_GRAY;
__rt_visit_members(s, VISIT_MARKGRAY); __visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_MARKGRAY);
} }
} }
@ -217,7 +217,7 @@ function scan(s: Block): void {
scanBlack(s); scanBlack(s);
} else { } else {
s.gcInfo = (info & ~COLOR_MASK) | COLOR_WHITE; s.gcInfo = (info & ~COLOR_MASK) | COLOR_WHITE;
__rt_visit_members(s, VISIT_SCAN); __visit_members(changetype<usize>(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. */ /** Marks a block as black (in use) if it was found to be reachable during the collection phase. */
function scanBlack(s: Block): void { function scanBlack(s: Block): void {
s.gcInfo = (s.gcInfo & ~COLOR_MASK) | COLOR_BLACK; s.gcInfo = (s.gcInfo & ~COLOR_MASK) | COLOR_BLACK;
__rt_visit_members(s, VISIT_SCANBLACK); __visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_SCANBLACK);
} }
/** Collects all white (member of a garbage cycle) nodes when completing the collection phase. */ /** 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; var info = s.gcInfo;
if ((info & COLOR_MASK) == COLOR_WHITE && !(info & BUFFERED_MASK)) { if ((info & COLOR_MASK) == COLOR_WHITE && !(info & BUFFERED_MASK)) {
// s.gcInfo = (info & ~COLOR_MASK) | COLOR_BLACK; // s.gcInfo = (info & ~COLOR_MASK) | COLOR_BLACK;
__rt_visit_members(s, VISIT_COLLECTWHITE); __visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_COLLECTWHITE);
} }
freeBlock(ROOT, s); freeBlock(ROOT, s);
} }
// @ts-ignore: decorator
@global @unsafe
export function __retain(ref: usize): usize {
if (ref > HEAP_BASE) increment(changetype<Block>(ref - BLOCK_OVERHEAD));
return ref;
}
// @ts-ignore: decorator
@global @unsafe
export function __release(ref: usize): void {
if (ref > HEAP_BASE) decrement(changetype<Block>(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<Block>(ref - BLOCK_OVERHEAD));
if (oldRef > heapBase) decrement(changetype<Block>(oldRef - BLOCK_OVERHEAD));
}
return ref;
}

View File

@ -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 /////////////////////// /////////////////////// The TLSF (Two-Level Segregate Fit) memory allocator ///////////////////////
// see: http://www.gii.upv.es/tlsf/ // 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 ▲ │ ◄─┘ // │ if free: back ▲ │ ◄─┘
// └───────────────────────────────────────────────────────────────┘ payload ┘ >= MIN SIZE // └───────────────────────────────────────────────────────────────┘ payload ┘ >= MIN SIZE
// F: FREE, L: LEFTFREE // 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. */ /** Previous free block, if any. Only valid if free, otherwise part of payload. */
prev: Block | null; 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. // 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 // Block constants. A block must have a minimum size of three pointers so it can hold `prev`,
// a minimum size of three pointers so it can hold `prev`, `next` and `back` if free. // `next` and `back` if free.
// @ts-ignore: decorator // @ts-ignore: decorator
@inline export const BLOCK_OVERHEAD: usize = (offsetof<Block>("prev") + AL_MASK) & ~AL_MASK; @inline const BLOCK_MINSIZE: usize = (3 * sizeof<usize>() + AL_MASK) & ~AL_MASK; // prev + next + back
// @ts-ignore: decorator // @ts-ignore: decorator
@inline const BLOCK_MINSIZE: usize = (3 * sizeof<usize>() + AL_MASK) & ~AL_MASK;// prev + next + back // @inline const BLOCK_MAXSIZE: usize = 1 << (FL_BITS + SB_BITS - 1); // exclusive, lives in common.ts
// @ts-ignore: decorator
@inline const BLOCK_MAXSIZE: usize = 1 << (FL_BITS + SB_BITS - 1); // exclusive
/** Gets the left block of a block. Only valid if the left block is free. */ /** Gets the left block of a block. Only valid if the left block is free. */
// @ts-ignore: decorator // @ts-ignore: decorator
@ -532,3 +530,32 @@ export function freeBlock(root: Root, block: Block): void {
block.mmInfo = blockInfo | FREE; block.mmInfo = blockInfo | FREE;
insertBlock(root, block); 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<usize>(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<usize>(reallocateBlock(ROOT, changetype<Block>(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<Block>(ref - BLOCK_OVERHEAD));
}

View File

@ -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<T>(): 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<HEADER>(ref - HEADER_SIZE).classId;
var ptr = RTTI_BASE;
if (id && id <= load<u32>(ptr)) {
do if (id == superId) return true;
while (id = changetype<RTTIData>(ptr + id * offsetof<RTTIData>()).base);
}
return false;
}
}
export namespace runtime {
/** Gets the runtime flags of the managed type represented by the specified runtime id. */
export function flags(id: u32): RTTIFlags {
var ptr = RTTI_BASE;
return !id || id > load<u32>(ptr)
? unreachable()
: changetype<RTTIData>(ptr + id * offsetof<RTTIData>()).flags;
}
/** Allocates and registers, but doesn't initialize the data of, a new managed object of the specified kind. */
// @ts-ignore: decorator
@unsafe
export function newObject(payloadSize: u32, id: u32): usize {
return register(allocate(<usize>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<String>());
}
/** 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<ArrayBuffer>());
}
/** 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 = (<u32>flags / RTTIFlags.VALUE_ALIGN_0) & 31;
var byteLength: i32;
if (!buffer) buffer = newArrayBuffer(byteLength = 0);
else byteLength = changetype<ArrayBuffer>(buffer).byteLength;
var array = newObject(id, offsetof<i32[]>());
changetype<ArrayBufferView>(array).data = changetype<ArrayBuffer>(buffer); // links
changetype<ArrayBufferView>(array).dataStart = buffer;
changetype<ArrayBufferView>(array).dataLength = byteLength;
store<i32>(changetype<usize>(array), byteLength >>> alignLog2, offsetof<i32[]>("length_"));
if (flags & RTTIFlags.VALUE_MANAGED) {
let cur = buffer;
let end = cur + <usize>byteLength;
while (cur < end) {
let ref = load<usize>(cur);
if (ref) {
if (isDefined(__ref_link)) __ref_link(ref, array);
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
cur += sizeof<usize>();
}
}
return array;
}
/** 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<usize>(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<usize>(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();

View File

@ -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

View File

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

View File

@ -1,4 +0,0 @@
import "allocator/tlsf";
import "collector/itcm";
export { runtime as $ };

View File

@ -1,7 +1,6 @@
/// <reference path="./collector/index.d.ts" /> /// <reference path="./rt/index.d.ts" />
import { HASH } from "./util/hash"; 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 // A deterministic hash set based on CloseTable from https://github.com/jorendorff/dht
@ -79,7 +78,7 @@ export class Set<K> {
} }
private find(key: K, hashCode: u32): SetEntry<K> | null { private find(key: K, hashCode: u32): SetEntry<K> | null {
var entry = load<SetEntry<K>>( var entry = load<SetEntry<K>>( // unmanaged!
changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE
); );
while (entry) { while (entry) {
@ -106,23 +105,10 @@ export class Set<K> {
); );
} }
// append new entry // append new entry
let entries = this.entries; entry = changetype<SetEntry<K>>(changetype<usize>(this.entries) + this.entriesOffset++ * ENTRY_SIZE<K>());
entry = changetype<SetEntry<K>>(changetype<usize>(entries) + this.entriesOffset++ * ENTRY_SIZE<K>()); entry.key = isManaged<K>()
entry.key = key; ? changetype<K>(__retain(changetype<usize>(key)))
// link with the set : key;
if (isManaged<K>()) {
if (isNullable<K>()) {
if (key !== null) {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(key), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(key));
else assert(false);
}
} else {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(key), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(key));
else assert(false);
}
}
++this.entriesCount; ++this.entriesCount;
// link with previous entry in bucket // link with previous entry in bucket
let bucketPtrBase = changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE; let bucketPtrBase = changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE;
@ -132,24 +118,9 @@ export class Set<K> {
} }
delete(key: K): bool { delete(key: K): bool {
var entry = this.find(key, HASH<K>(key)); var entry = this.find(key, HASH<K>(key)); // unmanaged!
if (!entry) return false; if (!entry) return false;
if (isManaged<K>()) { if (isManaged<K>()) __release(changetype<usize>(entry.key)); // exact 'key'
key = entry.key; // exact, e.g. string
if (isNullable<K>()) {
if (key !== null) {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(key), changetype<usize>(this));
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(key));
else assert(false);
}
} else {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(key), changetype<usize>(this));
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(key));
else assert(false);
}
}
entry.taggedNext |= EMPTY; entry.taggedNext |= EMPTY;
--this.entriesCount; --this.entriesCount;
// check if rehashing is appropriate // check if rehashing is appropriate
@ -172,9 +143,9 @@ export class Set<K> {
var oldEnd = oldPtr + <usize>this.entriesOffset * ENTRY_SIZE<K>(); var oldEnd = oldPtr + <usize>this.entriesOffset * ENTRY_SIZE<K>();
var newPtr = changetype<usize>(newEntries); var newPtr = changetype<usize>(newEntries);
while (oldPtr != oldEnd) { while (oldPtr != oldEnd) {
let oldEntry = changetype<SetEntry<K>>(oldPtr); let oldEntry = changetype<SetEntry<K>>(oldPtr); // unmanaged!
if (!(oldEntry.taggedNext & EMPTY)) { if (!(oldEntry.taggedNext & EMPTY)) {
let newEntry = changetype<SetEntry<K>>(newPtr); let newEntry = changetype<SetEntry<K>>(newPtr); // unmanaged!
newEntry.key = oldEntry.key; newEntry.key = oldEntry.key;
let newBucketIndex = HASH<K>(oldEntry.key) & newBucketsMask; let newBucketIndex = HASH<K>(oldEntry.key) & newBucketsMask;
let newBucketPtrBase = changetype<usize>(newBuckets) + <usize>newBucketIndex * BUCKET_SIZE; let newBucketPtrBase = changetype<usize>(newBuckets) + <usize>newBucketIndex * BUCKET_SIZE;
@ -198,10 +169,10 @@ export class Set<K> {
// GC integration // GC integration
@unsafe private __traverse(): void { @unsafe private __traverse(cookie: u32): void {
__ref_mark(changetype<usize>(this.buckets)); __visit(changetype<usize>(this.buckets), cookie);
var entries = this.entries; var entries = this.entries;
__ref_mark(changetype<usize>(entries)); __visit(changetype<usize>(entries), cookie);
if (isManaged<K>()) { if (isManaged<K>()) {
let cur = changetype<usize>(entries); let cur = changetype<usize>(entries);
let end = cur + <usize>this.entriesOffset * ENTRY_SIZE<K>(); let end = cur + <usize>this.entriesOffset * ENTRY_SIZE<K>();
@ -211,12 +182,12 @@ export class Set<K> {
let val = changetype<usize>(entry.key); let val = changetype<usize>(entry.key);
if (isNullable<K>()) { if (isNullable<K>()) {
if (val) { if (val) {
__ref_mark(val); __visit(val, cookie);
__gc_mark_members(__runtime_id<K>(), val); __visit_members(val, cookie);
} }
} else { } else {
__ref_mark(val); __visit(val, cookie);
__gc_mark_members(__runtime_id<K>(), val); __visit_members(val, cookie);
} }
} }
cur += ENTRY_SIZE<K>(); cur += ENTRY_SIZE<K>();

View File

@ -1,27 +1,26 @@
/// <reference path="./collector/index.d.ts" /> /// <reference path="./rt/index.d.ts" />
import { MAX_SIZE_32 } from "./util/allocator"; import { BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "./rt/common";
import { HEADER, HEADER_SIZE, allocate, register, NEWARRAY } from "./util/runtime";
import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./util/string"; import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./util/string";
import { E_INVALIDLENGTH } from "./util/error"; import { E_INVALIDLENGTH } from "./util/error";
import { __runtime_id } from "./runtime";
import { ArrayBufferView } from "./arraybuffer"; import { ArrayBufferView } from "./arraybuffer";
import { idof } from "./builtins";
@sealed export abstract class String { @sealed export abstract class String {
@lazy static readonly MAX_LENGTH: i32 = (MAX_SIZE_32 - HEADER_SIZE) >> alignof<u16>(); @lazy static readonly MAX_LENGTH: i32 = BLOCK_MAXSIZE >>> alignof<u16>();
// TODO Add and handle second argument // TODO Add and handle second argument
static fromCharCode(code: i32): String { static fromCharCode(code: i32): string {
var out = allocate(2); var out = __alloc(2, idof<string>());
store<u16>(out, <u16>code); store<u16>(out, <u16>code);
return changetype<String>(register(out, __runtime_id<String>())); return changetype<string>(out); // retains
} }
static fromCodePoint(code: i32): String { static fromCodePoint(code: i32): string {
assert(<u32>code <= 0x10FFFF); assert(<u32>code <= 0x10FFFF);
var sur = code > 0xFFFF; var sur = code > 0xFFFF;
var out = allocate((i32(sur) + 1) << 1); var out = __alloc((i32(sur) + 1) << 1, idof<string>());
if (!sur) { if (!sur) {
store<u16>(out, <u16>code); store<u16>(out, <u16>code);
} else { } else {
@ -30,25 +29,19 @@ import { ArrayBufferView } from "./arraybuffer";
let lo: u32 = (code & 0x3FF) + 0xDC00; let lo: u32 = (code & 0x3FF) + 0xDC00;
store<u32>(out, (hi << 16) | lo); store<u32>(out, (hi << 16) | lo);
} }
return changetype<String>(register(out, __runtime_id<String>())); return changetype<string>(out); // retains
} }
// @ts-ignore: decorator
// @unsafe
// constructor(length: i32) {
// return changetype<String>(register(allocate(<usize>length << 1), __runtime_id<String>()));
// }
get length(): i32 { get length(): i32 {
return changetype<HEADER>(changetype<usize>(this) - HEADER_SIZE).payloadSize >> 1; return changetype<BLOCK>(changetype<usize>(this) - BLOCK_OVERHEAD).rtSize >> 1;
} }
@operator("[]") charAt(pos: i32): String { @operator("[]") charAt(pos: i32): String {
assert(this !== null); assert(this !== null);
if (<u32>pos >= <u32>this.length) return changetype<String>(""); if (<u32>pos >= <u32>this.length) return changetype<String>("");
var out = allocate(2); var out = __alloc(2, idof<String>());
store<u16>(out, load<u16>(changetype<usize>(this) + (<usize>pos << 1))); store<u16>(out, load<u16>(changetype<usize>(this) + (<usize>pos << 1)));
return changetype<String>(register(out, __runtime_id<String>())); return changetype<String>(out); // retains
} }
charCodeAt(pos: i32): i32 { charCodeAt(pos: i32): i32 {
@ -75,10 +68,10 @@ import { ArrayBufferView } from "./arraybuffer";
var otherSize: isize = other.length << 1; var otherSize: isize = other.length << 1;
var outSize: usize = thisSize + otherSize; var outSize: usize = thisSize + otherSize;
if (outSize == 0) return changetype<String>(""); if (outSize == 0) return changetype<String>("");
var out = allocate(outSize); var out = __alloc(outSize, idof<String>());
memory.copy(out, changetype<usize>(this), thisSize); memory.copy(out, changetype<usize>(this), thisSize);
memory.copy(out + thisSize, changetype<usize>(other), otherSize); memory.copy(out + thisSize, changetype<usize>(other), otherSize);
return changetype<String>(register(out, __runtime_id<String>())); return changetype<String>(out); // retains
} }
endsWith(searchString: String, endPosition: i32 = String.MAX_LENGTH): bool { 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); if (intStart < 0) intStart = max(size + intStart, 0);
var resultLength = min(max(end, 0), size - intStart); var resultLength = min(max(end, 0), size - intStart);
if (resultLength <= 0) return changetype<String>(""); if (resultLength <= 0) return changetype<String>("");
var out = allocate(resultLength << 1); var out = __alloc(resultLength << 1, idof<String>());
memory.copy(out, changetype<usize>(this) + intStart, resultLength); memory.copy(out, changetype<usize>(this) + intStart, resultLength);
return changetype<String>(register(out, __runtime_id<String>())); return changetype<String>(out); // retains
} }
substring(start: i32, end: i32 = i32.MAX_VALUE): String { substring(start: i32, end: i32 = i32.MAX_VALUE): String {
@ -209,9 +202,9 @@ import { ArrayBufferView } from "./arraybuffer";
len = toPos - fromPos; len = toPos - fromPos;
if (!len) return changetype<String>(""); if (!len) return changetype<String>("");
if (!fromPos && toPos == this.length << 1) return this; if (!fromPos && toPos == this.length << 1) return this;
var out = allocate(len); var out = __alloc(len, idof<String>());
memory.copy(out, changetype<usize>(this) + fromPos, len); memory.copy(out, changetype<usize>(this) + fromPos, len);
return changetype<String>(register(out, __runtime_id<String>())); return changetype<String>(out); // retains
} }
trim(): String { trim(): String {
@ -237,9 +230,9 @@ import { ArrayBufferView } from "./arraybuffer";
} }
if (!size) return changetype<String>(""); if (!size) return changetype<String>("");
if (!start && size == length << 1) return this; if (!start && size == length << 1) return this;
var out = allocate(size); var out = __alloc(size, idof<String>());
memory.copy(out, changetype<usize>(this) + offset, size); memory.copy(out, changetype<usize>(this) + offset, size);
return changetype<String>(register(out, __runtime_id<String>())); return changetype<String>(out); // retains
} }
@inline @inline
@ -267,9 +260,9 @@ import { ArrayBufferView } from "./arraybuffer";
if (!offset) return this; if (!offset) return this;
size -= offset; size -= offset;
if (!size) return changetype<String>(""); if (!size) return changetype<String>("");
var out = allocate(size); var out = __alloc(size, idof<String>());
memory.copy(out, changetype<usize>(this) + offset, size); memory.copy(out, changetype<usize>(this) + offset, size);
return changetype<String>(register(out, __runtime_id<String>())); return changetype<String>(out); // retains
} }
trimEnd(): String { trimEnd(): String {
@ -286,9 +279,9 @@ import { ArrayBufferView } from "./arraybuffer";
} }
if (!size) return changetype<String>(""); if (!size) return changetype<String>("");
if (size == originalSize) return this; if (size == originalSize) return this;
var out = allocate(size); var out = __alloc(size, idof<String>());
memory.copy(out, changetype<usize>(this), size); memory.copy(out, changetype<usize>(this), size);
return changetype<String>(register(out, __runtime_id<String>())); return changetype<String>(out); // retains
} }
padStart(targetLength: i32, padString: string = " "): String { padStart(targetLength: i32, padString: string = " "): String {
@ -298,7 +291,7 @@ import { ArrayBufferView } from "./arraybuffer";
var padSize = <usize>padString.length << 1; var padSize = <usize>padString.length << 1;
if (targetSize < thisSize || !padSize) return this; if (targetSize < thisSize || !padSize) return this;
var prependSize = targetSize - thisSize; var prependSize = targetSize - thisSize;
var out = allocate(targetSize); var out = __alloc(targetSize, idof<String>());
if (prependSize > padSize) { if (prependSize > padSize) {
let repeatCount = (prependSize - 2) / padSize; let repeatCount = (prependSize - 2) / padSize;
let restBase = repeatCount * padSize; let restBase = repeatCount * padSize;
@ -309,7 +302,7 @@ import { ArrayBufferView } from "./arraybuffer";
memory.copy(out, changetype<usize>(padString), prependSize); memory.copy(out, changetype<usize>(padString), prependSize);
} }
memory.copy(out + prependSize, changetype<usize>(this), thisSize); memory.copy(out + prependSize, changetype<usize>(this), thisSize);
return changetype<String>(register(out, __runtime_id<String>())); return changetype<String>(out); // retains
} }
padEnd(targetLength: i32, padString: string = " "): String { padEnd(targetLength: i32, padString: string = " "): String {
@ -319,7 +312,7 @@ import { ArrayBufferView } from "./arraybuffer";
var padSize = <usize>padString.length << 1; var padSize = <usize>padString.length << 1;
if (targetSize < thisSize || !padSize) return this; if (targetSize < thisSize || !padSize) return this;
var appendSize = targetSize - thisSize; var appendSize = targetSize - thisSize;
var out = allocate(targetSize); var out = __alloc(targetSize, idof<String>());
memory.copy(out, changetype<usize>(this), thisSize); memory.copy(out, changetype<usize>(this), thisSize);
if (appendSize > padSize) { if (appendSize > padSize) {
let repeatCount = (appendSize - 2) / padSize; let repeatCount = (appendSize - 2) / padSize;
@ -330,7 +323,7 @@ import { ArrayBufferView } from "./arraybuffer";
} else { } else {
memory.copy(out + thisSize, changetype<usize>(padString), appendSize); memory.copy(out + thisSize, changetype<usize>(padString), appendSize);
} }
return changetype<String>(register(out, __runtime_id<String>())); return changetype<String>(out); // retains
} }
repeat(count: i32 = 0): String { repeat(count: i32 = 0): String {
@ -344,9 +337,9 @@ import { ArrayBufferView } from "./arraybuffer";
if (count == 0 || !length) return changetype<String>(""); if (count == 0 || !length) return changetype<String>("");
if (count == 1) return this; if (count == 1) return this;
var out = allocate((length * count) << 1); var out = __alloc((length * count) << 1, idof<String>());
memory.repeat(out, changetype<usize>(this), <usize>length << 1, count); memory.repeat(out, changetype<usize>(this), <usize>length << 1, count);
return changetype<String>(register(out, __runtime_id<String>())); return changetype<String>(out); // retains
} }
slice(beginIndex: i32, endIndex: i32 = i32.MAX_VALUE): String { 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); var end = endIndex < 0 ? max(endIndex + len, 0) : min(endIndex, len);
len = end - begin; len = end - begin;
if (len <= 0) return changetype<String>(""); if (len <= 0) return changetype<String>("");
var out = allocate(len << 1); var out = __alloc(len << 1, idof<String>());
memory.copy(out, changetype<usize>(this) + (<usize>begin << 1), <usize>len << 1); memory.copy(out, changetype<usize>(this) + (<usize>begin << 1), <usize>len << 1);
return changetype<String>(register(out, __runtime_id<String>())); return changetype<String>(out); // retains
} }
split(separator: String | null = null, limit: i32 = i32.MAX_VALUE): String[] { split(separator: String | null = null, limit: i32 = i32.MAX_VALUE): String[] {
assert(this !== null); assert(this !== null);
if (!limit) return NEWARRAY<String>(0); if (!limit) return changetype<Array<String>>(__allocArray(0, alignof<String>(), idof<Array<String>>())); // retains
if (separator === null) return <String[]>[this]; if (separator === null) return <String[]>[this];
var length: isize = this.length; var length: isize = this.length;
var sepLen: isize = separator.length; var sepLen: isize = separator.length;
if (limit < 0) limit = i32.MAX_VALUE; if (limit < 0) limit = i32.MAX_VALUE;
if (!sepLen) { if (!sepLen) {
if (!length) return NEWARRAY<String>(0); if (!length) return changetype<Array<String>>(__allocArray(0, alignof<String>(), idof<Array<String>>())); // retains
// split by chars // split by chars
length = min<isize>(length, <isize>limit); length = min<isize>(length, <isize>limit);
let result = NEWARRAY<String>(length); let result = __allocArray(length, alignof<String>(), idof<Array<String>>());
let resultStart = changetype<ArrayBufferView>(result).dataStart; let resultStart = changetype<ArrayBufferView>(result).dataStart;
for (let i: isize = 0; i < length; ++i) { for (let i: isize = 0; i < length; ++i) {
let charStr = allocate(2); let charStr = __alloc(2, idof<String>());
store<u16>(charStr, load<u16>(changetype<usize>(this) + (<usize>i << 1))); store<u16>(charStr, load<u16>(changetype<usize>(this) + (<usize>i << 1)));
store<usize>(resultStart + (<usize>i << alignof<usize>()), charStr); // result[i] = charStr store<usize>(resultStart + (<usize>i << alignof<usize>()), charStr); // result[i] = charStr
register(charStr, __runtime_id<String>()); if (isManaged<String>()) __retain(charStr);
if (isManaged<String>()) {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(charStr), changetype<usize>(result));
if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(charStr));
} }
} return changetype<Array<String>>(result); // retains
return result;
} else if (!length) { } else if (!length) {
let result = NEWARRAY<String>(1); let result = __allocArray(1, alignof<String>(), idof<Array<String>>());
store<string>(changetype<ArrayBufferView>(result).dataStart, ""); // no need to register/link store<usize>(changetype<ArrayBufferView>(result).dataStart, changetype<usize>("")); // static ""
return result; return changetype<Array<String>>(result); // retains
} }
var result = NEWARRAY<String>(0); var result = changetype<Array<String>>(__allocArray(0, alignof<String>(), idof<Array<String>>())); // retains
var end = 0, start = 0, i = 0; var end = 0, start = 0, i = 0;
while ((end = this.indexOf(separator, start)) != -1) { while ((end = this.indexOf(separator, start)) != -1) {
let len = end - start; let len = end - start;
if (len > 0) { if (len > 0) {
let out = allocate(<usize>len << 1); let out = __alloc(<usize>len << 1, idof<String>());
memory.copy(out, changetype<usize>(this) + (<usize>start << 1), <usize>len << 1); memory.copy(out, changetype<usize>(this) + (<usize>start << 1), <usize>len << 1);
result.push(changetype<String>(register(out, __runtime_id<String>()))); result.push(changetype<String>(out));
} else { } else {
result.push(changetype<String>("")); result.push(changetype<String>(""));
} }
if (++i == limit) return result; if (++i == limit) return changetype<Array<String>>(result); // retains
start = end + sepLen; start = end + sepLen;
} }
if (!start) { if (!start) { // also means: loop above didn't do anything
let result = NEWARRAY<String>(1); result.push(this);
unchecked(result[0] = this); return changetype<Array<String>>(result); // retains
return result;
} }
var len = length - start; var len = length - start;
if (len > 0) { if (len > 0) {
let out = allocate(<usize>len << 1); let out = __alloc(<usize>len << 1, idof<String>());
memory.copy(out, changetype<usize>(this) + (<usize>start << 1), <usize>len << 1); memory.copy(out, changetype<usize>(this) + (<usize>start << 1), <usize>len << 1);
result.push(changetype<String>(register(out, __runtime_id<String>()))); result.push(changetype<String>(out)); // retains
} else { } else {
result.push(changetype<String>("")); result.push(changetype<String>("")); // static ""
} }
return result; return changetype<Array<String>>(result); // retains
// releases result
} }
toString(): String { toString(): String {
@ -484,10 +473,10 @@ import { ArrayBufferView } from "./arraybuffer";
} }
} }
assert(ptrPos == len); assert(ptrPos == len);
var out = allocate(bufPos); var out = __alloc(bufPos, idof<String>());
memory.copy(changetype<usize>(out), buf, bufPos); memory.copy(out, buf, bufPos);
memory.free(buf); memory.free(buf);
return changetype<String>(register(out, __runtime_id<String>())); return changetype<String>(out); // retains
} }
toUTF8(): usize { toUTF8(): usize {
@ -553,7 +542,7 @@ export function parseFloat(str: String): f64 {
var len: i32 = str.length; var len: i32 = str.length;
if (!len) return NaN; if (!len) return NaN;
var ptr = changetype<usize>(str) /* + HEAD -> offset */; var ptr = changetype<usize>(str);
var code = <i32>load<u16>(ptr); var code = <i32>load<u16>(ptr);
// determine sign // determine sign

View File

@ -1,7 +1,6 @@
import { allocate, register } from "./util/runtime";
import { COMPARATOR, SORT as SORT_IMPL } from "./util/sort"; import { COMPARATOR, SORT as SORT_IMPL } from "./util/sort";
import { E_INDEXOUTOFRANGE } from "./util/error"; import { E_INDEXOUTOFRANGE } from "./util/error";
import { __runtime_id } from "./runtime"; import { idof } from "./builtins";
import { ArrayBufferView } from "./arraybuffer"; import { ArrayBufferView } from "./arraybuffer";
export class Int8Array extends ArrayBufferView { export class Int8Array extends ArrayBufferView {
@ -962,13 +961,11 @@ function SUBARRAY<TArray extends ArrayBufferView, T>(
else begin = min(begin, length); else begin = min(begin, length);
if (end < 0) end = max(length + end, begin); if (end < 0) end = max(length + end, begin);
else end = max(min(end, length), begin); else end = max(min(end, length), begin);
var out = allocate(offsetof<TArray>()); var out = __alloc(offsetof<TArray>(), idof<TArray>());
var data = array.data; changetype<ArrayBufferView>(out).data = array.data; // retains
var dataStart = array.dataStart; changetype<ArrayBufferView>(out).dataStart = array.dataStart + (<usize>begin << alignof<T>());
changetype<ArrayBufferView>(out).data = data; // links
changetype<ArrayBufferView>(out).dataStart = dataStart + (<usize>begin << alignof<T>());
changetype<ArrayBufferView>(out).dataLength = (end - begin) << alignof<T>(); changetype<ArrayBufferView>(out).dataLength = (end - begin) << alignof<T>();
return changetype<TArray>(register(out, __runtime_id<TArray>())); return changetype<TArray>(out); // retains
} }
// @ts-ignore: decorator // @ts-ignore: decorator

View File

@ -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 << <usize>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

View File

@ -1,6 +1,7 @@
import { allocate, register, discard } from "./runtime"; /// <reference path="../rt/index.d.ts" />
import { idof } from "../builtins";
import { CharCode } from "./string"; import { CharCode } from "./string";
import { __runtime_id } from "../runtime";
import { ArrayBufferView } from "../arraybuffer"; import { ArrayBufferView } from "../arraybuffer";
// @ts-ignore: decorator // @ts-ignore: decorator
@ -264,10 +265,10 @@ export function utoa32(value: u32): String {
if (!value) return "0"; if (!value) return "0";
var decimals = decimalCount32(value); var decimals = decimalCount32(value);
var out = allocate(decimals << 1); var out = __alloc(decimals << 1, idof<String>());
utoa32_core(changetype<usize>(out), value, decimals); utoa32_core(changetype<usize>(out), value, decimals);
return changetype<String>(register(out, __runtime_id<String>())); return changetype<String>(out); // retains
} }
export function itoa32(value: i32): String { export function itoa32(value: i32): String {
@ -277,12 +278,12 @@ export function itoa32(value: i32): String {
if (sign) value = -value; if (sign) value = -value;
var decimals = decimalCount32(value) + u32(sign); var decimals = decimalCount32(value) + u32(sign);
var out = allocate(decimals << 1); var out = __alloc(decimals << 1, idof<String>());
utoa32_core(changetype<usize>(out), value, decimals); utoa32_core(changetype<usize>(out), value, decimals);
if (sign) store<u16>(changetype<usize>(out), CharCode.MINUS); if (sign) store<u16>(changetype<usize>(out), CharCode.MINUS);
return changetype<String>(register(out, __runtime_id<String>())); return changetype<String>(out); // retains
} }
export function utoa64(value: u64): String { export function utoa64(value: u64): String {
@ -292,14 +293,14 @@ export function utoa64(value: u64): String {
if (value <= u32.MAX_VALUE) { if (value <= u32.MAX_VALUE) {
let val32 = <u32>value; let val32 = <u32>value;
let decimals = decimalCount32(val32); let decimals = decimalCount32(val32);
out = allocate(decimals << 1); out = __alloc(decimals << 1, idof<String>());
utoa32_core(out, val32, decimals); utoa32_core(out, val32, decimals);
} else { } else {
let decimals = decimalCount64(value); let decimals = decimalCount64(value);
out = allocate(decimals << 1); out = __alloc(decimals << 1, idof<String>());
utoa64_core(changetype<usize>(out), value, decimals); utoa64_core(changetype<usize>(out), value, decimals);
} }
return changetype<String>(register(out, __runtime_id<String>())); return changetype<String>(out); // retains
} }
export function itoa64(value: i64): String { export function itoa64(value: i64): String {
@ -312,16 +313,16 @@ export function itoa64(value: i64): String {
if (<u64>value <= <u64>u32.MAX_VALUE) { if (<u64>value <= <u64>u32.MAX_VALUE) {
let val32 = <u32>value; let val32 = <u32>value;
let decimals = decimalCount32(val32) + u32(sign); let decimals = decimalCount32(val32) + u32(sign);
out = allocate(decimals << 1); out = __alloc(decimals << 1, idof<String>());
utoa32_core(changetype<usize>(out), val32, decimals); utoa32_core(changetype<usize>(out), val32, decimals);
} else { } else {
let decimals = decimalCount64(value) + u32(sign); let decimals = decimalCount64(value) + u32(sign);
out = allocate(decimals << 1); out = __alloc(decimals << 1, idof<String>());
utoa64_core(changetype<usize>(out), value, decimals); utoa64_core(changetype<usize>(out), value, decimals);
} }
if (sign) store<u16>(changetype<usize>(out), CharCode.MINUS); if (sign) store<u16>(changetype<usize>(out), CharCode.MINUS);
return changetype<String>(register(out, __runtime_id<String>())); return changetype<String>(out); // retains
} }
export function itoa<T extends number>(value: T): String { export function itoa<T extends number>(value: T): String {
@ -626,11 +627,12 @@ export function dtoa(value: f64): String {
if (isNaN<f64>(value)) return "NaN"; if (isNaN<f64>(value)) return "NaN";
return select<String>("-Infinity", "Infinity", value < 0); return select<String>("-Infinity", "Infinity", value < 0);
} }
var temp = allocate(MAX_DOUBLE_LENGTH << 1); var temp = __alloc(MAX_DOUBLE_LENGTH << 1, idof<String>());
var length = dtoa_core(temp, value); var length = dtoa_core(temp, value);
var result = changetype<String>(temp).substring(0, length); // registers if (length < MAX_DOUBLE_LENGTH) {
discard(temp); return changetype<String>(temp).substring(0, length); // retains/releases `temp`, retains return
return result; }
return changetype<String>(temp); // retains
} }
export function itoa_stream<T extends number>(buffer: usize, offset: usize, value: T): u32 { export function itoa_stream<T extends number>(buffer: usize, offset: usize, value: T): u32 {
@ -692,8 +694,7 @@ export function dtoa_stream(buffer: usize, offset: usize, value: f64): u32 {
} else { } else {
let sign = i32(value < 0); let sign = i32(value < 0);
let len = 8 + sign; let len = 8 + sign;
let source = changetype<usize>(select<String>("-Infinity", "Infinity", sign)); memory.copy(buffer, changetype<usize>(select<String>("-Infinity", "Infinity", sign)), len << 1);
memory.copy(buffer, source, len << 1);
return len; return len;
} }
} }

View File

@ -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<HEADER>() + 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 <usize>1 << <usize>(<u32>32 - clz<u32>(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<HEADER>(memory.allocate(adjust(payloadSize)));
header.classId = HEADER_MAGIC;
header.payloadSize = payloadSize;
if (isDefined(__ref_collect)) {
header.reserved1 = 0;
header.reserved2 = 0;
}
return changetype<usize>(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<HEADER>(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<HEADER>(memory.allocate(newAdjustedSize));
newHeader.classId = header.classId;
if (isDefined(__ref_collect)) {
newHeader.reserved1 = 0;
newHeader.reserved2 = 0;
}
let newRef = changetype<usize>(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<usize>(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<HEADER>(ref - HEADER_SIZE);
assert(header.classId == HEADER_MAGIC);
memory.free(changetype<usize>(header));
} else {
memory.free(changetype<usize>(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<HEADER>(ref - HEADER_SIZE);
assert(header.classId == HEADER_MAGIC);
header.classId = id;
} else {
changetype<HEADER>(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<i32[]>()), id);
var bufferSize = <usize>length << alignLog2;
var buffer = register(allocate(bufferSize), __runtime_id<ArrayBuffer>());
changetype<ArrayBufferView>(array).data = changetype<ArrayBuffer>(buffer); // links
changetype<ArrayBufferView>(array).dataStart = buffer;
changetype<ArrayBufferView>(array).dataLength = bufferSize;
store<i32>(changetype<usize>(array), length, offsetof<i32[]>("length_"));
if (data) memory.copy(buffer, data, bufferSize);
return array;
}
// @ts-ignore: decorator
@inline
export function NEWARRAY<T>(length: i32): Array<T> {
return changetype<Array<T>>(makeArray(length, alignof<T>(), __runtime_id<Array<T>>(), 0));
}

View File

@ -28,8 +28,8 @@ fetch("untouched.wasm").then(result =>
).then(result => { ).then(result => {
exports = result.instance.exports; exports = result.instance.exports;
U32 = new Uint32Array(exports.memory.buffer); U32 = new Uint32Array(exports.memory.buffer);
var first = exports.__rt_allocate(255); var first = exports.__alloc(255);
exports.__rt_free(first); exports.__free(first);
ROOT = first - 17; ROOT = first - 17;
while (!U32[ROOT >> 2]) --ROOT; // find tail while (!U32[ROOT >> 2]) --ROOT; // find tail
ROOT -= (1 + FL_BITS + HL_SIZE) << 2; ROOT -= (1 + FL_BITS + HL_SIZE) << 2;
@ -113,7 +113,7 @@ function update() {
} }
function allocate(size) { function allocate(size) {
var ptr = exports.__rt_allocate(size); var ptr = exports.__alloc(size);
if (!ptr) { if (!ptr) {
alert("should not happen"); alert("should not happen");
return; return;
@ -127,7 +127,7 @@ function allocate(size) {
var er = document.createElement("button"); var er = document.createElement("button");
er.innerText = "realloc"; er.innerText = "realloc";
er.onclick = function() { er.onclick = function() {
ptr = exports.__rt_reallocate(ptr, es.value >>> 0); ptr = exports.__realloc(ptr, es.value >>> 0);
update(); update();
}; };
el.appendChild(er); el.appendChild(er);
@ -136,7 +136,7 @@ function allocate(size) {
ef.className = "free"; ef.className = "free";
el.appendChild(ef); el.appendChild(ef);
ef.onclick = function() { ef.onclick = function() {
exports.__rt_free(ptr); exports.__free(ptr);
document.getElementById("segs").removeChild(el); document.getElementById("segs").removeChild(el);
update(); update();
}; };

View File

@ -20,13 +20,13 @@
(global $~lib/rt/pure/ROOTS (mut i32) (i32.const 0)) (global $~lib/rt/pure/ROOTS (mut i32) (i32.const 0))
(export "memory" (memory $0)) (export "memory" (memory $0))
(export "main" (func $assembly/index/main)) (export "main" (func $assembly/index/main))
(export "__rt_allocate" (func $~lib/rt/index/__rt_allocate)) (export "__alloc" (func $~lib/rt/index/__alloc))
(export "__rt_reallocate" (func $~lib/rt/index/__rt_reallocate)) (export "__realloc" (func $~lib/rt/index/__realloc))
(export "__rt_free" (func $~lib/rt/index/__rt_free)) (export "__free" (func $~lib/rt/index/__free))
(export "__rt_retain" (func $~lib/rt/index/__rt_retain)) (export "__retain" (func $~lib/rt/index/__retain))
(export "__rt_release" (func $~lib/rt/index/__rt_release)) (export "__release" (func $~lib/rt/index/__release))
(export "__rt_collect" (func $~lib/rt/index/__rt_collect)) (export "__collect" (func $~lib/rt/index/__collect))
(export "__rt_typeinfo" (func $~lib/rt/common/__rt_typeinfo)) (export "__info" (func $~lib/rt/common/__info))
(func $assembly/index/main (; 1 ;) (type $FUNCSIG$v) (func $assembly/index/main (; 1 ;) (type $FUNCSIG$v)
global.get $~lib/started global.get $~lib/started
i32.eqz i32.eqz
@ -778,7 +778,7 @@
call $~lib/rt/tlsf/prepareBlock call $~lib/rt/tlsf/prepareBlock
local.get $2 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) (local $2 i32)
global.get $~lib/rt/tlsf/ROOT global.get $~lib/rt/tlsf/ROOT
local.tee $2 local.tee $2
@ -1073,7 +1073,7 @@
call $~lib/rt/tlsf/insertBlock call $~lib/rt/tlsf/insertBlock
local.get $3 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 global.get $~lib/rt/tlsf/ROOT
local.get $0 local.get $0
i32.const 16 i32.const 16
@ -1094,14 +1094,14 @@
local.get $1 local.get $1
call $~lib/rt/tlsf/insertBlock 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 global.get $~lib/rt/tlsf/ROOT
local.get $0 local.get $0
i32.const 16 i32.const 16
i32.sub i32.sub
call $~lib/rt/tlsf/freeBlock 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 local.get $0
if if
local.get $0 local.get $0
@ -1115,7 +1115,7 @@
i32.store offset=4 i32.store offset=4
end 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 local.get $0
if if
local.get $0 local.get $0
@ -1333,10 +1333,10 @@
local.get $5 local.get $5
global.set $~lib/rt/pure/CUR 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 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) (local $1 i32)
i32.const 104 i32.const 104
local.set $1 local.set $1

Binary file not shown.

View File

@ -26,13 +26,13 @@
(global $~lib/memory/HEAP_BASE i32 (i32.const 336)) (global $~lib/memory/HEAP_BASE i32 (i32.const 336))
(export "memory" (memory $0)) (export "memory" (memory $0))
(export "main" (func $assembly/index/main)) (export "main" (func $assembly/index/main))
(export "__rt_allocate" (func $~lib/rt/index/__rt_allocate)) (export "__alloc" (func $~lib/rt/index/__alloc))
(export "__rt_reallocate" (func $~lib/rt/index/__rt_reallocate)) (export "__realloc" (func $~lib/rt/index/__realloc))
(export "__rt_free" (func $~lib/rt/index/__rt_free)) (export "__free" (func $~lib/rt/index/__free))
(export "__rt_retain" (func $~lib/rt/index/__rt_retain)) (export "__retain" (func $~lib/rt/index/__retain))
(export "__rt_release" (func $~lib/rt/index/__rt_release)) (export "__release" (func $~lib/rt/index/__release))
(export "__rt_collect" (func $~lib/rt/index/__rt_collect)) (export "__collect" (func $~lib/rt/index/__collect))
(export "__rt_typeinfo" (func $~lib/rt/common/__rt_typeinfo)) (export "__info" (func $~lib/rt/common/__info))
(func $assembly/index/main (; 1 ;) (type $FUNCSIG$v) (func $assembly/index/main (; 1 ;) (type $FUNCSIG$v)
global.get $~lib/started global.get $~lib/started
i32.eqz i32.eqz
@ -1369,7 +1369,7 @@
call $~lib/rt/tlsf/prepareBlock call $~lib/rt/tlsf/prepareBlock
local.get $3 local.get $3
) )
(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) (local $2 i32)
(local $3 i32) (local $3 i32)
global.get $~lib/rt/tlsf/ROOT global.get $~lib/rt/tlsf/ROOT
@ -1736,13 +1736,13 @@
call $~lib/rt/tlsf/insertBlock call $~lib/rt/tlsf/insertBlock
local.get $8 local.get $8
) )
(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 global.get $~lib/rt/tlsf/ROOT
i32.eqz i32.eqz
if if
i32.const 0 i32.const 0
i32.const 72 i32.const 72
i32.const 23 i32.const 21
i32.const 13 i32.const 13
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -1762,7 +1762,7 @@
if if
i32.const 0 i32.const 0
i32.const 72 i32.const 72
i32.const 24 i32.const 22
i32.const 2 i32.const 2
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -1803,13 +1803,13 @@
local.get $1 local.get $1
call $~lib/rt/tlsf/insertBlock 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 global.get $~lib/rt/tlsf/ROOT
i32.eqz i32.eqz
if if
i32.const 0 i32.const 0
i32.const 72 i32.const 72
i32.const 31 i32.const 29
i32.const 13 i32.const 13
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -1829,7 +1829,7 @@
if if
i32.const 0 i32.const 0
i32.const 72 i32.const 72
i32.const 32 i32.const 30
i32.const 2 i32.const 2
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -1873,7 +1873,7 @@
i32.add i32.add
i32.store offset=4 i32.store offset=4
) )
(func $~lib/rt/index/__rt_retain (; 18 ;) (type $FUNCSIG$vi) (param $0 i32) (func $~lib/rt/index/__retain (; 18 ;) (type $FUNCSIG$vi) (param $0 i32)
local.get $0 local.get $0
if if
local.get $0 local.get $0
@ -2046,7 +2046,7 @@
end end
end end
) )
(func $~lib/rt/index/__rt_release (; 25 ;) (type $FUNCSIG$vi) (param $0 i32) (func $~lib/rt/index/__release (; 25 ;) (type $FUNCSIG$vi) (param $0 i32)
local.get $0 local.get $0
if if
local.get $0 local.get $0
@ -2309,10 +2309,10 @@
local.get $0 local.get $0
global.set $~lib/rt/pure/CUR global.set $~lib/rt/pure/CUR
) )
(func $~lib/rt/index/__rt_collect (; 31 ;) (type $FUNCSIG$v) (func $~lib/rt/index/__collect (; 31 ;) (type $FUNCSIG$v)
call $~lib/rt/pure/collectCycles call $~lib/rt/pure/collectCycles
) )
(func $~lib/rt/common/__rt_typeinfo (; 32 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) (func $~lib/rt/common/__info (; 32 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
(local $1 i32) (local $1 i32)
global.get $~lib/runtime/RTTI_BASE global.get $~lib/runtime/RTTI_BASE
local.set $1 local.set $1