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
{
let templateName = String(args.runtime);
let templateText = exports.libraryFiles["runtime/" + templateName];
let templateText = exports.libraryFiles["rt/index-" + templateName];
if (templateText == null) {
templateText = readFile(templateName + ".ts", baseDir);
if (templateText == null) {
return callback(Error("Runtime template '" + templateName + " not found."));
return callback(Error("Runtime template '" + templateName + "' not found."));
}
}
stats.parseCount++;

View File

@ -83,15 +83,14 @@
},
"runtime": {
"description": [
"Specifies the runtime template to include in the program.",
"Specifies the runtime implementation to include in the program.",
"",
" none No allocator/GC or compose your own. [default]",
" trace TLSF memory allocator and ITCM garbage collector.",
" arena Just the arena memory allocator. No free/GC.",
" full Default runtime based on TLSF and reference counting.",
" stub Minimal stub implementation without free/GC support.",
""
],
"type": "s",
"default": "none"
"default": "full"
},
"debug": {
"description": "Enables debug information in emitted binaries.",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -349,58 +349,49 @@ export class Program extends DiagnosticEmitter {
/** Managed classes contained in the program, by id. */
managedClasses: Map<i32,Class> = new Map();
// runtime references
// standard references
/** ArrayBufferView reference. */
arrayBufferViewInstance: Class | null = null;
arrayBufferViewInstance: Class;
/** ArrayBuffer instance reference. */
arrayBufferInstance: Class | null = null;
arrayBufferInstance: Class;
/** Array prototype reference. */
arrayPrototype: ClassPrototype | null = null;
arrayPrototype: ClassPrototype;
/** Set prototype reference. */
setPrototype: ClassPrototype | null = null;
setPrototype: ClassPrototype;
/** Map prototype reference. */
mapPrototype: ClassPrototype | null = null;
mapPrototype: ClassPrototype;
/** Fixed array prototype reference. */
fixedArrayPrototype: ClassPrototype | null = null;
fixedArrayPrototype: ClassPrototype;
/** String instance reference. */
stringInstance: Class | null = null;
stringInstance: Class;
/** Abort function reference, if present. */
abortInstance: Function | null = null;
abortInstance: Function;
/** Runtime allocation function. `allocate(payloadSize: usize): usize` */
allocateInstance: Function | null = null;
/** Memory allocation function. `memory.allocate(size)` */
memoryAllocateInstance: Function | null = null;
/** Runtime reallocation function. `reallocate(ref: usize, newPayloadSize: usize): usize` */
reallocateInstance: Function | null = null;
/** Runtime discard function. `discard(ref: usize): void` */
discardInstance: Function | null = null;
/** Runtime register function. `register(ref: usize, cid: u32): usize` */
registerInstance: Function | null = null;
/** Runtime make array function. `newArray(length: i32, alignLog2: usize, id: u32, source: usize = 0): usize` */
makeArrayInstance: Function | null = null;
/** Runtime instanceof function. */
instanceofInstance: Function | null = null;
/** Runtime flags function. */
flagsInstance: Function | null = null;
// runtime references
/** The kind of garbage collector being present. */
collectorKind: CollectorKind = CollectorKind.NONE;
/** Memory allocation implementation, if present: `__mem_allocate(size: usize): usize` */
allocateMem: Function | null = null;
/** Memory free implementation, if present: `__mem_free(ref: usize): void` */
freeMem: Function | null = null;
/** Reference link implementation, if present: `__ref_link(ref: usize, parentRef: usize): void` */
linkRef: Function | null = null;
/** Reference unlink implementation, if present: `__ref_unlink(ref: usize, parentRef: usize): void` */
unlinkRef: Function | null = null;
/** Reference retain implementation, if present: `__ref_retain(ref: usize): void` */
retainRef: Function | null = null;
/** Reference release implementation, if present: `__ref_release(ref: usize): void` */
releaseRef: Function | null = null;
/** Reference mark implementation, if present: `__ref_mark(ref: usize): void` */
markRef: Function | null = null;
/** RT `__alloc(size: usize, id: u32): usize` */
allocInstance: Function;
/** RT `__realloc(ref: usize, newSize: usize): usize` */
reallocInstance: Function;
/** RT `__free(ref: usize): void` */
freeInstance: Function;
/** RT `__retain(ref: usize): usize` */
retainInstance: Function;
/** RT `__release(ref: usize): void` */
releaseInstance: Function;
/** RT `__retainRelease(newRef: usize, oldRef: usize): usize` */
retainReleaseInstance: Function;
/** RT `__collect(): void` */
collectInstance: Function;
/** RT `__visit(ref: usize, cookie: u32): void` */
visitInstance: Function;
/** RT `__typeinfo(id: u32): RTTIFlags` */
typeinfoInstance: Function;
/** RT `__instanceof(ref: usize, superId: u32): bool` */
instanceofInstance: Function;
/** RT `__allocArray(length: i32, alignLog2: usize, id: u32, data: usize = 0): usize` */
allocArrayInstance: Function;
/** Next class id. */
nextClassId: u32 = 1;
@ -805,104 +796,26 @@ export class Program extends DiagnosticEmitter {
}
}
// register library elements
{
let element: Element | null;
if (element = this.lookupGlobal(CommonSymbols.ArrayBufferView)) {
assert(element.kind == ElementKind.CLASS_PROTOTYPE);
this.arrayBufferViewInstance = resolver.resolveClass(<ClassPrototype>element, null);
}
if (element = this.lookupGlobal(CommonSymbols.ArrayBuffer)) {
assert(element.kind == ElementKind.CLASS_PROTOTYPE);
this.arrayBufferInstance = resolver.resolveClass(<ClassPrototype>element, null);
}
if (element = this.lookupGlobal(CommonSymbols.String)) {
assert(element.kind == ElementKind.CLASS_PROTOTYPE);
this.stringInstance = resolver.resolveClass(<ClassPrototype>element, null);
}
if (element = this.lookupGlobal(CommonSymbols.Array)) {
assert(element.kind == ElementKind.CLASS_PROTOTYPE);
this.arrayPrototype = <ClassPrototype>element;
}
if (element = this.lookupGlobal(CommonSymbols.FixedArray)) {
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;
}
}
}
// register stdlib components
this.arrayBufferViewInstance = this.requireClass(CommonSymbols.ArrayBufferView);
this.arrayBufferInstance = this.requireClass(CommonSymbols.ArrayBuffer);
this.stringInstance = this.requireClass(CommonSymbols.String);
this.arrayPrototype = <ClassPrototype>this.require(CommonSymbols.Array, ElementKind.CLASS_PROTOTYPE);
this.fixedArrayPrototype = <ClassPrototype>this.require(CommonSymbols.FixedArray, ElementKind.CLASS_PROTOTYPE);
this.setPrototype = <ClassPrototype>this.require(CommonSymbols.Set, ElementKind.CLASS_PROTOTYPE);
this.mapPrototype = <ClassPrototype>this.require(CommonSymbols.Map, ElementKind.CLASS_PROTOTYPE);
this.abortInstance = this.requireFunction(CommonSymbols.abort);
this.allocInstance = this.requireFunction(CommonSymbols.alloc);
this.reallocInstance = this.requireFunction(CommonSymbols.realloc);
this.freeInstance = this.requireFunction(CommonSymbols.free);
this.retainInstance = this.requireFunction(CommonSymbols.retain);
this.releaseInstance = this.requireFunction(CommonSymbols.release);
this.retainReleaseInstance = this.requireFunction(CommonSymbols.retainRelease);
this.collectInstance = this.requireFunction(CommonSymbols.collect);
this.typeinfoInstance = this.requireFunction(CommonSymbols.typeinfo);
this.instanceofInstance = this.requireFunction(CommonSymbols.instanceof_);
this.visitInstance = this.requireFunction(CommonSymbols.visit);
this.allocArrayInstance = this.requireFunction(CommonSymbols.allocArray);
// mark module exports, i.e. to apply proper wrapping behavior on the boundaries
for (let file of this.filesByName.values()) {
@ -912,6 +825,30 @@ export class Program extends DiagnosticEmitter {
}
}
/** Requires that a global library element of the specified kind is present and returns it. */
private require(name: string, kind: ElementKind): Element {
var element = this.lookupGlobal(name);
if (!element) throw new Error("missing " + name);
if (element.kind != kind) throw new Error("unexpected " + name);
return element;
}
/** Requires that a non-generic global class is present and returns it. */
private requireClass(name: string): Class {
var prototype = this.require(name, ElementKind.CLASS_PROTOTYPE);
var resolved = this.resolver.resolveClass(<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. */
private markModuleExport(element: Element): void {
element.set(CommonFlags.MODULE_EXPORT);

View File

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

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

View File

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

View File

@ -1740,3 +1740,23 @@ declare function trace(
a3?: f64,
a4?: f64
): void;
// @ts-ignore: decorator
@builtin
export declare const HEAP_BASE: usize;
// @ts-ignore: decorator
@builtin
export declare const RTTI_BASE: usize;
// @ts-ignore: decorator
@builtin
export declare function idof<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 { E_INDEXOUTOFRANGE, E_INVALIDLENGTH } from "./util/error";
@ -16,10 +16,10 @@ export class DataView {
byteLength: i32 = buffer.byteLength
) {
if (
i32(<u32>byteLength > <u32>MAX_BYTELENGTH) |
i32(<u32>byteLength > <u32>BLOCK_MAXSIZE) |
i32(<u32>byteOffset + byteLength > <u32>buffer.byteLength)
) throw new RangeError(E_INVALIDLENGTH);
this.data = buffer; // links
this.data = buffer; // retains
var dataStart = changetype<usize>(buffer) + <usize>byteOffset;
this.dataStart = dataStart;
this.dataLength = byteLength;

View File

@ -1,5 +1,7 @@
import { HEADER, HEADER_SIZE, MAX_BYTELENGTH, allocate, register } from "./util/runtime";
import { __runtime_id, __gc_mark_members } from "./runtime";
/// <reference path="./rt/index.d.ts" />
import { BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "./rt/common";
import { idof } from "./builtins";
import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH, E_HOLEYARRAY } from "./util/error";
// NOTE: DO NOT USE YET!
@ -11,20 +13,20 @@ export class FixedArray<T> {
[key: number]: T;
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 (!isNullable<T>()) {
if (length) throw new Error(E_HOLEYARRAY);
}
}
var outSize = <usize>length << alignof<T>();
var out = allocate(outSize);
var out = __alloc(outSize, idof<FixedArray<T>>());
memory.fill(out, 0, outSize);
return changetype<FixedArray<T>>(register(out, __runtime_id<FixedArray<T>>()));
return changetype<FixedArray<T>>(out); // retains
}
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 {
@ -44,26 +46,10 @@ export class FixedArray<T> {
@operator("{}=") private __unchecked_set(index: i32, value: T): void {
if (isManaged<T>()) {
let offset = changetype<usize>(this) + (<usize>index << alignof<T>());
let oldValue = load<T>(offset);
if (value !== oldValue) {
store<T>(offset, value);
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 (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);
}
let oldValue = load<usize>(offset);
if (changetype<usize>(value) != oldValue) {
store<usize>(offset, __retain(changetype<usize>(value)));
__release(changetype<usize>(oldValue));
}
} else {
store<T>(changetype<usize>(this) + (<usize>index << alignof<T>()), value);
@ -72,20 +58,20 @@ export class FixedArray<T> {
// GC integration
@unsafe private __traverse(): void {
@unsafe private __traverse(cookie: u32): void {
if (isManaged<T>()) {
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) {
let val = load<usize>(cur);
if (isNullable<T>()) {
if (val) {
__ref_mark(val);
__gc_mark_members(__runtime_id<T>(), val);
__visit(val, cookie);
__visit_members(val, cookie);
}
} else {
__ref_mark(val);
__gc_mark_members(__runtime_id<T>(), val);
__visit(val, cookie);
__visit_members(val, cookie);
}
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 { __runtime_id, __gc_mark_members } from "./runtime";
// A deterministic hash map based on CloseTable from https://github.com/jorendorff/dht
@ -83,7 +82,7 @@ export class Map<K,V> {
}
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
);
while (entry) {
@ -106,35 +105,9 @@ export class Map<K,V> {
var hashCode = HASH<K>(key);
var entry = this.find(key, hashCode); // unmanaged!
if (entry) {
if (isManaged<V>()) {
let oldValue = entry.value;
if (value !== oldValue) {
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;
}
entry.value = isManaged<V>()
? changetype<V>(__retainRelease(changetype<usize>(value), changetype<usize>(entry.value)))
: value;
} else {
// check if rehashing is necessary
if (this.entriesOffset == this.entriesCapacity) {
@ -147,35 +120,13 @@ export class Map<K,V> {
// append new entry
let entries = this.entries;
entry = changetype<MapEntry<K,V>>(changetype<usize>(entries) + this.entriesOffset++ * ENTRY_SIZE<K,V>());
entry.key = key;
entry.value = value;
// link with the map
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);
}
}
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);
}
}
entry.key = isManaged<K>()
? changetype<K>(__retain(changetype<usize>(key)))
: key;
entry.value = isManaged<V>()
? changetype<V>(__retain(changetype<usize>(value)))
: value;
++this.entriesCount;
// link with previous entry in bucket
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 {
var entry = this.find(key, HASH<K>(key));
if (!entry) return false;
if (isManaged<K>()) {
let oldKey = entry.key;
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);
}
}
if (isManaged<K>()) __release(changetype<usize>(entry.key));
if (isManaged<V>()) __release(changetype<usize>(entry.value));
entry.taggedNext |= EMPTY;
--this.entriesCount;
// check if rehashing is appropriate
@ -268,10 +189,10 @@ export class Map<K,V> {
// GC integration
@unsafe private __traverse(): void {
__ref_mark(changetype<usize>(this.buckets));
@unsafe private __traverse(cookie: u32): void {
__visit(changetype<usize>(this.buckets), cookie);
var entries = this.entries;
__ref_mark(changetype<usize>(entries));
__visit(changetype<usize>(entries), cookie);
if (isManaged<K>() || isManaged<V>()) {
let cur = changetype<usize>(entries);
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);
if (isNullable<K>()) {
if (val) {
__ref_mark(val);
__gc_mark_members(__runtime_id<K>(), val);
__visit(val, cookie);
__visit_members(val, cookie);
}
} else {
__ref_mark(val);
__gc_mark_members(__runtime_id<K>(), val);
__visit(val, cookie);
__visit_members(val, cookie);
}
}
if (isManaged<V>()) {
let val = changetype<usize>(entry.value);
if (isNullable<V>()) {
if (val) {
__ref_mark(val);
__gc_mark_members(__runtime_id<V>(), val);
__visit(val, cookie);
__visit_members(val, cookie);
}
} else {
__ref_mark(val);
__gc_mark_members(__runtime_id<V>(), val);
__visit(val, cookie);
__visit_members(val, cookie);
}
}
}

View File

@ -1,20 +1,9 @@
/// <reference path="./allocator/index.d.ts" />
import { memcmp, memmove, memset } from "./util/memory";
import { E_NOTIMPLEMENTED } from "./util/error";
// @ts-ignore: decorator
@builtin
export declare const HEAP_BASE: usize;
/** Memory manager interface. */
export namespace memory {
/** Whether the memory managed interface is implemented. */
// @ts-ignore: decorator
@lazy
export const implemented: bool = isDefined(__mem_allocate);
/** Gets the size of the memory in pages. */
// @ts-ignore: decorator
@builtin
@ -53,30 +42,6 @@ export namespace memory {
throw new Error(E_NOTIMPLEMENTED);
}
/** Dynamically allocates a section of memory and returns its address. */
// @ts-ignore: decorator
@unsafe
export function allocate(size: usize): usize {
if (isDefined(__mem_allocate)) return __mem_allocate(size);
else throw new Error(E_NOTIMPLEMENTED);
}
/** Dynamically frees a section of memory by the previously allocated address. */
// @ts-ignore: decorator
@unsafe
export function free(ptr: usize): void {
if (isDefined(__mem_free)) __mem_free(ptr);
else throw new Error(E_NOTIMPLEMENTED);
}
/** Resets the memory to its initial state. Arena allocator only. */
// @ts-ignore: decorator
@unsafe
export function reset(): void {
if (isDefined(__mem_reset)) __mem_reset();
else throw new Error(E_NOTIMPLEMENTED);
}
/** Repeats a section of memory at a specific address. */
// @ts-ignore: decorator
@unsafe

View File

@ -8,29 +8,68 @@ It is based on [the TLSF memory manager](./tlsf.ts) and [a pure reference counti
Interface
---------
* **__rt_allocate**(size: `usize`, id: `u32` = 0): `usize`<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.
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.
* **__rt_free**(ref: `usize`): `void`<br />
* **__free**(ref: `usize`): `void`<br />
Frees a dynamically allocated chunk of memory by its address.
* **__rt_retain**(ref: `usize`): `void`<br />
Retains a reference.
* **__retain**(ref: `usize`): `void`<br />
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 />
Releases a reference.
* **__release**(ref: `usize`): `void`<br />
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 />
Forces a full garbage collection cycle.
* **__collect**(): `void`<br />
Forces a full garbage collection cycle. By default this means that reference cycles are resolved and possibly collected.
* **__rt_typeinfo**(id: `u32`): `void`<br />
Obtains the runtime type information for objects of the kind represented by the specified id.
* **__visit**(ref: `usize`, cookie: `u32`): `void`<br />
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
----
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
@inline export const DEBUG = true;
/** Common block structure. */
@unmanaged export class CommonBlock {
// ╒════════════════ Common block layout (32-bit) ═════════════════╕
// 3 2 1
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤
// │ MM info │ -16
// ├───────────────────────────────────────────────────────────────┤
// │ GC info │ -12
// ├───────────────────────────────────────────────────────────────┤
// │ runtime id │ -8
// ├───────────────────────────────────────────────────────────────┤
// │ runtime size │ -4
// ╞═══════════════════════════════════════════════════════════════╡
// │ ... │ ref
@unmanaged export class BLOCK {
/** Memory manager info. */
mmInfo: usize; // WASM64 might need adaption
mmInfo: usize; // WASM64 needs adaption
/** Garbage collector info. */
gcInfo: u32;
/** Runtime class id. */
@ -24,16 +36,53 @@
rtSize: u32;
}
/////////////////////////////////// Type information interface ////////////////////////////////////
import { RTTI_BASE } from "../runtime";
import { RTTIData } from "../common/rtti";
// @ts-ignore: decorator
@inline export const BLOCK_OVERHEAD = (offsetof<BLOCK>() + AL_MASK) & ~AL_MASK;
// @ts-ignore: decorator
@global @unsafe
function __rt_typeinfo(id: u32): u32 {
var ptr: usize = RTTI_BASE;
@inline export const BLOCK_MAXSIZE: usize = (1 << 30) - BLOCK_OVERHEAD;
/////////////////////////////////// Type information interface ////////////////////////////////////
import { RTTI_BASE } from "builtins";
import { RTTIData, RTTIFlags } from "common/rtti";
// @ts-ignore: decorator
@unsafe @global
export function __typeinfo(id: u32): RTTIFlags {
var ptr = RTTI_BASE;
return !id || id > load<u32>(ptr)
? unreachable()
: 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";
// @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
import { AL_MASK, BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "rt/common";
// @ts-ignore: decorator
@lazy
@ -16,11 +8,9 @@ var startOffset: usize = (HEAP_BASE + AL_MASK) & ~AL_MASK;
@lazy
var offset: usize = startOffset;
//////////////////////////////////// Memory manager interface /////////////////////////////////////
// @ts-ignore: decorator
@unsafe @global
export function __rt_allocate(size: usize, id: u32): usize {
export function __alloc(size: usize, id: u32): usize {
if (size > BLOCK_MAXSIZE) unreachable();
var ptr = offset + BLOCK_OVERHEAD;
var newPtr = (ptr + max<usize>(size, 1) + AL_MASK) & ~AL_MASK;
@ -33,7 +23,7 @@ export function __rt_allocate(size: usize, id: u32): usize {
}
}
offset = newPtr;
var block = changetype<CommonBlock>(ptr - BLOCK_OVERHEAD);
var block = changetype<BLOCK>(ptr - BLOCK_OVERHEAD);
block.rtId = id;
block.rtSize = size;
return ptr;
@ -41,11 +31,11 @@ export function __rt_allocate(size: usize, id: u32): usize {
// @ts-ignore: decorator
@unsafe @global
export function __rt_reallocate(ref: usize, size: usize): usize {
var block = changetype<CommonBlock>(ref - BLOCK_OVERHEAD);
export function __realloc(ref: usize, size: usize): usize {
var block = changetype<BLOCK>(ref - BLOCK_OVERHEAD);
var oldSize = <usize>block.rtSize;
if (size > oldSize) {
let newRef = __rt_allocate(size, block.rtId);
let newRef = __alloc(size, block.rtId);
memory.copy(newRef, ref, oldSize);
ref = newRef;
} else {
@ -56,30 +46,40 @@ export function __rt_reallocate(ref: usize, size: usize): usize {
// @ts-ignore: decorator
@unsafe @global
export function __rt_free(ref: usize): void {
export function __free(ref: usize): void {
}
// @ts-ignore: decorator
@unsafe @global
export function __rt_reset(): void { // special
offset = startOffset;
}
/////////////////////////////////// Garbage collector interface ///////////////////////////////////
// @unsafe @global
// export function __reset(): void { // special
// offset = startOffset;
// }
// @ts-ignore: decorator
@global @unsafe
export function __rt_retain(ref: usize): void {
export function __retain(ref: usize): usize {
return ref;
}
// @ts-ignore: decorator
@global @unsafe
export function __rt_release(ref: usize): void {
export function __release(ref: usize): void {
}
// @ts-ignore: decorator
@global @unsafe
export function __rt_collect(): void {
function __visit(ref: usize, cookie: u32): void {
}
export { __rt_typeinfo };
// @ts-ignore: decorator
@global @unsafe
function __retainRelease(ref: usize, oldRef: usize): usize {
return ref;
}
// @ts-ignore: decorator
@global @unsafe
export function __collect(): void {
}
export { __instanceof, __typeinfo } from "rt/common";

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 { Block, freeBlock, ROOT } from "./tlsf";
import { DEBUG, BLOCK_OVERHEAD, BLOCK } from "rt/common";
import { Block, freeBlock, ROOT } from "rt/tlsf";
import { RTTIFlags } from "common/rtti";
/////////////////////////// A Pure Reference Counting Garbage Collector ///////////////////////////
// see: https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon03Pure.pdf
// TODO: make visitors eat cookies so we can compile direct calls into a switch
function __rt_visit_members(s: Block, cookie: i32): void { unreachable(); }
function __rt_flags(classId: u32): u32 { return unreachable(); }
const ACYCLIC_FLAG: u32 = 0;
// ╒══════════════════════ GC Info structure ══════════════════════╕
// │ 3 2 1 │
// │1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0│
@ -66,7 +62,9 @@ const ACYCLIC_FLAG: u32 = 0;
// @ts-ignore: decorator
@global
function __rt_visit(s: Block, cookie: i32): void {
function __visit(ref: usize, cookie: i32): void {
if (ref < HEAP_BASE) return;
var s = changetype<Block>(ref - BLOCK_OVERHEAD);
switch (cookie) {
case VISIT_DECREMENT: {
decrement(s);
@ -100,18 +98,18 @@ function __rt_visit(s: Block, cookie: i32): void {
}
/** Increments the reference count of the specified block by one.*/
export function increment(s: Block): void {
function increment(s: Block): void {
var info = s.gcInfo;
assert((info & ~REFCOUNT_MASK) == ((info + 1) & ~REFCOUNT_MASK)); // overflow
s.gcInfo = info + 1;
}
/** Decrements the reference count of the specified block by one, possibly freeing it. */
export function decrement(s: Block): void {
function decrement(s: Block): void {
var info = s.gcInfo;
var rc = info & REFCOUNT_MASK;
if (rc == 1) {
__rt_visit_members(s, VISIT_DECREMENT);
__visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_DECREMENT);
if (!(info & BUFFERED_MASK)) {
freeBlock(ROOT, s);
} else {
@ -119,7 +117,7 @@ export function decrement(s: Block): void {
}
} else {
if (DEBUG) assert(rc > 0);
if (!(__rt_flags(s.rtId) & ACYCLIC_FLAG)) {
if (!(__typeinfo(s.rtId) & RTTIFlags.ACYCLIC)) {
s.gcInfo = BUFFERED_MASK | COLOR_PURPLE | (rc - 1);
if (!(info & BUFFERED_MASK)) {
appendRoot(s);
@ -164,7 +162,9 @@ function growRoots(): void {
}
/** Collects cyclic garbage. */
export function collectCycles(): void {
// @ts-ignore: decorator
@global @unsafe
export function __collect(): void {
// markRoots
var roots = ROOTS;
@ -205,7 +205,7 @@ function markGray(s: Block): void {
var info = s.gcInfo;
if ((info & COLOR_MASK) != COLOR_GRAY) {
s.gcInfo = (info & ~COLOR_MASK) | COLOR_GRAY;
__rt_visit_members(s, VISIT_MARKGRAY);
__visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_MARKGRAY);
}
}
@ -217,7 +217,7 @@ function scan(s: Block): void {
scanBlack(s);
} else {
s.gcInfo = (info & ~COLOR_MASK) | COLOR_WHITE;
__rt_visit_members(s, VISIT_SCAN);
__visit_members(changetype<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. */
function scanBlack(s: Block): void {
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. */
@ -233,7 +233,31 @@ function collectWhite(s: Block): void {
var info = s.gcInfo;
if ((info & COLOR_MASK) == COLOR_WHITE && !(info & BUFFERED_MASK)) {
// s.gcInfo = (info & ~COLOR_MASK) | COLOR_BLACK;
__rt_visit_members(s, VISIT_COLLECTWHITE);
__visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_COLLECTWHITE);
}
freeBlock(ROOT, s);
}
// @ts-ignore: decorator
@global @unsafe
export function __retain(ref: usize): usize {
if (ref > HEAP_BASE) increment(changetype<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 ///////////////////////
// see: http://www.gii.upv.es/tlsf/
@ -69,7 +69,7 @@ import { AL_BITS, AL_SIZE, AL_MASK, DEBUG, CommonBlock } from "./common";
// │ if free: back ▲ │ ◄─┘
// └───────────────────────────────────────────────────────────────┘ payload ┘ >= MIN SIZE
// F: FREE, L: LEFTFREE
@unmanaged export class Block extends CommonBlock {
@unmanaged export class Block extends BLOCK {
/** Previous free block, if any. Only valid if free, otherwise part of payload. */
prev: Block | null;
@ -79,15 +79,13 @@ import { AL_BITS, AL_SIZE, AL_MASK, DEBUG, CommonBlock } from "./common";
// If the block is free, there is a 'back'reference at its end pointing at its start.
}
// Block constants. Overhead is always present, no matter if free or used. Also, a block must have
// a minimum size of three pointers so it can hold `prev`, `next` and `back` if free.
// Block constants. A block must have a minimum size of three pointers so it can hold `prev`,
// `next` and `back` if free.
// @ts-ignore: decorator
@inline export const BLOCK_OVERHEAD: usize = (offsetof<Block>("prev") + AL_MASK) & ~AL_MASK;
@inline const BLOCK_MINSIZE: usize = (3 * sizeof<usize>() + AL_MASK) & ~AL_MASK; // prev + next + back
// @ts-ignore: decorator
@inline const BLOCK_MINSIZE: usize = (3 * sizeof<usize>() + AL_MASK) & ~AL_MASK;// prev + next + back
// @ts-ignore: decorator
@inline const BLOCK_MAXSIZE: usize = 1 << (FL_BITS + SB_BITS - 1); // exclusive
// @inline const BLOCK_MAXSIZE: usize = 1 << (FL_BITS + SB_BITS - 1); // exclusive, lives in common.ts
/** Gets the left block of a block. Only valid if the left block is free. */
// @ts-ignore: decorator
@ -532,3 +530,32 @@ export function freeBlock(root: Root, block: Block): void {
block.mmInfo = blockInfo | FREE;
insertBlock(root, block);
}
// @ts-ignore: decorator
@global @unsafe
export function __alloc(size: usize, id: u32): usize {
var root = ROOT;
if (!root) {
initializeRoot();
root = ROOT;
}
var block = allocateBlock(root, size);
block.rtId = id;
return changetype<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 { __runtime_id, __gc_mark_members } from "./runtime";
// A deterministic hash set based on CloseTable from https://github.com/jorendorff/dht
@ -79,7 +78,7 @@ export class Set<K> {
}
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
);
while (entry) {
@ -106,23 +105,10 @@ export class Set<K> {
);
}
// append new entry
let entries = this.entries;
entry = changetype<SetEntry<K>>(changetype<usize>(entries) + this.entriesOffset++ * ENTRY_SIZE<K>());
entry.key = key;
// link with the set
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);
}
}
entry = changetype<SetEntry<K>>(changetype<usize>(this.entries) + this.entriesOffset++ * ENTRY_SIZE<K>());
entry.key = isManaged<K>()
? changetype<K>(__retain(changetype<usize>(key)))
: key;
++this.entriesCount;
// link with previous entry in bucket
let bucketPtrBase = changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE;
@ -132,24 +118,9 @@ export class Set<K> {
}
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 (isManaged<K>()) {
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);
}
}
if (isManaged<K>()) __release(changetype<usize>(entry.key)); // exact 'key'
entry.taggedNext |= EMPTY;
--this.entriesCount;
// check if rehashing is appropriate
@ -172,9 +143,9 @@ export class Set<K> {
var oldEnd = oldPtr + <usize>this.entriesOffset * ENTRY_SIZE<K>();
var newPtr = changetype<usize>(newEntries);
while (oldPtr != oldEnd) {
let oldEntry = changetype<SetEntry<K>>(oldPtr);
let oldEntry = changetype<SetEntry<K>>(oldPtr); // unmanaged!
if (!(oldEntry.taggedNext & EMPTY)) {
let newEntry = changetype<SetEntry<K>>(newPtr);
let newEntry = changetype<SetEntry<K>>(newPtr); // unmanaged!
newEntry.key = oldEntry.key;
let newBucketIndex = HASH<K>(oldEntry.key) & newBucketsMask;
let newBucketPtrBase = changetype<usize>(newBuckets) + <usize>newBucketIndex * BUCKET_SIZE;
@ -198,10 +169,10 @@ export class Set<K> {
// GC integration
@unsafe private __traverse(): void {
__ref_mark(changetype<usize>(this.buckets));
@unsafe private __traverse(cookie: u32): void {
__visit(changetype<usize>(this.buckets), cookie);
var entries = this.entries;
__ref_mark(changetype<usize>(entries));
__visit(changetype<usize>(entries), cookie);
if (isManaged<K>()) {
let cur = changetype<usize>(entries);
let end = cur + <usize>this.entriesOffset * ENTRY_SIZE<K>();
@ -211,12 +182,12 @@ export class Set<K> {
let val = changetype<usize>(entry.key);
if (isNullable<K>()) {
if (val) {
__ref_mark(val);
__gc_mark_members(__runtime_id<K>(), val);
__visit(val, cookie);
__visit_members(val, cookie);
}
} else {
__ref_mark(val);
__gc_mark_members(__runtime_id<K>(), val);
__visit(val, cookie);
__visit_members(val, cookie);
}
}
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 { HEADER, HEADER_SIZE, allocate, register, NEWARRAY } from "./util/runtime";
import { BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "./rt/common";
import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./util/string";
import { E_INVALIDLENGTH } from "./util/error";
import { __runtime_id } from "./runtime";
import { ArrayBufferView } from "./arraybuffer";
import { idof } from "./builtins";
@sealed export abstract class String {
@lazy static readonly MAX_LENGTH: i32 = (MAX_SIZE_32 - HEADER_SIZE) >> alignof<u16>();
@lazy static readonly MAX_LENGTH: i32 = BLOCK_MAXSIZE >>> alignof<u16>();
// TODO Add and handle second argument
static fromCharCode(code: i32): String {
var out = allocate(2);
static fromCharCode(code: i32): string {
var out = __alloc(2, idof<string>());
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);
var sur = code > 0xFFFF;
var out = allocate((i32(sur) + 1) << 1);
var out = __alloc((i32(sur) + 1) << 1, idof<string>());
if (!sur) {
store<u16>(out, <u16>code);
} else {
@ -30,25 +29,19 @@ import { ArrayBufferView } from "./arraybuffer";
let lo: u32 = (code & 0x3FF) + 0xDC00;
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 {
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 {
assert(this !== null);
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)));
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
charCodeAt(pos: i32): i32 {
@ -75,10 +68,10 @@ import { ArrayBufferView } from "./arraybuffer";
var otherSize: isize = other.length << 1;
var outSize: usize = thisSize + otherSize;
if (outSize == 0) return changetype<String>("");
var out = allocate(outSize);
var out = __alloc(outSize, idof<String>());
memory.copy(out, changetype<usize>(this), thisSize);
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 {
@ -194,9 +187,9 @@ import { ArrayBufferView } from "./arraybuffer";
if (intStart < 0) intStart = max(size + intStart, 0);
var resultLength = min(max(end, 0), size - intStart);
if (resultLength <= 0) return changetype<String>("");
var out = allocate(resultLength << 1);
var out = __alloc(resultLength << 1, idof<String>());
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 {
@ -209,9 +202,9 @@ import { ArrayBufferView } from "./arraybuffer";
len = toPos - fromPos;
if (!len) return changetype<String>("");
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);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
trim(): String {
@ -237,9 +230,9 @@ import { ArrayBufferView } from "./arraybuffer";
}
if (!size) return changetype<String>("");
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);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
@inline
@ -267,9 +260,9 @@ import { ArrayBufferView } from "./arraybuffer";
if (!offset) return this;
size -= offset;
if (!size) return changetype<String>("");
var out = allocate(size);
var out = __alloc(size, idof<String>());
memory.copy(out, changetype<usize>(this) + offset, size);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
trimEnd(): String {
@ -286,9 +279,9 @@ import { ArrayBufferView } from "./arraybuffer";
}
if (!size) return changetype<String>("");
if (size == originalSize) return this;
var out = allocate(size);
var out = __alloc(size, idof<String>());
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 {
@ -298,7 +291,7 @@ import { ArrayBufferView } from "./arraybuffer";
var padSize = <usize>padString.length << 1;
if (targetSize < thisSize || !padSize) return this;
var prependSize = targetSize - thisSize;
var out = allocate(targetSize);
var out = __alloc(targetSize, idof<String>());
if (prependSize > padSize) {
let repeatCount = (prependSize - 2) / padSize;
let restBase = repeatCount * padSize;
@ -309,7 +302,7 @@ import { ArrayBufferView } from "./arraybuffer";
memory.copy(out, changetype<usize>(padString), prependSize);
}
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 {
@ -319,7 +312,7 @@ import { ArrayBufferView } from "./arraybuffer";
var padSize = <usize>padString.length << 1;
if (targetSize < thisSize || !padSize) return this;
var appendSize = targetSize - thisSize;
var out = allocate(targetSize);
var out = __alloc(targetSize, idof<String>());
memory.copy(out, changetype<usize>(this), thisSize);
if (appendSize > padSize) {
let repeatCount = (appendSize - 2) / padSize;
@ -330,7 +323,7 @@ import { ArrayBufferView } from "./arraybuffer";
} else {
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 {
@ -344,9 +337,9 @@ import { ArrayBufferView } from "./arraybuffer";
if (count == 0 || !length) return changetype<String>("");
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);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
slice(beginIndex: i32, endIndex: i32 = i32.MAX_VALUE): String {
@ -355,68 +348,64 @@ import { ArrayBufferView } from "./arraybuffer";
var end = endIndex < 0 ? max(endIndex + len, 0) : min(endIndex, len);
len = end - begin;
if (len <= 0) return changetype<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);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
split(separator: String | null = null, limit: i32 = i32.MAX_VALUE): String[] {
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];
var length: isize = this.length;
var sepLen: isize = separator.length;
if (limit < 0) limit = i32.MAX_VALUE;
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
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;
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<usize>(resultStart + (<usize>i << alignof<usize>()), charStr); // result[i] = charStr
register(charStr, __runtime_id<String>());
if (isManaged<String>()) {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(charStr), changetype<usize>(result));
if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(charStr));
}
if (isManaged<String>()) __retain(charStr);
}
return result;
return changetype<Array<String>>(result); // retains
} else if (!length) {
let result = NEWARRAY<String>(1);
store<string>(changetype<ArrayBufferView>(result).dataStart, ""); // no need to register/link
return result;
let result = __allocArray(1, alignof<String>(), idof<Array<String>>());
store<usize>(changetype<ArrayBufferView>(result).dataStart, changetype<usize>("")); // static ""
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;
while ((end = this.indexOf(separator, start)) != -1) {
let len = end - start;
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);
result.push(changetype<String>(register(out, __runtime_id<String>())));
result.push(changetype<String>(out));
} else {
result.push(changetype<String>(""));
}
if (++i == limit) return result;
if (++i == limit) return changetype<Array<String>>(result); // retains
start = end + sepLen;
}
if (!start) {
let result = NEWARRAY<String>(1);
unchecked(result[0] = this);
return result;
if (!start) { // also means: loop above didn't do anything
result.push(this);
return changetype<Array<String>>(result); // retains
}
var len = length - start;
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);
result.push(changetype<String>(register(out, __runtime_id<String>())));
result.push(changetype<String>(out)); // retains
} else {
result.push(changetype<String>(""));
result.push(changetype<String>("")); // static ""
}
return result;
return changetype<Array<String>>(result); // retains
// releases result
}
toString(): String {
@ -484,10 +473,10 @@ import { ArrayBufferView } from "./arraybuffer";
}
}
assert(ptrPos == len);
var out = allocate(bufPos);
memory.copy(changetype<usize>(out), buf, bufPos);
var out = __alloc(bufPos, idof<String>());
memory.copy(out, buf, bufPos);
memory.free(buf);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
toUTF8(): usize {
@ -553,7 +542,7 @@ export function parseFloat(str: String): f64 {
var len: i32 = str.length;
if (!len) return NaN;
var ptr = changetype<usize>(str) /* + HEAD -> offset */;
var ptr = changetype<usize>(str);
var code = <i32>load<u16>(ptr);
// determine sign

View File

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

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 { __runtime_id } from "../runtime";
import { ArrayBufferView } from "../arraybuffer";
// @ts-ignore: decorator
@ -264,10 +265,10 @@ export function utoa32(value: u32): String {
if (!value) return "0";
var decimals = decimalCount32(value);
var out = allocate(decimals << 1);
var out = __alloc(decimals << 1, idof<String>());
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 {
@ -277,12 +278,12 @@ export function itoa32(value: i32): String {
if (sign) value = -value;
var decimals = decimalCount32(value) + u32(sign);
var out = allocate(decimals << 1);
var out = __alloc(decimals << 1, idof<String>());
utoa32_core(changetype<usize>(out), value, decimals);
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 {
@ -292,14 +293,14 @@ export function utoa64(value: u64): String {
if (value <= u32.MAX_VALUE) {
let val32 = <u32>value;
let decimals = decimalCount32(val32);
out = allocate(decimals << 1);
out = __alloc(decimals << 1, idof<String>());
utoa32_core(out, val32, decimals);
} else {
let decimals = decimalCount64(value);
out = allocate(decimals << 1);
out = __alloc(decimals << 1, idof<String>());
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 {
@ -312,16 +313,16 @@ export function itoa64(value: i64): String {
if (<u64>value <= <u64>u32.MAX_VALUE) {
let val32 = <u32>value;
let decimals = decimalCount32(val32) + u32(sign);
out = allocate(decimals << 1);
out = __alloc(decimals << 1, idof<String>());
utoa32_core(changetype<usize>(out), val32, decimals);
} else {
let decimals = decimalCount64(value) + u32(sign);
out = allocate(decimals << 1);
out = __alloc(decimals << 1, idof<String>());
utoa64_core(changetype<usize>(out), value, decimals);
}
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 {
@ -626,11 +627,12 @@ export function dtoa(value: f64): String {
if (isNaN<f64>(value)) return "NaN";
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 result = changetype<String>(temp).substring(0, length); // registers
discard(temp);
return result;
if (length < MAX_DOUBLE_LENGTH) {
return changetype<String>(temp).substring(0, length); // retains/releases `temp`, retains return
}
return changetype<String>(temp); // retains
}
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 {
let sign = i32(value < 0);
let len = 8 + sign;
let source = changetype<usize>(select<String>("-Infinity", "Infinity", sign));
memory.copy(buffer, source, len << 1);
memory.copy(buffer, changetype<usize>(select<String>("-Infinity", "Infinity", sign)), len << 1);
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 => {
exports = result.instance.exports;
U32 = new Uint32Array(exports.memory.buffer);
var first = exports.__rt_allocate(255);
exports.__rt_free(first);
var first = exports.__alloc(255);
exports.__free(first);
ROOT = first - 17;
while (!U32[ROOT >> 2]) --ROOT; // find tail
ROOT -= (1 + FL_BITS + HL_SIZE) << 2;
@ -113,7 +113,7 @@ function update() {
}
function allocate(size) {
var ptr = exports.__rt_allocate(size);
var ptr = exports.__alloc(size);
if (!ptr) {
alert("should not happen");
return;
@ -127,7 +127,7 @@ function allocate(size) {
var er = document.createElement("button");
er.innerText = "realloc";
er.onclick = function() {
ptr = exports.__rt_reallocate(ptr, es.value >>> 0);
ptr = exports.__realloc(ptr, es.value >>> 0);
update();
};
el.appendChild(er);
@ -136,7 +136,7 @@ function allocate(size) {
ef.className = "free";
el.appendChild(ef);
ef.onclick = function() {
exports.__rt_free(ptr);
exports.__free(ptr);
document.getElementById("segs").removeChild(el);
update();
};

View File

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

Binary file not shown.

View File

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