mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-05-19 02:31:25 +00:00
Initial new rt integration
This commit is contained in:
parent
dd2bdd0383
commit
ba1a0c2369
@ -392,11 +392,11 @@ exports.main = function main(argv, options, callback) {
|
|||||||
// Include runtime template
|
// Include runtime template
|
||||||
{
|
{
|
||||||
let templateName = String(args.runtime);
|
let templateName = String(args.runtime);
|
||||||
let templateText = exports.libraryFiles["runtime/" + templateName];
|
let templateText = exports.libraryFiles["rt/index-" + templateName];
|
||||||
if (templateText == null) {
|
if (templateText == null) {
|
||||||
templateText = readFile(templateName + ".ts", baseDir);
|
templateText = readFile(templateName + ".ts", baseDir);
|
||||||
if (templateText == null) {
|
if (templateText == null) {
|
||||||
return callback(Error("Runtime template '" + templateName + " not found."));
|
return callback(Error("Runtime template '" + templateName + "' not found."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stats.parseCount++;
|
stats.parseCount++;
|
||||||
|
@ -83,15 +83,14 @@
|
|||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"description": [
|
"description": [
|
||||||
"Specifies the runtime template to include in the program.",
|
"Specifies the runtime implementation to include in the program.",
|
||||||
"",
|
"",
|
||||||
" none No allocator/GC or compose your own. [default]",
|
" full Default runtime based on TLSF and reference counting.",
|
||||||
" trace TLSF memory allocator and ITCM garbage collector.",
|
" stub Minimal stub implementation without free/GC support.",
|
||||||
" arena Just the arena memory allocator. No free/GC.",
|
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
"type": "s",
|
"type": "s",
|
||||||
"default": "none"
|
"default": "full"
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"description": "Enables debug information in emitted binaries.",
|
"description": "Enables debug information in emitted binaries.",
|
||||||
|
@ -42,9 +42,6 @@
|
|||||||
"no-default-export": {
|
"no-default-export": {
|
||||||
"severity": "error"
|
"severity": "error"
|
||||||
},
|
},
|
||||||
"no-duplicate-imports": {
|
|
||||||
"severity": "error"
|
|
||||||
},
|
|
||||||
"no-duplicate-super": {
|
"no-duplicate-super": {
|
||||||
"severity": "error"
|
"severity": "error"
|
||||||
},
|
},
|
||||||
|
@ -1,23 +1,31 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const hasBigInt64 = typeof BigUint64Array !== "undefined";
|
/** Size of the runtime header, in bytes. */
|
||||||
const thisPtr = Symbol();
|
const HEADER_SIZE = 16;
|
||||||
|
/** Runtime header offset of `classId`. */
|
||||||
|
const CLASSID_OFFSET = -HEADER_SIZE;
|
||||||
|
/** Runtime header offset of `payloadLength`. */
|
||||||
|
const PAYLOADLENGTH_OFFSET = -HEADER_SIZE + 4;
|
||||||
|
/** Whether BigInt arrays are supported. */
|
||||||
|
const SUPPORTS_BIGINT = typeof BigUint64Array !== "undefined";
|
||||||
|
/** Unique symbol for memoized 'this'. */
|
||||||
|
const THIS = Symbol();
|
||||||
|
|
||||||
/** Gets a string from an U32 and an U16 view on a memory. */
|
/** Gets a string from an U32 and an U16 view on a memory. */
|
||||||
function getStringImpl(U32, U16, ptr) {
|
function getStringImpl(U32, U16, ptr) {
|
||||||
var dataLength = U32[ptr >>> 2];
|
var size32 = U32[(ptr + PAYLOADLENGTH_OFFSET) >>> 2];
|
||||||
var dataOffset = (ptr + 4) >>> 1;
|
var offset16 = ptr >>> 1;
|
||||||
var dataRemain = dataLength;
|
var remain32 = size32;
|
||||||
var parts = [];
|
var parts = [];
|
||||||
const chunkSize = 1024;
|
const chunkSize = 1024;
|
||||||
while (dataRemain > chunkSize) {
|
while (remain32 > chunkSize) {
|
||||||
let last = U16[dataOffset + chunkSize - 1];
|
let last = U16[offset16 + chunkSize - 1];
|
||||||
let size = last >= 0xD800 && last < 0xDC00 ? chunkSize - 1 : chunkSize;
|
let size = last >= 0xD800 && last < 0xDC00 ? chunkSize - 1 : chunkSize;
|
||||||
let part = U16.subarray(dataOffset, dataOffset += size);
|
let part = U16.subarray(offset16, offset16 += size);
|
||||||
parts.push(String.fromCharCode.apply(String, part));
|
parts.push(String.fromCharCode.apply(String, part));
|
||||||
dataRemain -= size;
|
remain32 -= size;
|
||||||
}
|
}
|
||||||
return parts.join("") + String.fromCharCode.apply(String, U16.subarray(dataOffset, dataOffset + dataRemain));
|
return parts.join("") + String.fromCharCode.apply(String, U16.subarray(offset16, offset16 + remain32));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Prepares the base module prior to instantiation. */
|
/** Prepares the base module prior to instantiation. */
|
||||||
@ -54,8 +62,7 @@ function postInstantiate(baseModule, instance) {
|
|||||||
var memory_fill = rawExports["memory.fill"];
|
var memory_fill = rawExports["memory.fill"];
|
||||||
var memory_free = rawExports["memory.free"];
|
var memory_free = rawExports["memory.free"];
|
||||||
var table = rawExports.table;
|
var table = rawExports.table;
|
||||||
var capabilities = rawExports[".capabilities"] || 0;
|
var setargc = rawExports["$.setArgc"] || function() {};
|
||||||
var setargc = rawExports[".setargc"] || function() {};
|
|
||||||
|
|
||||||
// Provide views for all sorts of basic values
|
// Provide views for all sorts of basic values
|
||||||
var buffer, I8, U8, I16, U16, I32, U32, F32, F64, I64, U64;
|
var buffer, I8, U8, I16, U16, I32, U32, F32, F64, I64, U64;
|
||||||
@ -71,7 +78,7 @@ function postInstantiate(baseModule, instance) {
|
|||||||
U16 = new Uint16Array(buffer);
|
U16 = new Uint16Array(buffer);
|
||||||
I32 = new Int32Array(buffer);
|
I32 = new Int32Array(buffer);
|
||||||
U32 = new Uint32Array(buffer);
|
U32 = new Uint32Array(buffer);
|
||||||
if (hasBigInt64) {
|
if (SUPPORTS_BIGINT) {
|
||||||
I64 = new BigInt64Array(buffer);
|
I64 = new BigInt64Array(buffer);
|
||||||
U64 = new BigUint64Array(buffer);
|
U64 = new BigUint64Array(buffer);
|
||||||
}
|
}
|
||||||
@ -271,7 +278,7 @@ function demangle(exports, baseModule) {
|
|||||||
};
|
};
|
||||||
ctor.prototype = {};
|
ctor.prototype = {};
|
||||||
ctor.wrap = function(thisValue) {
|
ctor.wrap = function(thisValue) {
|
||||||
return Object.create(ctor.prototype, { [thisPtr]: { value: thisValue, writable: false } });
|
return Object.create(ctor.prototype, { [THIS]: { value: thisValue, writable: false } });
|
||||||
};
|
};
|
||||||
if (classElem) Object.getOwnPropertyNames(classElem).forEach(name =>
|
if (classElem) Object.getOwnPropertyNames(classElem).forEach(name =>
|
||||||
Object.defineProperty(ctor, name, Object.getOwnPropertyDescriptor(classElem, name))
|
Object.defineProperty(ctor, name, Object.getOwnPropertyDescriptor(classElem, name))
|
||||||
@ -285,8 +292,8 @@ function demangle(exports, baseModule) {
|
|||||||
let getter = exports[internalName.replace("set:", "get:")];
|
let getter = exports[internalName.replace("set:", "get:")];
|
||||||
let setter = exports[internalName.replace("get:", "set:")];
|
let setter = exports[internalName.replace("get:", "set:")];
|
||||||
Object.defineProperty(curr, name, {
|
Object.defineProperty(curr, name, {
|
||||||
get: function() { return getter(this[thisPtr]); },
|
get: function() { return getter(this[THIS]); },
|
||||||
set: function(value) { setter(this[thisPtr], value); },
|
set: function(value) { setter(this[THIS], value); },
|
||||||
enumerable: true
|
enumerable: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -297,7 +304,7 @@ function demangle(exports, baseModule) {
|
|||||||
Object.defineProperty(curr, name, {
|
Object.defineProperty(curr, name, {
|
||||||
value: function (...args) {
|
value: function (...args) {
|
||||||
setargc(args.length);
|
setargc(args.length);
|
||||||
return elem(this[thisPtr], ...args);
|
return elem(this[THIS], ...args);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
116
src/builtins.ts
116
src/builtins.ts
@ -461,13 +461,19 @@ export namespace BuiltinSymbols {
|
|||||||
|
|
||||||
export const v8x16_shuffle = "~lib/builtins/v8x16.shuffle";
|
export const v8x16_shuffle = "~lib/builtins/v8x16.shuffle";
|
||||||
|
|
||||||
|
// internals
|
||||||
|
export const HEAP_BASE = "~lib/builtins/HEAP_BASE";
|
||||||
|
export const RTTI_BASE = "~lib/builtins/RTTI_BASE";
|
||||||
|
export const idof = "~lib/builtins/idof";
|
||||||
|
export const visit_globals = "~lib/builtins/visit_globals";
|
||||||
|
export const visit_members = "~lib/builtins/visit_members";
|
||||||
|
|
||||||
// std/diagnostics.ts
|
// std/diagnostics.ts
|
||||||
export const ERROR = "~lib/diagnostics/ERROR";
|
export const ERROR = "~lib/diagnostics/ERROR";
|
||||||
export const WARNING = "~lib/diagnostics/WARNING";
|
export const WARNING = "~lib/diagnostics/WARNING";
|
||||||
export const INFO = "~lib/diagnostics/INFO";
|
export const INFO = "~lib/diagnostics/INFO";
|
||||||
|
|
||||||
// std/memory.ts
|
// std/memory.ts
|
||||||
export const HEAP_BASE = "~lib/memory/HEAP_BASE";
|
|
||||||
export const memory_size = "~lib/memory/memory.size";
|
export const memory_size = "~lib/memory/memory.size";
|
||||||
export const memory_grow = "~lib/memory/memory.grow";
|
export const memory_grow = "~lib/memory/memory.grow";
|
||||||
export const memory_copy = "~lib/memory/memory.copy";
|
export const memory_copy = "~lib/memory/memory.copy";
|
||||||
@ -477,8 +483,6 @@ export namespace BuiltinSymbols {
|
|||||||
export const memory_reset = "~lib/memory/memory.reset";
|
export const memory_reset = "~lib/memory/memory.reset";
|
||||||
|
|
||||||
// std/runtime.ts
|
// std/runtime.ts
|
||||||
export const RTTI_BASE = "~lib/runtime/RTTI_BASE";
|
|
||||||
export const runtime_id = "~lib/runtime/__runtime_id";
|
|
||||||
export const runtime_instanceof = "~lib/runtime/runtime.instanceof";
|
export const runtime_instanceof = "~lib/runtime/runtime.instanceof";
|
||||||
export const runtime_flags = "~lib/runtime/runtime.flags";
|
export const runtime_flags = "~lib/runtime/runtime.flags";
|
||||||
export const runtime_allocate = "~lib/util/runtime/allocate";
|
export const runtime_allocate = "~lib/util/runtime/allocate";
|
||||||
@ -486,8 +490,6 @@ export namespace BuiltinSymbols {
|
|||||||
export const runtime_register = "~lib/util/runtime/register";
|
export const runtime_register = "~lib/util/runtime/register";
|
||||||
export const runtime_discard = "~lib/util/runtime/discard";
|
export const runtime_discard = "~lib/util/runtime/discard";
|
||||||
export const runtime_makeArray = "~lib/util/runtime/makeArray";
|
export const runtime_makeArray = "~lib/util/runtime/makeArray";
|
||||||
export const gc_mark_roots = "~lib/runtime/__gc_mark_roots";
|
|
||||||
export const gc_mark_members = "~lib/runtime/__gc_mark_members";
|
|
||||||
|
|
||||||
// std/typedarray.ts
|
// std/typedarray.ts
|
||||||
export const Int8Array = "~lib/typedarray/Int8Array";
|
export const Int8Array = "~lib/typedarray/Int8Array";
|
||||||
@ -3630,7 +3632,7 @@ export function compileCall(
|
|||||||
|
|
||||||
// === Internal runtime =======================================================================
|
// === Internal runtime =======================================================================
|
||||||
|
|
||||||
case BuiltinSymbols.runtime_id: {
|
case BuiltinSymbols.idof: {
|
||||||
let type = evaluateConstantType(compiler, typeArguments, operands, reportNode);
|
let type = evaluateConstantType(compiler, typeArguments, operands, reportNode);
|
||||||
compiler.currentType = Type.u32;
|
compiler.currentType = Type.u32;
|
||||||
if (!type) return module.createUnreachable();
|
if (!type) return module.createUnreachable();
|
||||||
@ -3644,31 +3646,32 @@ export function compileCall(
|
|||||||
}
|
}
|
||||||
return module.createI32(classReference.id);
|
return module.createI32(classReference.id);
|
||||||
}
|
}
|
||||||
case BuiltinSymbols.gc_mark_roots: {
|
case BuiltinSymbols.visit_globals: {
|
||||||
if (
|
if (
|
||||||
checkTypeAbsent(typeArguments, reportNode, prototype) |
|
checkTypeAbsent(typeArguments, reportNode, prototype) |
|
||||||
checkArgsRequired(operands, 0, reportNode, compiler)
|
checkArgsRequired(operands, 1, reportNode, compiler) // cookie
|
||||||
) {
|
|
||||||
compiler.currentType = Type.void;
|
|
||||||
return module.createUnreachable();
|
|
||||||
}
|
|
||||||
compiler.needsGcMark = true;
|
|
||||||
compiler.currentType = Type.void;
|
|
||||||
return module.createCall(BuiltinSymbols.gc_mark_roots, null, NativeType.None);
|
|
||||||
}
|
|
||||||
case BuiltinSymbols.gc_mark_members: {
|
|
||||||
if (
|
|
||||||
checkTypeAbsent(typeArguments, reportNode, prototype) |
|
|
||||||
checkArgsRequired(operands, 2, reportNode, compiler)
|
|
||||||
) {
|
) {
|
||||||
compiler.currentType = Type.void;
|
compiler.currentType = Type.void;
|
||||||
return module.createUnreachable();
|
return module.createUnreachable();
|
||||||
}
|
}
|
||||||
let arg0 = compiler.compileExpression(operands[0], Type.u32, ConversionKind.IMPLICIT, WrapMode.NONE);
|
let arg0 = compiler.compileExpression(operands[0], Type.u32, ConversionKind.IMPLICIT, WrapMode.NONE);
|
||||||
let arg1 = compiler.compileExpression(operands[1], compiler.options.usizeType, ConversionKind.IMPLICIT, WrapMode.NONE);
|
compiler.needsVisitGlobals = true;
|
||||||
compiler.needsGcMark = true;
|
|
||||||
compiler.currentType = Type.void;
|
compiler.currentType = Type.void;
|
||||||
return module.createCall(BuiltinSymbols.gc_mark_members, [ arg0, arg1 ], NativeType.None);
|
return module.createCall(BuiltinSymbols.visit_globals, [ arg0 ], NativeType.None);
|
||||||
|
}
|
||||||
|
case BuiltinSymbols.visit_members: {
|
||||||
|
if (
|
||||||
|
checkTypeAbsent(typeArguments, reportNode, prototype) |
|
||||||
|
checkArgsRequired(operands, 2, reportNode, compiler) // ref, cookie
|
||||||
|
) {
|
||||||
|
compiler.currentType = Type.void;
|
||||||
|
return module.createUnreachable();
|
||||||
|
}
|
||||||
|
let arg0 = compiler.compileExpression(operands[0], compiler.options.usizeType, ConversionKind.IMPLICIT, WrapMode.NONE);
|
||||||
|
let arg1 = compiler.compileExpression(operands[1], Type.u32, ConversionKind.IMPLICIT, WrapMode.NONE);
|
||||||
|
compiler.needsVisitMembers = true;
|
||||||
|
compiler.currentType = Type.void;
|
||||||
|
return module.createCall(BuiltinSymbols.visit_members, [ arg0, arg1 ], NativeType.None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4051,15 +4054,15 @@ export function compileAbort(
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Compiles the `__gc_mark_roots` function. */
|
/** Compiles the `visit_globals` function. */
|
||||||
export function compileMarkRoots(compiler: Compiler): void {
|
export function compileVisitGlobals(compiler: Compiler): void {
|
||||||
var module = compiler.module;
|
var module = compiler.module;
|
||||||
var exprs = new Array<ExpressionRef>();
|
var exprs = new Array<ExpressionRef>();
|
||||||
var typeRef = compiler.ensureFunctionType(null, Type.void);
|
var typeRef = compiler.ensureFunctionType([ Type.u32 ], Type.void); // cookie
|
||||||
var nativeSizeType = compiler.options.nativeSizeType;
|
var nativeSizeType = compiler.options.nativeSizeType;
|
||||||
var markRef = assert(compiler.program.markRef);
|
var visitInstance = assert(compiler.program.visitInstance);
|
||||||
|
|
||||||
compiler.compileFunction(markRef);
|
compiler.compileFunction(visitInstance);
|
||||||
|
|
||||||
for (let element of compiler.program.elementsByName.values()) {
|
for (let element of compiler.program.elementsByName.values()) {
|
||||||
if (element.kind != ElementKind.GLOBAL) continue;
|
if (element.kind != ElementKind.GLOBAL) continue;
|
||||||
@ -4074,7 +4077,7 @@ export function compileMarkRoots(compiler: Compiler): void {
|
|||||||
let value = global.constantIntegerValue;
|
let value = global.constantIntegerValue;
|
||||||
if (i64_low(value) || i64_high(value)) {
|
if (i64_low(value) || i64_high(value)) {
|
||||||
exprs.push(
|
exprs.push(
|
||||||
module.createCall(markRef.internalName, [
|
module.createCall(visitInstance.internalName, [
|
||||||
compiler.options.isWasm64
|
compiler.options.isWasm64
|
||||||
? module.createI64(i64_low(value), i64_high(value))
|
? module.createI64(i64_low(value), i64_high(value))
|
||||||
: module.createI32(i64_low(value))
|
: module.createI32(i64_low(value))
|
||||||
@ -4084,35 +4087,35 @@ export function compileMarkRoots(compiler: Compiler): void {
|
|||||||
} else {
|
} else {
|
||||||
exprs.push(
|
exprs.push(
|
||||||
module.createIf(
|
module.createIf(
|
||||||
module.createTeeLocal(
|
module.createTeeLocal(1,
|
||||||
0,
|
|
||||||
module.createGetGlobal(global.internalName, nativeSizeType)
|
module.createGetGlobal(global.internalName, nativeSizeType)
|
||||||
),
|
),
|
||||||
module.createCall(markRef.internalName, [
|
module.createCall(visitInstance.internalName, [
|
||||||
module.createGetLocal(0, nativeSizeType)
|
module.createGetLocal(1, nativeSizeType), // tempRef != null
|
||||||
|
module.createGetLocal(0, NativeType.I32) // cookie
|
||||||
], NativeType.None)
|
], NativeType.None)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.addFunction(BuiltinSymbols.gc_mark_roots, typeRef, [ nativeSizeType ],
|
module.addFunction(BuiltinSymbols.visit_globals, typeRef, [ nativeSizeType ],
|
||||||
exprs.length
|
exprs.length
|
||||||
? module.createBlock(null, exprs)
|
? module.createBlock(null, exprs)
|
||||||
: module.createNop()
|
: module.createNop()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Compiles the `__gc_mark_members` function. */
|
/** Compiles the `visit_members` function. */
|
||||||
export function compileMarkMembers(compiler: Compiler): void {
|
export function compileVisitMembers(compiler: Compiler): void {
|
||||||
var program = compiler.program;
|
var program = compiler.program;
|
||||||
var module = compiler.module;
|
var module = compiler.module;
|
||||||
var usizeType = program.options.usizeType;
|
var usizeType = program.options.usizeType;
|
||||||
var nativeSizeType = usizeType.toNativeType();
|
var nativeSizeType = usizeType.toNativeType();
|
||||||
var nativeSizeSize = usizeType.byteSize;
|
var nativeSizeSize = usizeType.byteSize;
|
||||||
var ftype = compiler.ensureFunctionType([ Type.i32, usizeType ], Type.void);
|
var ftype = compiler.ensureFunctionType([ usizeType, Type.i32 ], Type.void); // ref, cookie
|
||||||
var managedClasses = program.managedClasses;
|
var managedClasses = program.managedClasses;
|
||||||
var markRef = assert(program.markRef);
|
var visitInstance = assert(program.visitInstance);
|
||||||
var names: string[] = [ "invalid" ]; // classId=0 is invalid
|
var names: string[] = [ "invalid" ]; // classId=0 is invalid
|
||||||
var blocks = new Array<ExpressionRef[]>();
|
var blocks = new Array<ExpressionRef[]>();
|
||||||
var lastId = 0;
|
var lastId = 0;
|
||||||
@ -4136,7 +4139,7 @@ export function compileMarkMembers(compiler: Compiler): void {
|
|||||||
}
|
}
|
||||||
blocks.push([
|
blocks.push([
|
||||||
module.createCall(traverseFunc.internalName, [
|
module.createCall(traverseFunc.internalName, [
|
||||||
module.createGetLocal(1, nativeSizeType)
|
module.createGetLocal(0, nativeSizeType)
|
||||||
], NativeType.None),
|
], NativeType.None),
|
||||||
module.createReturn()
|
module.createReturn()
|
||||||
]);
|
]);
|
||||||
@ -4160,19 +4163,16 @@ export function compileMarkMembers(compiler: Compiler): void {
|
|||||||
// if ($2 = value) FIELDCLASS~traverse($2)
|
// if ($2 = value) FIELDCLASS~traverse($2)
|
||||||
module.createIf(
|
module.createIf(
|
||||||
module.createTeeLocal(2,
|
module.createTeeLocal(2,
|
||||||
module.createLoad(
|
module.createLoad(nativeSizeSize, false,
|
||||||
nativeSizeSize,
|
module.createGetLocal(0, nativeSizeType),
|
||||||
false,
|
nativeSizeType, fieldOffset
|
||||||
module.createGetLocal(1, nativeSizeType),
|
|
||||||
nativeSizeType,
|
|
||||||
fieldOffset
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
module.createBlock(null, [
|
module.createBlock(null, [
|
||||||
module.createCall(markRef.internalName, [
|
module.createCall(visitInstance.internalName, [
|
||||||
module.createGetLocal(2, nativeSizeType)
|
module.createGetLocal(2, nativeSizeType)
|
||||||
], NativeType.None),
|
], NativeType.None),
|
||||||
module.createCall(BuiltinSymbols.gc_mark_members, [
|
module.createCall(BuiltinSymbols.visit_members, [
|
||||||
module.createI32(fieldClassId),
|
module.createI32(fieldClassId),
|
||||||
module.createGetLocal(2, nativeSizeType)
|
module.createGetLocal(2, nativeSizeType)
|
||||||
], NativeType.None)
|
], NativeType.None)
|
||||||
@ -4191,15 +4191,29 @@ export function compileMarkMembers(compiler: Compiler): void {
|
|||||||
|
|
||||||
var current: ExpressionRef;
|
var current: ExpressionRef;
|
||||||
if (blocks.length) {
|
if (blocks.length) {
|
||||||
// create a big switch mapping class ids to traversal logic
|
// create a big switch mapping runtime ids to traversal functions
|
||||||
current = module.createBlock(names[1], [
|
current = module.createBlock(names[1], [
|
||||||
module.createSwitch(names, "invalid", module.createGetLocal(0, NativeType.I32))
|
module.createSwitch(names, "invalid",
|
||||||
|
module.createLoad(nativeSizeSize, false,
|
||||||
|
nativeSizeType == NativeType.I64
|
||||||
|
? module.createBinary(BinaryOp.SubI64,
|
||||||
|
module.createGetLocal(0, nativeSizeType),
|
||||||
|
module.createI64(8)
|
||||||
|
)
|
||||||
|
: module.createBinary(BinaryOp.SubI32,
|
||||||
|
module.createGetLocal(0, nativeSizeType),
|
||||||
|
module.createI32(8) // rtId is at -8
|
||||||
|
),
|
||||||
|
NativeType.I32,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)
|
||||||
]);
|
]);
|
||||||
for (let i = 0, k = blocks.length; i < k; ++i) {
|
for (let i = 0, k = blocks.length; i < k; ++i) {
|
||||||
blocks[i].unshift(current);
|
blocks[i].unshift(current);
|
||||||
current = module.createBlock(i == k - 1 ? "invalid" : names[i + 2], blocks[i]);
|
current = module.createBlock(i == k - 1 ? "invalid" : names[i + 2], blocks[i]);
|
||||||
}
|
}
|
||||||
compiler.compileFunction(markRef);
|
compiler.compileFunction(visitInstance);
|
||||||
// wrap the function with a terminating unreachable
|
// wrap the function with a terminating unreachable
|
||||||
current = module.createBlock(null, [
|
current = module.createBlock(null, [
|
||||||
current,
|
current,
|
||||||
@ -4209,7 +4223,7 @@ export function compileMarkMembers(compiler: Compiler): void {
|
|||||||
// simplify
|
// simplify
|
||||||
current = module.createUnreachable();
|
current = module.createUnreachable();
|
||||||
}
|
}
|
||||||
module.addFunction(BuiltinSymbols.gc_mark_members, ftype, [ nativeSizeType ], current);
|
module.addFunction(BuiltinSymbols.visit_members, ftype, [ nativeSizeType ], current);
|
||||||
}
|
}
|
||||||
|
|
||||||
function typeToRuntimeFlags(type: Type, program: Program): RTTIFlags {
|
function typeToRuntimeFlags(type: Type, program: Program): RTTIFlags {
|
||||||
|
@ -182,13 +182,17 @@ export namespace CommonSymbols {
|
|||||||
export const abort = "abort";
|
export const abort = "abort";
|
||||||
export const pow = "pow";
|
export const pow = "pow";
|
||||||
export const mod = "mod";
|
export const mod = "mod";
|
||||||
export const allocate = "allocate";
|
export const alloc = "__alloc";
|
||||||
export const reallocate = "reallocate";
|
export const realloc = "__realloc";
|
||||||
export const register = "register";
|
export const free = "__free";
|
||||||
export const discard = "discard";
|
export const retain = "__retain";
|
||||||
export const newString = "newString";
|
export const release = "__release";
|
||||||
export const newArrayBuffer = "newArrayBuffer";
|
export const retainRelease = "__retainRelease";
|
||||||
export const newArray = "newArray";
|
export const collect = "__collect";
|
||||||
|
export const typeinfo = "__typeinfo";
|
||||||
|
export const instanceof_ = "__instanceof";
|
||||||
|
export const visit = "__visit";
|
||||||
|
export const allocArray = "__allocArray";
|
||||||
}
|
}
|
||||||
|
|
||||||
// shared
|
// shared
|
||||||
|
648
src/compiler.ts
648
src/compiler.ts
@ -7,8 +7,8 @@ import {
|
|||||||
BuiltinSymbols,
|
BuiltinSymbols,
|
||||||
compileCall as compileBuiltinCall,
|
compileCall as compileBuiltinCall,
|
||||||
compileAbort,
|
compileAbort,
|
||||||
compileMarkRoots,
|
compileVisitGlobals,
|
||||||
compileMarkMembers,
|
compileVisitMembers,
|
||||||
compileRTTI,
|
compileRTTI,
|
||||||
} from "./builtins";
|
} from "./builtins";
|
||||||
|
|
||||||
@ -281,8 +281,10 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
argcSet: FunctionRef = 0;
|
argcSet: FunctionRef = 0;
|
||||||
/** Whether HEAP_BASE is required. */
|
/** Whether HEAP_BASE is required. */
|
||||||
needsHeap: bool = false;
|
needsHeap: bool = false;
|
||||||
/** Indicates whether the __gc_mark_* functions must be generated. */
|
/** Indicates whether the __visit_globals function must be generated. */
|
||||||
needsGcMark: bool = false;
|
needsVisitGlobals: bool = false;
|
||||||
|
/** Indicated whether the __visit_members function must be generated. */
|
||||||
|
needsVisitMembers: bool = false;
|
||||||
/** Whether RTTI is required. */
|
/** Whether RTTI is required. */
|
||||||
needsRTTI: bool = false;
|
needsRTTI: bool = false;
|
||||||
|
|
||||||
@ -359,10 +361,8 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// compile gc features if utilized
|
// compile gc features if utilized
|
||||||
if (this.needsGcMark) {
|
if (this.needsVisitGlobals) compileVisitGlobals(this);
|
||||||
compileMarkRoots(this);
|
if (this.needsVisitMembers) compileVisitMembers(this);
|
||||||
compileMarkMembers(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// compile runtime type information
|
// compile runtime type information
|
||||||
module.removeGlobal(BuiltinSymbols.RTTI_BASE);
|
module.removeGlobal(BuiltinSymbols.RTTI_BASE);
|
||||||
@ -593,58 +593,20 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
var nativeType = type.toNativeType();
|
var nativeType = type.toNativeType();
|
||||||
var usizeType = this.options.usizeType;
|
var usizeType = this.options.usizeType;
|
||||||
var nativeSizeType = usizeType.toNativeType();
|
var nativeSizeType = usizeType.toNativeType();
|
||||||
|
var valueExpr: ExpressionRef;
|
||||||
if (type.isManaged(program)) {
|
if (type.isManaged(program)) {
|
||||||
let fn1: Function | null, fn2: Function | null;
|
let retainReleaseInstance = program.retainReleaseInstance;
|
||||||
let body: ExpressionRef[] = [];
|
this.compileFunction(retainReleaseInstance);
|
||||||
if (fn1 = program.linkRef) { // tracing
|
valueExpr = module.createCall(retainReleaseInstance.internalName, [
|
||||||
if (fn2 = program.unlinkRef) {
|
module.createGetLocal(1, nativeType), // newRef
|
||||||
body.push(
|
module.createLoad(type.byteSize, false, // oldRef
|
||||||
module.createCall(fn2.internalName, [
|
|
||||||
module.createGetLocal(2, nativeType),
|
|
||||||
module.createGetLocal(0, nativeSizeType)
|
|
||||||
], NativeType.None)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
body.push(
|
|
||||||
module.createCall(fn1.internalName, [
|
|
||||||
module.createGetLocal(1, nativeSizeType),
|
|
||||||
module.createGetLocal(0, nativeSizeType)
|
|
||||||
], NativeType.None)
|
|
||||||
);
|
|
||||||
} else if (fn1 = program.retainRef) { // arc
|
|
||||||
fn2 = assert(program.releaseRef);
|
|
||||||
body.push(
|
|
||||||
module.createCall(fn2.internalName, [
|
|
||||||
module.createGetLocal(2, nativeType)
|
|
||||||
], NativeType.None)
|
|
||||||
);
|
|
||||||
body.push(
|
|
||||||
module.createCall(fn1.internalName, [
|
|
||||||
module.createGetLocal(1, nativeSizeType)
|
|
||||||
], NativeType.None)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
module.addFunction(
|
|
||||||
name,
|
|
||||||
this.ensureFunctionType([ type ], Type.void, usizeType),
|
|
||||||
[ nativeType ],
|
|
||||||
module.createIf( // if (value != oldValue) release/retain ..
|
|
||||||
module.createBinary(
|
|
||||||
nativeSizeType == NativeType.I64
|
|
||||||
? BinaryOp.NeI64
|
|
||||||
: BinaryOp.NeI32,
|
|
||||||
module.createGetLocal(1, nativeType),
|
|
||||||
module.createTeeLocal(2,
|
|
||||||
module.createLoad(type.byteSize, false,
|
|
||||||
module.createGetLocal(0, nativeSizeType),
|
module.createGetLocal(0, nativeSizeType),
|
||||||
nativeType, field.memoryOffset
|
nativeType, field.memoryOffset
|
||||||
)
|
)
|
||||||
)
|
], nativeType);
|
||||||
),
|
|
||||||
module.createBlock(null, body)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
|
valueExpr = module.createGetLocal(1, nativeType);
|
||||||
|
}
|
||||||
module.addFunction(
|
module.addFunction(
|
||||||
name,
|
name,
|
||||||
this.ensureFunctionType([ type ], Type.void, usizeType),
|
this.ensureFunctionType([ type ], Type.void, usizeType),
|
||||||
@ -652,12 +614,11 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
module.createStore(
|
module.createStore(
|
||||||
type.byteSize,
|
type.byteSize,
|
||||||
module.createGetLocal(0, nativeSizeType),
|
module.createGetLocal(0, nativeSizeType),
|
||||||
module.createGetLocal(1, nativeType),
|
valueExpr,
|
||||||
nativeType,
|
nativeType,
|
||||||
field.memoryOffset
|
field.memoryOffset
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
|
||||||
module.addFunctionExport(name, name);
|
module.addFunctionExport(name, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -969,8 +930,11 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
module.addGlobal(internalName, nativeType, true, type.toNativeZero(module));
|
module.addGlobal(internalName, nativeType, true, type.toNativeZero(module));
|
||||||
if (type.isManaged(this.program) && this.program.retainRef) {
|
let program = this.program;
|
||||||
initExpr = this.makeInsertRef(initExpr, null, type.is(TypeFlags.NULLABLE));
|
if (type.isManaged(program)) {
|
||||||
|
let retainInstance = program.retainInstance;
|
||||||
|
this.compileFunction(retainInstance);
|
||||||
|
initExpr = module.createCall(retainInstance.internalName, [ initExpr ], nativeType);
|
||||||
}
|
}
|
||||||
this.currentBody.push(
|
this.currentBody.push(
|
||||||
module.createSetGlobal(internalName, initExpr)
|
module.createSetGlobal(internalName, initExpr)
|
||||||
@ -5230,58 +5194,103 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
return module.createUnreachable();
|
return module.createUnreachable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Makes an assignment to a local, possibly retaining and releasing affected references and keeping track of wrap and null states. */
|
||||||
makeLocalAssignment(
|
makeLocalAssignment(
|
||||||
local: Local,
|
local: Local,
|
||||||
valueExpr: ExpressionRef,
|
valueExpr: ExpressionRef,
|
||||||
tee: bool,
|
tee: bool,
|
||||||
possiblyNull: bool
|
possiblyNull: bool
|
||||||
): ExpressionRef {
|
): ExpressionRef {
|
||||||
// TBD: use REPLACE macro to keep track of managed refcounts? or can the compiler evaluate
|
var module = this.module;
|
||||||
// this statically in closed contexts like functions in order to safe the extra work?
|
var program = this.program;
|
||||||
var type = local.type;
|
var type = local.type;
|
||||||
assert(type != Type.void);
|
assert(type != Type.void);
|
||||||
|
var nativeType = type.toNativeType();
|
||||||
var flow = this.currentFlow;
|
var flow = this.currentFlow;
|
||||||
var localIndex = local.index;
|
var localIndex = local.index;
|
||||||
|
|
||||||
if (type.is(TypeFlags.SHORT | TypeFlags.INTEGER)) {
|
if (type.is(TypeFlags.SHORT | TypeFlags.INTEGER)) {
|
||||||
if (!flow.canOverflow(valueExpr, type)) flow.setLocalFlag(localIndex, LocalFlags.WRAPPED);
|
if (!flow.canOverflow(valueExpr, type)) flow.setLocalFlag(localIndex, LocalFlags.WRAPPED);
|
||||||
else flow.unsetLocalFlag(localIndex, LocalFlags.WRAPPED);
|
else flow.unsetLocalFlag(localIndex, LocalFlags.WRAPPED);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type.is(TypeFlags.NULLABLE)) {
|
if (type.is(TypeFlags.NULLABLE)) {
|
||||||
if (possiblyNull) flow.unsetLocalFlag(localIndex, LocalFlags.NONNULL);
|
if (possiblyNull) flow.unsetLocalFlag(localIndex, LocalFlags.NONNULL);
|
||||||
else flow.setLocalFlag(localIndex, LocalFlags.NONNULL);
|
else flow.setLocalFlag(localIndex, LocalFlags.NONNULL);
|
||||||
}
|
}
|
||||||
if (tee) {
|
|
||||||
|
// TODO: retain/release on each local assignment is costly in that increments and decrements
|
||||||
|
// easily involve cache misses when updating refcounts. ultimate goal should be to statically
|
||||||
|
// eliminate as many retain/release calls on locals as possible, i.e. where it can be proven
|
||||||
|
// that refcount doesn't change during the execution of a function, respectively refcount on
|
||||||
|
// arguments (which are locals) can be proven to remain the same from pre-call to post-call.
|
||||||
|
|
||||||
|
if (type.isManaged(program)) {
|
||||||
|
let retainReleaseInstance = program.retainReleaseInstance;
|
||||||
|
this.compileFunction(retainReleaseInstance);
|
||||||
|
if (tee) { // TEE(local = __retainRelease(value, local))
|
||||||
|
this.currentType = type;
|
||||||
|
return module.createTeeLocal(localIndex,
|
||||||
|
module.createCall(retainReleaseInstance.internalName, [
|
||||||
|
valueExpr,
|
||||||
|
module.createGetLocal(localIndex, nativeType)
|
||||||
|
], nativeType)
|
||||||
|
);
|
||||||
|
} else { // local = __retainRelease(value, local)
|
||||||
|
this.currentType = Type.void;
|
||||||
|
return module.createSetLocal(localIndex,
|
||||||
|
module.createCall(retainReleaseInstance.internalName, [
|
||||||
|
valueExpr,
|
||||||
|
module.createGetLocal(localIndex, nativeType)
|
||||||
|
], nativeType)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (tee) { // TEE(local = value)
|
||||||
this.currentType = type;
|
this.currentType = type;
|
||||||
return this.module.createTeeLocal(localIndex, valueExpr);
|
return this.module.createTeeLocal(localIndex, valueExpr);
|
||||||
} else {
|
} else { // local = value
|
||||||
this.currentType = Type.void;
|
this.currentType = Type.void;
|
||||||
return this.module.createSetLocal(localIndex, valueExpr);
|
return this.module.createSetLocal(localIndex, valueExpr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Makes an assignment to a global, possibly retaining and releasing affected references. */
|
||||||
makeGlobalAssignment(global: Global, valueExpr: ExpressionRef, tee: bool): ExpressionRef {
|
makeGlobalAssignment(global: Global, valueExpr: ExpressionRef, tee: bool): ExpressionRef {
|
||||||
var module = this.module;
|
var module = this.module;
|
||||||
|
var program = this.program;
|
||||||
var type = global.type;
|
var type = global.type;
|
||||||
assert(type != Type.void);
|
assert(type != Type.void);
|
||||||
var nativeType = type.toNativeType();
|
var nativeType = type.toNativeType();
|
||||||
|
|
||||||
// MANAGED (reference counting)
|
if (type.isManaged(program)) {
|
||||||
if (type.isManaged(this.program)) {
|
let retainReleaseInstance = program.retainReleaseInstance;
|
||||||
if (this.program.retainRef) {
|
this.compileFunction(retainReleaseInstance);
|
||||||
valueExpr = this.makeReplaceRef(
|
if (tee) { // (global = __retainRelease(t1 = value, global)), t1
|
||||||
|
let tempValue = this.currentFlow.getAndFreeTempLocal(type, true); // globals are wrapped
|
||||||
|
this.currentType = type;
|
||||||
|
return module.createBlock(null, [
|
||||||
|
module.createSetGlobal(global.internalName,
|
||||||
|
module.createCall(retainReleaseInstance.internalName, [
|
||||||
|
module.createTeeLocal(tempValue.index, valueExpr),
|
||||||
|
module.createGetGlobal(global.internalName, nativeType)
|
||||||
|
], nativeType)
|
||||||
|
),
|
||||||
|
module.createGetLocal(tempValue.index, nativeType)
|
||||||
|
], nativeType);
|
||||||
|
} else { // global = __retainRelease(value, global)
|
||||||
|
this.currentType = Type.void;
|
||||||
|
return module.createSetGlobal(global.internalName,
|
||||||
|
module.createCall(retainReleaseInstance.internalName, [
|
||||||
valueExpr,
|
valueExpr,
|
||||||
module.createGetGlobal(global.internalName, nativeType),
|
module.createGetGlobal(global.internalName, nativeType)
|
||||||
null,
|
], nativeType)
|
||||||
type.is(TypeFlags.NULLABLE)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// UNMANAGED
|
|
||||||
} else {
|
} else {
|
||||||
valueExpr = this.ensureSmallIntegerWrap(valueExpr, type); // global values must be wrapped
|
valueExpr = this.ensureSmallIntegerWrap(valueExpr, type); // globals must be wrapped
|
||||||
}
|
if (tee) { // (global = (t1 = value)), t1
|
||||||
|
|
||||||
if (tee) {
|
|
||||||
let tempValue = this.currentFlow.getAndFreeTempLocal(type, true);
|
let tempValue = this.currentFlow.getAndFreeTempLocal(type, true);
|
||||||
this.currentType = type;
|
this.currentType = type;
|
||||||
return module.createBlock(null, [
|
return module.createBlock(null, [
|
||||||
@ -5290,15 +5299,19 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
),
|
),
|
||||||
module.createGetLocal(tempValue.index, nativeType)
|
module.createGetLocal(tempValue.index, nativeType)
|
||||||
], nativeType);
|
], nativeType);
|
||||||
} else {
|
} else { // global = value
|
||||||
this.currentType = Type.void;
|
this.currentType = Type.void;
|
||||||
return this.module.createSetGlobal(global.internalName, valueExpr);
|
return module.createSetGlobal(global.internalName,
|
||||||
|
valueExpr
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Makes an assignment to a field, possibly retaining and releasing affected references. */
|
||||||
makeFieldAssignment(field: Field, valueExpr: ExpressionRef, thisExpr: ExpressionRef, tee: bool): ExpressionRef {
|
makeFieldAssignment(field: Field, valueExpr: ExpressionRef, thisExpr: ExpressionRef, tee: bool): ExpressionRef {
|
||||||
var program = this.program;
|
|
||||||
var module = this.module;
|
var module = this.module;
|
||||||
|
var program = this.program;
|
||||||
var flow = this.currentFlow;
|
var flow = this.currentFlow;
|
||||||
var fieldType = field.type;
|
var fieldType = field.type;
|
||||||
var nativeFieldType = fieldType.toNativeType();
|
var nativeFieldType = fieldType.toNativeType();
|
||||||
@ -5306,54 +5319,47 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
var thisType = (<Class>field.parent).type;
|
var thisType = (<Class>field.parent).type;
|
||||||
var nativeThisType = thisType.toNativeType();
|
var nativeThisType = thisType.toNativeType();
|
||||||
|
|
||||||
// MANAGED: this.field = replace(value, this.field)
|
if (fieldType.isManaged(program) && thisType.isManaged(program)) {
|
||||||
if (fieldType.isManaged(program)) {
|
|
||||||
let tempThis = flow.getTempLocal(thisType, false);
|
let tempThis = flow.getTempLocal(thisType, false);
|
||||||
let expr: ExpressionRef;
|
let retainReleaseInstance = program.retainReleaseInstance;
|
||||||
if (tee) { // tee value to a temp local and make it the block's result
|
this.compileFunction(retainReleaseInstance);
|
||||||
let tempValue = flow.getTempLocal(fieldType, !flow.canOverflow(valueExpr, fieldType));
|
if (tee) { // ((t1 = this).field = __retainRelease(t2 = value, t1.field)), t2
|
||||||
expr = module.createBlock(null, [
|
let tempValue = flow.getAndFreeTempLocal(fieldType, !flow.canOverflow(valueExpr, fieldType));
|
||||||
|
flow.freeTempLocal(tempThis);
|
||||||
|
this.currentType = fieldType;
|
||||||
|
return module.createBlock(null, [
|
||||||
module.createStore(fieldType.byteSize,
|
module.createStore(fieldType.byteSize,
|
||||||
module.createTeeLocal(tempThis.index, thisExpr),
|
module.createTeeLocal(tempThis.index, thisExpr),
|
||||||
this.makeReplaceRef(
|
module.createCall(retainReleaseInstance.internalName, [
|
||||||
module.createTeeLocal(tempValue.index, valueExpr),
|
module.createTeeLocal(tempValue.index, valueExpr), // newRef
|
||||||
module.createLoad(fieldType.byteSize, fieldType.is(TypeFlags.SIGNED),
|
module.createLoad(fieldType.byteSize, fieldType.is(TypeFlags.SIGNED), // oldRef
|
||||||
module.createGetLocal(tempThis.index, nativeThisType),
|
module.createGetLocal(tempThis.index, nativeThisType),
|
||||||
nativeFieldType, field.memoryOffset
|
nativeFieldType, field.memoryOffset
|
||||||
),
|
)
|
||||||
tempThis,
|
], nativeFieldType),
|
||||||
fieldType.is(TypeFlags.NULLABLE)
|
|
||||||
),
|
|
||||||
nativeFieldType, field.memoryOffset
|
nativeFieldType, field.memoryOffset
|
||||||
),
|
),
|
||||||
module.createGetLocal(tempValue.index, nativeFieldType)
|
module.createGetLocal(tempValue.index, nativeFieldType)
|
||||||
], nativeFieldType);
|
], nativeFieldType);
|
||||||
flow.freeTempLocal(tempValue);
|
} else { // (t1 = this).field = __retainRelease(value, t1.field)
|
||||||
this.currentType = fieldType;
|
flow.freeTempLocal(tempThis);
|
||||||
} else { // no need for a temp local
|
this.currentType = Type.void;
|
||||||
expr = module.createStore(fieldType.byteSize,
|
return module.createStore(fieldType.byteSize,
|
||||||
module.createTeeLocal(tempThis.index, thisExpr),
|
module.createTeeLocal(tempThis.index, thisExpr),
|
||||||
this.makeReplaceRef(
|
module.createCall(retainReleaseInstance.internalName, [
|
||||||
valueExpr,
|
valueExpr, // newRef
|
||||||
module.createLoad(fieldType.byteSize, fieldType.is(TypeFlags.SIGNED),
|
module.createLoad(fieldType.byteSize, fieldType.is(TypeFlags.SIGNED), // oldRef
|
||||||
module.createGetLocal(tempThis.index, nativeThisType),
|
module.createGetLocal(tempThis.index, nativeThisType),
|
||||||
nativeFieldType, field.memoryOffset
|
nativeFieldType, field.memoryOffset
|
||||||
),
|
)
|
||||||
tempThis,
|
], nativeFieldType),
|
||||||
fieldType.is(TypeFlags.NULLABLE)
|
|
||||||
),
|
|
||||||
nativeFieldType, field.memoryOffset
|
nativeFieldType, field.memoryOffset
|
||||||
);
|
);
|
||||||
this.currentType = Type.void;
|
|
||||||
}
|
}
|
||||||
flow.freeTempLocal(tempThis);
|
} else {
|
||||||
return expr;
|
if (tee) { // (this.field = (t1 = value)), t1
|
||||||
}
|
|
||||||
|
|
||||||
// UNMANAGED: this.field = value
|
|
||||||
if (tee) {
|
|
||||||
this.currentType = fieldType;
|
|
||||||
let tempValue = flow.getAndFreeTempLocal(fieldType, !flow.canOverflow(valueExpr, fieldType));
|
let tempValue = flow.getAndFreeTempLocal(fieldType, !flow.canOverflow(valueExpr, fieldType));
|
||||||
|
this.currentType = fieldType;
|
||||||
return module.createBlock(null, [
|
return module.createBlock(null, [
|
||||||
module.createStore(fieldType.byteSize,
|
module.createStore(fieldType.byteSize,
|
||||||
thisExpr,
|
thisExpr,
|
||||||
@ -5362,13 +5368,14 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
),
|
),
|
||||||
module.createGetLocal(tempValue.index, nativeFieldType)
|
module.createGetLocal(tempValue.index, nativeFieldType)
|
||||||
], nativeFieldType);
|
], nativeFieldType);
|
||||||
} else {
|
} else { // this.field = value
|
||||||
this.currentType = Type.void;
|
this.currentType = Type.void;
|
||||||
return module.createStore(fieldType.byteSize,
|
return module.createStore(fieldType.byteSize,
|
||||||
thisExpr,
|
thisExpr,
|
||||||
valueExpr,
|
valueExpr,
|
||||||
nativeFieldType, field.memoryOffset
|
nativeFieldType, field.memoryOffset
|
||||||
);
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7015,7 +7022,7 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
// otherwise allocate a new array header and make it wrap a copy of the static buffer
|
// otherwise allocate a new array header and make it wrap a copy of the static buffer
|
||||||
} else {
|
} else {
|
||||||
// makeArray(length, alignLog2, classId, staticBuffer)
|
// makeArray(length, alignLog2, classId, staticBuffer)
|
||||||
let expr = this.makeCallDirect(assert(program.makeArrayInstance), [
|
let expr = this.makeCallDirect(program.allocArrayInstance, [
|
||||||
module.createI32(length),
|
module.createI32(length),
|
||||||
program.options.isWasm64
|
program.options.isWasm64
|
||||||
? module.createI64(elementType.alignLog2)
|
? module.createI64(elementType.alignLog2)
|
||||||
@ -7044,12 +7051,11 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
var flow = this.currentFlow;
|
var flow = this.currentFlow;
|
||||||
var tempThis = flow.getTempLocal(arrayType, false);
|
var tempThis = flow.getTempLocal(arrayType, false);
|
||||||
var tempDataStart = flow.getTempLocal(arrayBufferInstance.type);
|
var tempDataStart = flow.getTempLocal(arrayBufferInstance.type);
|
||||||
var newArrayInstance = assert(program.makeArrayInstance);
|
|
||||||
var stmts = new Array<ExpressionRef>();
|
var stmts = new Array<ExpressionRef>();
|
||||||
// tempThis = makeArray(length, alignLog2, classId, source = 0)
|
// tempThis = makeArray(length, alignLog2, classId, source = 0)
|
||||||
stmts.push(
|
stmts.push(
|
||||||
module.createSetLocal(tempThis.index,
|
module.createSetLocal(tempThis.index,
|
||||||
this.makeCallDirect(newArrayInstance, [
|
this.makeCallDirect(program.allocArrayInstance, [
|
||||||
module.createI32(length),
|
module.createI32(length),
|
||||||
program.options.isWasm64
|
program.options.isWasm64
|
||||||
? module.createI64(elementType.alignLog2)
|
? module.createI64(elementType.alignLog2)
|
||||||
@ -7080,12 +7086,10 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
? this.compileExpression(valueExpression, elementType, ConversionKind.IMPLICIT, WrapMode.NONE)
|
? this.compileExpression(valueExpression, elementType, ConversionKind.IMPLICIT, WrapMode.NONE)
|
||||||
: elementType.toNativeZero(module);
|
: elementType.toNativeZero(module);
|
||||||
if (isManaged) {
|
if (isManaged) {
|
||||||
// value = link/retain(value[, tempThis])
|
// value = __retain(value)
|
||||||
valueExpr = this.makeInsertRef(
|
valueExpr = this.makeCallDirect(program.retainInstance, [
|
||||||
valueExpr,
|
valueExpr
|
||||||
tempThis,
|
], reportNode);
|
||||||
elementType.is(TypeFlags.NULLABLE)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// store<T>(tempData, value, immOffset)
|
// store<T>(tempData, value, immOffset)
|
||||||
stmts.push(
|
stmts.push(
|
||||||
@ -8290,35 +8294,17 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
var module = this.module;
|
var module = this.module;
|
||||||
var options = this.options;
|
var options = this.options;
|
||||||
var classType = classInstance.type;
|
var classType = classInstance.type;
|
||||||
|
|
||||||
if (!program.allocateMem) {
|
|
||||||
this.error(
|
|
||||||
DiagnosticCode.An_allocator_must_be_present_to_use_0,
|
|
||||||
reportNode.range, "new"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (classInstance.hasDecorator(DecoratorFlags.UNMANAGED)) {
|
|
||||||
// memory.allocate(sizeof<T>())
|
|
||||||
this.currentType = classType;
|
this.currentType = classType;
|
||||||
return this.makeCallDirect(assert(program.memoryAllocateInstance), [
|
return this.makeCallDirect(program.allocInstance, [
|
||||||
options.isWasm64
|
options.isWasm64
|
||||||
? module.createI64(classInstance.currentMemoryOffset)
|
? module.createI64(classInstance.currentMemoryOffset)
|
||||||
: module.createI32(classInstance.currentMemoryOffset)
|
: module.createI32(classInstance.currentMemoryOffset),
|
||||||
|
module.createI32(
|
||||||
|
classInstance.hasDecorator(DecoratorFlags.UNMANAGED)
|
||||||
|
? 0
|
||||||
|
: classInstance.id
|
||||||
|
)
|
||||||
], reportNode);
|
], reportNode);
|
||||||
|
|
||||||
} else {
|
|
||||||
// register(allocate(sizeof<T>()), classId)
|
|
||||||
this.currentType = classType;
|
|
||||||
return this.makeCallDirect(assert(program.registerInstance), [
|
|
||||||
this.makeCallDirect(assert(program.allocateInstance), [
|
|
||||||
options.isWasm64
|
|
||||||
? module.createI64(classInstance.currentMemoryOffset)
|
|
||||||
: module.createI32(classInstance.currentMemoryOffset)
|
|
||||||
], reportNode),
|
|
||||||
module.createI32(classInstance.id)
|
|
||||||
], reportNode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Makes the initializers for a class's fields. */
|
/** Makes the initializers for a class's fields. */
|
||||||
@ -8383,182 +8369,206 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
return stmts;
|
return stmts;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Wraps a reference in a `retain` call. Returns the reference if `tempLocal` is specified. */
|
// private makeRetainOrRelease(fn: Function, expr: ExpressionRef, possiblyNull: bool, tee: bool, tempIndex: i32 = -1): ExpressionRef {
|
||||||
makeRetain(valueExpr: ExpressionRef, tempIndex: i32 = -1): ExpressionRef {
|
// var module = this.module;
|
||||||
var module = this.module;
|
// var nativeSizeType = this.options.nativeSizeType;
|
||||||
var program = this.program;
|
// if (tee) {
|
||||||
var retainFn = assert(program.retainRef);
|
// if (possiblyNull) {
|
||||||
this.compileFunction(retainFn);
|
// assert(tempIndex >= 0);
|
||||||
if (tempIndex >= 0) {
|
// return module.createBlock(null, [
|
||||||
let nativeSizeType = this.options.nativeSizeType;
|
// module.createIf(
|
||||||
return module.createBlock(null, [
|
// module.createTeeLocal(tempIndex, expr),
|
||||||
module.createCall(retainFn.internalName, [
|
// module.createCall(fn.internalName, [
|
||||||
module.createTeeLocal(tempIndex, valueExpr)
|
// module.createGetLocal(tempIndex, nativeSizeType)
|
||||||
], NativeType.None),
|
// ], NativeType.None)
|
||||||
module.createGetLocal(tempIndex, nativeSizeType)
|
// ),
|
||||||
], nativeSizeType);
|
// module.createGetLocal(tempIndex, nativeSizeType)
|
||||||
} else {
|
// ], nativeSizeType);
|
||||||
return module.createCall(retainFn.internalName, [ valueExpr ], NativeType.None);
|
// } else {
|
||||||
}
|
// assert(tempIndex >= 0);
|
||||||
}
|
// return module.createBlock(null, [
|
||||||
|
// module.createCall(fn.internalName, [
|
||||||
|
// module.createTeeLocal(tempIndex, expr)
|
||||||
|
// ], NativeType.None),
|
||||||
|
// module.createGetLocal(tempIndex, nativeSizeType)
|
||||||
|
// ], nativeSizeType);
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// if (possiblyNull) {
|
||||||
|
// assert(tempIndex >= 0);
|
||||||
|
// return module.createIf(
|
||||||
|
// module.createTeeLocal(tempIndex, expr),
|
||||||
|
// module.createCall(fn.internalName, [
|
||||||
|
// module.createGetLocal(tempIndex, nativeSizeType)
|
||||||
|
// ], NativeType.None)
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// return module.createCall(fn.internalName, [ expr ], NativeType.None);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
/** Wraps a reference in `release` call. Returns the reference if `tempLocal` is specified. */
|
// /** Wraps an expression of a reference type in a `retain` call. */
|
||||||
makeRelease(valueExpr: ExpressionRef, tempIndex: i32 = -1): ExpressionRef {
|
// makeRetain(expr: ExpressionRef, possiblyNull: bool, tee: bool, tempIndex: i32 = -1): ExpressionRef {
|
||||||
var module = this.module;
|
// return this.makeRetainOrRelease(this.program.retainInstance, expr, possiblyNull, tee, tempIndex);
|
||||||
var program = this.program;
|
// }
|
||||||
var releaseFn = assert(program.releaseRef);
|
|
||||||
this.compileFunction(releaseFn);
|
|
||||||
if (tempIndex >= 0) {
|
|
||||||
let nativeSizeType = this.options.nativeSizeType;
|
|
||||||
return module.createBlock(null, [
|
|
||||||
module.createCall(releaseFn.internalName, [
|
|
||||||
module.createTeeLocal(tempIndex, valueExpr)
|
|
||||||
], NativeType.None),
|
|
||||||
module.createGetLocal(tempIndex, nativeSizeType)
|
|
||||||
], nativeSizeType);
|
|
||||||
} else {
|
|
||||||
return module.createCall(releaseFn.internalName, [ valueExpr ], NativeType.None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Wraps a new and an old reference in a sequence of `retain` and `release` calls. */
|
// /** Wraps an expression of a reference type in a `release` call. */
|
||||||
makeRetainRelease(newValueExpr: ExpressionRef, oldValueExpr: ExpressionRef, tempIndex: i32 = -1): ExpressionRef {
|
// makeRelease(expr: ExpressionRef, possiblyNull: bool, tee: bool, tempIndex: i32 = -1): ExpressionRef {
|
||||||
// TODO: checking `newValue != oldValue` significantly reduces strain on the roots buffer
|
// return this.makeRetainOrRelease(this.program.releaseInstance, expr, possiblyNull, tee, tempIndex);
|
||||||
// when cyclic structures may be immediately released but also requires a tempIndex. might
|
// }
|
||||||
// be worth to require a temp here. furthermore it might be worth to require it for retain
|
|
||||||
// and release as well so we can emit != null checks where necessary only?
|
|
||||||
var module = this.module;
|
|
||||||
if (tempIndex >= 0) {
|
|
||||||
let nativeSizeType = this.options.nativeSizeType;
|
|
||||||
return module.createBlock(null, [
|
|
||||||
this.makeRetain(module.createTeeLocal(tempIndex, newValueExpr)),
|
|
||||||
this.makeRelease(oldValueExpr),
|
|
||||||
module.createGetLocal(tempIndex, nativeSizeType)
|
|
||||||
], nativeSizeType);
|
|
||||||
} else {
|
|
||||||
return module.createBlock(null, [
|
|
||||||
this.makeRetain(newValueExpr),
|
|
||||||
this.makeRelease(oldValueExpr)
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Prepares the insertion of a reference into an _uninitialized_ parent using the GC interface. */
|
// /** Wraps a new and an old expression of a reference type in a `retain` call for the new and a `release` call for the old expression. */
|
||||||
makeInsertRef(
|
// makeRetainRelease(newExpr: ExpressionRef, oldExpr: ExpressionRef, possiblyNull: bool, tempIndexNew: i32, tempIndexOld: i32): ExpressionRef {
|
||||||
valueExpr: ExpressionRef,
|
// var module = this.module;
|
||||||
tempParent: Local | null,
|
// var nativeSizeType = this.options.nativeSizeType;
|
||||||
nullable: bool
|
// return module.createIf(
|
||||||
): ExpressionRef {
|
// module.createBinary(
|
||||||
var module = this.module;
|
// nativeSizeType == NativeType.I32
|
||||||
var program = this.program;
|
// ? BinaryOp.NeI32
|
||||||
var usizeType = this.options.usizeType;
|
// : BinaryOp.NeI64,
|
||||||
var nativeSizeType = this.options.nativeSizeType;
|
// module.createTeeLocal(tempIndexNew, newExpr),
|
||||||
var flow = this.currentFlow;
|
// module.createTeeLocal(tempIndexOld, oldExpr)
|
||||||
var tempValue = flow.getTempLocal(usizeType, false);
|
// ),
|
||||||
var handle: ExpressionRef;
|
// module.createBlock(null, [
|
||||||
var fn: Function | null;
|
// this.makeRetain(module.createGetLocal(tempIndexNew, nativeSizeType), possiblyNull, false, tempIndexNew),
|
||||||
if (fn = program.linkRef) { // tracing
|
|
||||||
handle = module.createCall(fn.internalName, [
|
|
||||||
module.createGetLocal(tempValue.index, nativeSizeType),
|
|
||||||
module.createGetLocal(assert(tempParent).index, nativeSizeType)
|
|
||||||
], NativeType.None);
|
|
||||||
} else if (fn = program.retainRef) { // arc
|
|
||||||
handle = module.createCall(fn.internalName, [
|
|
||||||
module.createGetLocal(tempValue.index, nativeSizeType)
|
|
||||||
], NativeType.None);
|
|
||||||
} else {
|
|
||||||
assert(false);
|
|
||||||
return module.createUnreachable();
|
|
||||||
}
|
|
||||||
flow.freeTempLocal(tempValue);
|
|
||||||
if (!this.compileFunction(fn)) return module.createUnreachable();
|
|
||||||
// {
|
|
||||||
// [if (value !== null)] link/retain(value[, parent])
|
|
||||||
// } -> value
|
|
||||||
return module.createBlock(null, [
|
|
||||||
module.createSetLocal(tempValue.index, valueExpr),
|
|
||||||
nullable
|
|
||||||
? module.createIf(
|
|
||||||
module.createGetLocal(tempValue.index, nativeSizeType),
|
|
||||||
handle
|
|
||||||
)
|
|
||||||
: handle,
|
|
||||||
module.createGetLocal(tempValue.index, nativeSizeType)
|
|
||||||
], nativeSizeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Prepares the replaces a reference hold by an _initialized_ parent using the GC interface. */
|
// ], NativeType.None)
|
||||||
makeReplaceRef(
|
// )
|
||||||
valueExpr: ExpressionRef,
|
// return module.createBlock(null, [
|
||||||
oldValueExpr: ExpressionRef,
|
// this.makeRetain(newExpr, possiblyNull, true, tempIndex),
|
||||||
tempParent: Local | null,
|
// this.makeRelease(oldExpr, possiblyNull, false), // wrong: reuses tempIndex if possiblyNull
|
||||||
nullable: bool
|
|
||||||
): ExpressionRef {
|
// ], nativeSizeType);
|
||||||
var module = this.module;
|
// }
|
||||||
var program = this.program;
|
|
||||||
var usizeType = this.options.usizeType;
|
// /** Wraps a new and an old reference in a sequence of `retain` and `release` calls. */
|
||||||
var nativeSizeType = this.options.nativeSizeType;
|
// makeRetainRelease(newValueExpr: ExpressionRef, oldValueExpr: ExpressionRef, tempIndex: i32, possiblyNull: bool = true): ExpressionRef {
|
||||||
var flow = this.currentFlow;
|
// var module = this.module;
|
||||||
var tempValue = flow.getTempLocal(usizeType, false);
|
// var nativeSizeType = this.options.nativeSizeType;
|
||||||
var tempOldValue = flow.getTempLocal(usizeType, false);
|
// return module.createBlock(null, [
|
||||||
var handleOld: ExpressionRef = 0;
|
// this.makeRetain(module.createTeeLocal(tempIndex, newValueExpr), possiblyNull ? tempIndex : -1),
|
||||||
var handleNew: ExpressionRef;
|
// this.makeRelease(oldValueExpr, possiblyNull ? tempIndex : -1),
|
||||||
var fn1: Function | null, fn2: Function | null;
|
// module.createGetLocal(tempIndex, nativeSizeType)
|
||||||
if (fn1 = program.linkRef) { // tracing
|
// ], nativeSizeType);
|
||||||
tempParent = assert(tempParent);
|
// }
|
||||||
if (fn2 = program.unlinkRef) {
|
|
||||||
handleOld = module.createCall(fn2.internalName, [
|
// /** Prepares the insertion of a reference into an _uninitialized_ parent using the GC interface. */
|
||||||
module.createGetLocal(tempOldValue.index, nativeSizeType),
|
// makeInsertRef(
|
||||||
module.createGetLocal(tempParent.index, nativeSizeType)
|
// valueExpr: ExpressionRef,
|
||||||
], NativeType.None);
|
// tempParent: Local | null,
|
||||||
}
|
// nullable: bool
|
||||||
handleNew = module.createCall(fn1.internalName, [
|
// ): ExpressionRef {
|
||||||
module.createGetLocal(tempValue.index, nativeSizeType),
|
// var module = this.module;
|
||||||
module.createGetLocal(tempParent.index, nativeSizeType)
|
// var program = this.program;
|
||||||
], NativeType.None);
|
// var usizeType = this.options.usizeType;
|
||||||
} else if (fn1 = program.retainRef) { // arc
|
// var nativeSizeType = this.options.nativeSizeType;
|
||||||
fn2 = assert(program.releaseRef);
|
// var flow = this.currentFlow;
|
||||||
handleOld = module.createCall(fn2.internalName, [
|
// var tempValue = flow.getTempLocal(usizeType, false);
|
||||||
module.createGetLocal(tempOldValue.index, nativeSizeType)
|
// var handle: ExpressionRef;
|
||||||
], NativeType.None);
|
// var fn: Function | null;
|
||||||
handleNew = module.createCall(fn1.internalName, [
|
// if (fn = program.linkRef) { // tracing
|
||||||
module.createGetLocal(tempValue.index, nativeSizeType)
|
// handle = module.createCall(fn.internalName, [
|
||||||
], NativeType.None);
|
// module.createGetLocal(tempValue.index, nativeSizeType),
|
||||||
} else {
|
// module.createGetLocal(assert(tempParent).index, nativeSizeType)
|
||||||
assert(false);
|
// ], NativeType.None);
|
||||||
return module.createUnreachable();
|
// } else if (fn = program.retainRef) { // arc
|
||||||
}
|
// handle = module.createCall(fn.internalName, [
|
||||||
flow.freeTempLocal(tempValue);
|
// module.createGetLocal(tempValue.index, nativeSizeType)
|
||||||
flow.freeTempLocal(tempOldValue);
|
// ], NativeType.None);
|
||||||
if (!this.compileFunction(fn1)) return module.createUnreachable();
|
// } else {
|
||||||
if (fn2 && !this.compileFunction(fn2)) return module.createUnreachable();
|
// assert(false);
|
||||||
// if (value != oldValue) {
|
// return module.createUnreachable();
|
||||||
// if (oldValue !== null) unlink/release(oldValue[, parent])
|
// }
|
||||||
// [if (value !== null)] link/retain(value[, parent])
|
// flow.freeTempLocal(tempValue);
|
||||||
// } -> value
|
// if (!this.compileFunction(fn)) return module.createUnreachable();
|
||||||
return module.createIf(
|
// // {
|
||||||
module.createBinary(nativeSizeType == NativeType.I32 ? BinaryOp.NeI32 : BinaryOp.NeI64,
|
// // [if (value !== null)] link/retain(value[, parent])
|
||||||
module.createTeeLocal(tempValue.index, valueExpr),
|
// // } -> value
|
||||||
module.createTeeLocal(tempOldValue.index, oldValueExpr)
|
// return module.createBlock(null, [
|
||||||
),
|
// module.createSetLocal(tempValue.index, valueExpr),
|
||||||
module.createBlock(null, [
|
// nullable
|
||||||
handleOld
|
// ? module.createIf(
|
||||||
? module.createIf(
|
// module.createGetLocal(tempValue.index, nativeSizeType),
|
||||||
module.createGetLocal(tempOldValue.index, nativeSizeType),
|
// handle
|
||||||
handleOld
|
// )
|
||||||
)
|
// : handle,
|
||||||
: module.createNop(),
|
// module.createGetLocal(tempValue.index, nativeSizeType)
|
||||||
nullable
|
// ], nativeSizeType);
|
||||||
? module.createIf(
|
// }
|
||||||
module.createGetLocal(tempValue.index, nativeSizeType),
|
|
||||||
handleNew
|
// /** Prepares the replaces a reference hold by an _initialized_ parent using the GC interface. */
|
||||||
)
|
// makeReplaceRef(
|
||||||
: handleNew,
|
// valueExpr: ExpressionRef,
|
||||||
module.createGetLocal(tempValue.index, nativeSizeType)
|
// oldValueExpr: ExpressionRef,
|
||||||
], nativeSizeType),
|
// tempParent: Local | null,
|
||||||
module.createGetLocal(tempValue.index, nativeSizeType)
|
// nullable: bool
|
||||||
);
|
// ): ExpressionRef {
|
||||||
}
|
// var module = this.module;
|
||||||
|
// var program = this.program;
|
||||||
|
// var usizeType = this.options.usizeType;
|
||||||
|
// var nativeSizeType = this.options.nativeSizeType;
|
||||||
|
// var flow = this.currentFlow;
|
||||||
|
// var tempValue = flow.getTempLocal(usizeType, false);
|
||||||
|
// var tempOldValue = flow.getTempLocal(usizeType, false);
|
||||||
|
// var handleOld: ExpressionRef = 0;
|
||||||
|
// var handleNew: ExpressionRef;
|
||||||
|
// var fn1: Function | null, fn2: Function | null;
|
||||||
|
// if (fn1 = program.linkRef) { // tracing
|
||||||
|
// tempParent = assert(tempParent);
|
||||||
|
// if (fn2 = program.unlinkRef) {
|
||||||
|
// handleOld = module.createCall(fn2.internalName, [
|
||||||
|
// module.createGetLocal(tempOldValue.index, nativeSizeType),
|
||||||
|
// module.createGetLocal(tempParent.index, nativeSizeType)
|
||||||
|
// ], NativeType.None);
|
||||||
|
// }
|
||||||
|
// handleNew = module.createCall(fn1.internalName, [
|
||||||
|
// module.createGetLocal(tempValue.index, nativeSizeType),
|
||||||
|
// module.createGetLocal(tempParent.index, nativeSizeType)
|
||||||
|
// ], NativeType.None);
|
||||||
|
// } else if (fn1 = program.retainRef) { // arc
|
||||||
|
// fn2 = assert(program.releaseRef);
|
||||||
|
// handleOld = module.createCall(fn2.internalName, [
|
||||||
|
// module.createGetLocal(tempOldValue.index, nativeSizeType)
|
||||||
|
// ], NativeType.None);
|
||||||
|
// handleNew = module.createCall(fn1.internalName, [
|
||||||
|
// module.createGetLocal(tempValue.index, nativeSizeType)
|
||||||
|
// ], NativeType.None);
|
||||||
|
// } else {
|
||||||
|
// assert(false);
|
||||||
|
// return module.createUnreachable();
|
||||||
|
// }
|
||||||
|
// flow.freeTempLocal(tempValue);
|
||||||
|
// flow.freeTempLocal(tempOldValue);
|
||||||
|
// if (!this.compileFunction(fn1)) return module.createUnreachable();
|
||||||
|
// if (fn2 && !this.compileFunction(fn2)) return module.createUnreachable();
|
||||||
|
// // if (value != oldValue) {
|
||||||
|
// // if (oldValue !== null) unlink/release(oldValue[, parent])
|
||||||
|
// // [if (value !== null)] link/retain(value[, parent])
|
||||||
|
// // } -> value
|
||||||
|
// return module.createIf(
|
||||||
|
// module.createBinary(nativeSizeType == NativeType.I32 ? BinaryOp.NeI32 : BinaryOp.NeI64,
|
||||||
|
// module.createTeeLocal(tempValue.index, valueExpr),
|
||||||
|
// module.createTeeLocal(tempOldValue.index, oldValueExpr)
|
||||||
|
// ),
|
||||||
|
// module.createBlock(null, [
|
||||||
|
// handleOld
|
||||||
|
// ? module.createIf(
|
||||||
|
// module.createGetLocal(tempOldValue.index, nativeSizeType),
|
||||||
|
// handleOld
|
||||||
|
// )
|
||||||
|
// : module.createNop(),
|
||||||
|
// nullable
|
||||||
|
// ? module.createIf(
|
||||||
|
// module.createGetLocal(tempValue.index, nativeSizeType),
|
||||||
|
// handleNew
|
||||||
|
// )
|
||||||
|
// : handleNew,
|
||||||
|
// module.createGetLocal(tempValue.index, nativeSizeType)
|
||||||
|
// ], nativeSizeType),
|
||||||
|
// module.createGetLocal(tempValue.index, nativeSizeType)
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
makeInstanceOfClass(
|
makeInstanceOfClass(
|
||||||
expr: ExpressionRef,
|
expr: ExpressionRef,
|
||||||
|
215
src/program.ts
215
src/program.ts
@ -349,58 +349,49 @@ export class Program extends DiagnosticEmitter {
|
|||||||
/** Managed classes contained in the program, by id. */
|
/** Managed classes contained in the program, by id. */
|
||||||
managedClasses: Map<i32,Class> = new Map();
|
managedClasses: Map<i32,Class> = new Map();
|
||||||
|
|
||||||
// runtime references
|
// standard references
|
||||||
|
|
||||||
/** ArrayBufferView reference. */
|
/** ArrayBufferView reference. */
|
||||||
arrayBufferViewInstance: Class | null = null;
|
arrayBufferViewInstance: Class;
|
||||||
/** ArrayBuffer instance reference. */
|
/** ArrayBuffer instance reference. */
|
||||||
arrayBufferInstance: Class | null = null;
|
arrayBufferInstance: Class;
|
||||||
/** Array prototype reference. */
|
/** Array prototype reference. */
|
||||||
arrayPrototype: ClassPrototype | null = null;
|
arrayPrototype: ClassPrototype;
|
||||||
/** Set prototype reference. */
|
/** Set prototype reference. */
|
||||||
setPrototype: ClassPrototype | null = null;
|
setPrototype: ClassPrototype;
|
||||||
/** Map prototype reference. */
|
/** Map prototype reference. */
|
||||||
mapPrototype: ClassPrototype | null = null;
|
mapPrototype: ClassPrototype;
|
||||||
/** Fixed array prototype reference. */
|
/** Fixed array prototype reference. */
|
||||||
fixedArrayPrototype: ClassPrototype | null = null;
|
fixedArrayPrototype: ClassPrototype;
|
||||||
/** String instance reference. */
|
/** String instance reference. */
|
||||||
stringInstance: Class | null = null;
|
stringInstance: Class;
|
||||||
/** Abort function reference, if present. */
|
/** Abort function reference, if present. */
|
||||||
abortInstance: Function | null = null;
|
abortInstance: Function;
|
||||||
|
|
||||||
/** Runtime allocation function. `allocate(payloadSize: usize): usize` */
|
// runtime references
|
||||||
allocateInstance: Function | null = null;
|
|
||||||
/** Memory allocation function. `memory.allocate(size)` */
|
|
||||||
memoryAllocateInstance: Function | null = null;
|
|
||||||
/** Runtime reallocation function. `reallocate(ref: usize, newPayloadSize: usize): usize` */
|
|
||||||
reallocateInstance: Function | null = null;
|
|
||||||
/** Runtime discard function. `discard(ref: usize): void` */
|
|
||||||
discardInstance: Function | null = null;
|
|
||||||
/** Runtime register function. `register(ref: usize, cid: u32): usize` */
|
|
||||||
registerInstance: Function | null = null;
|
|
||||||
/** Runtime make array function. `newArray(length: i32, alignLog2: usize, id: u32, source: usize = 0): usize` */
|
|
||||||
makeArrayInstance: Function | null = null;
|
|
||||||
/** Runtime instanceof function. */
|
|
||||||
instanceofInstance: Function | null = null;
|
|
||||||
/** Runtime flags function. */
|
|
||||||
flagsInstance: Function | null = null;
|
|
||||||
|
|
||||||
/** The kind of garbage collector being present. */
|
/** RT `__alloc(size: usize, id: u32): usize` */
|
||||||
collectorKind: CollectorKind = CollectorKind.NONE;
|
allocInstance: Function;
|
||||||
/** Memory allocation implementation, if present: `__mem_allocate(size: usize): usize` */
|
/** RT `__realloc(ref: usize, newSize: usize): usize` */
|
||||||
allocateMem: Function | null = null;
|
reallocInstance: Function;
|
||||||
/** Memory free implementation, if present: `__mem_free(ref: usize): void` */
|
/** RT `__free(ref: usize): void` */
|
||||||
freeMem: Function | null = null;
|
freeInstance: Function;
|
||||||
/** Reference link implementation, if present: `__ref_link(ref: usize, parentRef: usize): void` */
|
/** RT `__retain(ref: usize): usize` */
|
||||||
linkRef: Function | null = null;
|
retainInstance: Function;
|
||||||
/** Reference unlink implementation, if present: `__ref_unlink(ref: usize, parentRef: usize): void` */
|
/** RT `__release(ref: usize): void` */
|
||||||
unlinkRef: Function | null = null;
|
releaseInstance: Function;
|
||||||
/** Reference retain implementation, if present: `__ref_retain(ref: usize): void` */
|
/** RT `__retainRelease(newRef: usize, oldRef: usize): usize` */
|
||||||
retainRef: Function | null = null;
|
retainReleaseInstance: Function;
|
||||||
/** Reference release implementation, if present: `__ref_release(ref: usize): void` */
|
/** RT `__collect(): void` */
|
||||||
releaseRef: Function | null = null;
|
collectInstance: Function;
|
||||||
/** Reference mark implementation, if present: `__ref_mark(ref: usize): void` */
|
/** RT `__visit(ref: usize, cookie: u32): void` */
|
||||||
markRef: Function | null = null;
|
visitInstance: Function;
|
||||||
|
/** RT `__typeinfo(id: u32): RTTIFlags` */
|
||||||
|
typeinfoInstance: Function;
|
||||||
|
/** RT `__instanceof(ref: usize, superId: u32): bool` */
|
||||||
|
instanceofInstance: Function;
|
||||||
|
/** RT `__allocArray(length: i32, alignLog2: usize, id: u32, data: usize = 0): usize` */
|
||||||
|
allocArrayInstance: Function;
|
||||||
|
|
||||||
/** Next class id. */
|
/** Next class id. */
|
||||||
nextClassId: u32 = 1;
|
nextClassId: u32 = 1;
|
||||||
@ -805,104 +796,26 @@ export class Program extends DiagnosticEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// register library elements
|
// register stdlib components
|
||||||
{
|
this.arrayBufferViewInstance = this.requireClass(CommonSymbols.ArrayBufferView);
|
||||||
let element: Element | null;
|
this.arrayBufferInstance = this.requireClass(CommonSymbols.ArrayBuffer);
|
||||||
if (element = this.lookupGlobal(CommonSymbols.ArrayBufferView)) {
|
this.stringInstance = this.requireClass(CommonSymbols.String);
|
||||||
assert(element.kind == ElementKind.CLASS_PROTOTYPE);
|
this.arrayPrototype = <ClassPrototype>this.require(CommonSymbols.Array, ElementKind.CLASS_PROTOTYPE);
|
||||||
this.arrayBufferViewInstance = resolver.resolveClass(<ClassPrototype>element, null);
|
this.fixedArrayPrototype = <ClassPrototype>this.require(CommonSymbols.FixedArray, ElementKind.CLASS_PROTOTYPE);
|
||||||
}
|
this.setPrototype = <ClassPrototype>this.require(CommonSymbols.Set, ElementKind.CLASS_PROTOTYPE);
|
||||||
if (element = this.lookupGlobal(CommonSymbols.ArrayBuffer)) {
|
this.mapPrototype = <ClassPrototype>this.require(CommonSymbols.Map, ElementKind.CLASS_PROTOTYPE);
|
||||||
assert(element.kind == ElementKind.CLASS_PROTOTYPE);
|
this.abortInstance = this.requireFunction(CommonSymbols.abort);
|
||||||
this.arrayBufferInstance = resolver.resolveClass(<ClassPrototype>element, null);
|
this.allocInstance = this.requireFunction(CommonSymbols.alloc);
|
||||||
}
|
this.reallocInstance = this.requireFunction(CommonSymbols.realloc);
|
||||||
if (element = this.lookupGlobal(CommonSymbols.String)) {
|
this.freeInstance = this.requireFunction(CommonSymbols.free);
|
||||||
assert(element.kind == ElementKind.CLASS_PROTOTYPE);
|
this.retainInstance = this.requireFunction(CommonSymbols.retain);
|
||||||
this.stringInstance = resolver.resolveClass(<ClassPrototype>element, null);
|
this.releaseInstance = this.requireFunction(CommonSymbols.release);
|
||||||
}
|
this.retainReleaseInstance = this.requireFunction(CommonSymbols.retainRelease);
|
||||||
if (element = this.lookupGlobal(CommonSymbols.Array)) {
|
this.collectInstance = this.requireFunction(CommonSymbols.collect);
|
||||||
assert(element.kind == ElementKind.CLASS_PROTOTYPE);
|
this.typeinfoInstance = this.requireFunction(CommonSymbols.typeinfo);
|
||||||
this.arrayPrototype = <ClassPrototype>element;
|
this.instanceofInstance = this.requireFunction(CommonSymbols.instanceof_);
|
||||||
}
|
this.visitInstance = this.requireFunction(CommonSymbols.visit);
|
||||||
if (element = this.lookupGlobal(CommonSymbols.FixedArray)) {
|
this.allocArrayInstance = this.requireFunction(CommonSymbols.allocArray);
|
||||||
assert(element.kind == ElementKind.CLASS_PROTOTYPE);
|
|
||||||
this.fixedArrayPrototype = <ClassPrototype>element;
|
|
||||||
}
|
|
||||||
if (element = this.lookupGlobal(CommonSymbols.Set)) {
|
|
||||||
assert(element.kind == ElementKind.CLASS_PROTOTYPE);
|
|
||||||
this.setPrototype = <ClassPrototype>element;
|
|
||||||
}
|
|
||||||
if (element = this.lookupGlobal(CommonSymbols.Map)) {
|
|
||||||
assert(element.kind == ElementKind.CLASS_PROTOTYPE);
|
|
||||||
this.mapPrototype = <ClassPrototype>element;
|
|
||||||
}
|
|
||||||
if (element = this.lookupGlobal(CommonSymbols.abort)) {
|
|
||||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
|
||||||
this.abortInstance = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
|
||||||
}
|
|
||||||
if (element = this.lookupGlobal(BuiltinSymbols.runtime_allocate)) {
|
|
||||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
|
||||||
this.allocateInstance = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
|
||||||
}
|
|
||||||
if (element = this.lookupGlobal(BuiltinSymbols.memory_allocate)) {
|
|
||||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
|
||||||
this.memoryAllocateInstance = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
|
||||||
}
|
|
||||||
if (element = this.lookupGlobal(BuiltinSymbols.runtime_reallocate)) {
|
|
||||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
|
||||||
this.reallocateInstance = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
|
||||||
}
|
|
||||||
if (element = this.lookupGlobal(BuiltinSymbols.runtime_discard)) {
|
|
||||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
|
||||||
this.discardInstance = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
|
||||||
}
|
|
||||||
if (element = this.lookupGlobal(BuiltinSymbols.runtime_register)) {
|
|
||||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
|
||||||
this.registerInstance = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
|
||||||
}
|
|
||||||
if (element = this.lookupGlobal(BuiltinSymbols.runtime_makeArray)) {
|
|
||||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
|
||||||
this.makeArrayInstance = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
|
||||||
}
|
|
||||||
if (element = this.lookupGlobal(BuiltinSymbols.runtime_instanceof)) {
|
|
||||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
|
||||||
this.instanceofInstance = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
|
||||||
}
|
|
||||||
if (element = this.lookupGlobal(BuiltinSymbols.runtime_flags)) {
|
|
||||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
|
||||||
this.flagsInstance = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
|
||||||
}
|
|
||||||
// memory allocator interface
|
|
||||||
if (element = this.lookupGlobal("__mem_allocate")) {
|
|
||||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
|
||||||
this.allocateMem = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
|
||||||
element = assert(this.lookupGlobal("__mem_free"));
|
|
||||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
|
||||||
this.freeMem = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
|
||||||
}
|
|
||||||
// garbage collector interface
|
|
||||||
if (this.lookupGlobal("__ref_collect")) {
|
|
||||||
if (element = this.lookupGlobal("__ref_link")) {
|
|
||||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
|
||||||
this.linkRef = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
|
||||||
if (element = this.lookupGlobal("__ref_unlink")) {
|
|
||||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
|
||||||
this.unlinkRef = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
|
||||||
}
|
|
||||||
element = assert(this.lookupGlobal("__ref_mark"));
|
|
||||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
|
||||||
this.markRef = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
|
||||||
this.collectorKind = CollectorKind.TRACING;
|
|
||||||
} else if (element = this.lookupGlobal("__ref_retain")) {
|
|
||||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
|
||||||
this.retainRef = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
|
||||||
element = assert(this.lookupGlobal("__ref_release"));
|
|
||||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
|
||||||
this.releaseRef = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
|
||||||
this.collectorKind = CollectorKind.COUNTING;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mark module exports, i.e. to apply proper wrapping behavior on the boundaries
|
// mark module exports, i.e. to apply proper wrapping behavior on the boundaries
|
||||||
for (let file of this.filesByName.values()) {
|
for (let file of this.filesByName.values()) {
|
||||||
@ -912,6 +825,30 @@ export class Program extends DiagnosticEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Requires that a global library element of the specified kind is present and returns it. */
|
||||||
|
private require(name: string, kind: ElementKind): Element {
|
||||||
|
var element = this.lookupGlobal(name);
|
||||||
|
if (!element) throw new Error("missing " + name);
|
||||||
|
if (element.kind != kind) throw new Error("unexpected " + name);
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Requires that a non-generic global class is present and returns it. */
|
||||||
|
private requireClass(name: string): Class {
|
||||||
|
var prototype = this.require(name, ElementKind.CLASS_PROTOTYPE);
|
||||||
|
var resolved = this.resolver.resolveClass(<ClassPrototype>prototype, null);
|
||||||
|
if (!resolved) throw new Error("invalid " + name);
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Requires that a non-generic global function is present and returns it. */
|
||||||
|
private requireFunction(name: string): Function {
|
||||||
|
var prototype = this.require(name, ElementKind.FUNCTION_PROTOTYPE);
|
||||||
|
var resolved = this.resolver.resolveFunction(<FunctionPrototype>prototype, null);
|
||||||
|
if (!resolved) throw new Error("invalid " + name);
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
/** Marks an element and its children as a module export. */
|
/** Marks an element and its children as a module export. */
|
||||||
private markModuleExport(element: Element): void {
|
private markModuleExport(element: Element): void {
|
||||||
element.set(CommonFlags.MODULE_EXPORT);
|
element.set(CommonFlags.MODULE_EXPORT);
|
||||||
|
@ -153,12 +153,9 @@ export class Type {
|
|||||||
|
|
||||||
/** Tests if this is a managed type that needs GC hooks. */
|
/** Tests if this is a managed type that needs GC hooks. */
|
||||||
isManaged(program: Program): bool {
|
isManaged(program: Program): bool {
|
||||||
if (program.collectorKind != CollectorKind.NONE) {
|
var classReference = this.classReference;
|
||||||
let classReference = this.classReference;
|
|
||||||
return classReference !== null && !classReference.hasDecorator(DecoratorFlags.UNMANAGED);
|
return classReference !== null && !classReference.hasDecorator(DecoratorFlags.UNMANAGED);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Tests if this is a class type explicitly annotated as unmanaged. */
|
/** Tests if this is a class type explicitly annotated as unmanaged. */
|
||||||
get isUnmanaged(): bool {
|
get isUnmanaged(): bool {
|
||||||
|
@ -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.
|
|
@ -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;
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
3
std/assembly/allocator/index.d.ts
vendored
3
std/assembly/allocator/index.d.ts
vendored
@ -1,3 +0,0 @@
|
|||||||
declare function __mem_allocate(size: usize): usize;
|
|
||||||
declare function __mem_free(ref: usize): void;
|
|
||||||
declare function __mem_reset(): void;
|
|
@ -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);
|
|
||||||
}
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +1,21 @@
|
|||||||
/// <reference path="./collector/index.d.ts" />
|
/// <reference path="./rt/index.d.ts" />
|
||||||
|
|
||||||
import { MAX_BYTELENGTH, allocate, reallocate, discard, register, NEWARRAY } from "./util/runtime";
|
import { BLOCK_MAXSIZE } from "./rt/common";
|
||||||
import { COMPARATOR, SORT } from "./util/sort";
|
import { COMPARATOR, SORT } from "./util/sort";
|
||||||
import { __runtime_id, __gc_mark_members } from "./runtime";
|
|
||||||
import { ArrayBuffer, ArrayBufferView } from "./arraybuffer";
|
import { ArrayBuffer, ArrayBufferView } from "./arraybuffer";
|
||||||
import { itoa, dtoa, itoa_stream, dtoa_stream, MAX_DOUBLE_LENGTH } from "./util/number";
|
import { itoa, dtoa, itoa_stream, dtoa_stream, MAX_DOUBLE_LENGTH } from "./util/number";
|
||||||
import { isArray as builtin_isArray } from "./builtins";
|
import { idof, isArray as builtin_isArray } from "./builtins";
|
||||||
import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH, E_EMPTYARRAY, E_HOLEYARRAY } from "./util/error";
|
import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH, E_EMPTYARRAY, E_HOLEYARRAY } from "./util/error";
|
||||||
|
|
||||||
/** Ensures that the given array has _at least_ the specified capacity. */
|
/** Ensures that the given array has _at least_ the specified capacity. */
|
||||||
function ensureCapacity(array: ArrayBufferView, minCapacity: i32, alignLog2: u32): void {
|
function ensureCapacity(array: ArrayBufferView, minCapacity: i32, alignLog2: u32): void {
|
||||||
if (<u32>minCapacity > <u32>array.dataLength >>> alignLog2) {
|
if (<u32>minCapacity > <u32>array.dataLength >>> alignLog2) {
|
||||||
if (<u32>minCapacity > <u32>(MAX_BYTELENGTH >>> alignLog2)) throw new RangeError(E_INVALIDLENGTH);
|
if (<u32>minCapacity > <u32>(BLOCK_MAXSIZE >>> alignLog2)) throw new RangeError(E_INVALIDLENGTH);
|
||||||
let oldData = array.data;
|
let oldData = array.data;
|
||||||
let newByteLength = minCapacity << alignLog2;
|
let newByteLength = minCapacity << alignLog2;
|
||||||
let newData = reallocate(changetype<usize>(oldData), <usize>newByteLength); // registers on move
|
let newData = __realloc(changetype<usize>(oldData), <usize>newByteLength); // registers on move
|
||||||
if (newData !== changetype<usize>(oldData)) {
|
if (newData !== changetype<usize>(oldData)) {
|
||||||
array.data = changetype<ArrayBuffer>(newData); // links
|
array.data = changetype<ArrayBuffer>(newData); // retains
|
||||||
array.dataStart = newData;
|
array.dataStart = newData;
|
||||||
}
|
}
|
||||||
array.dataLength = newByteLength;
|
array.dataLength = newByteLength;
|
||||||
@ -41,11 +40,11 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static create<T>(capacity: i32 = 0): Array<T> {
|
static create<T>(capacity: i32 = 0): Array<T> {
|
||||||
if (<u32>capacity > <u32>MAX_BYTELENGTH >>> alignof<T>()) throw new RangeError(E_INVALIDLENGTH);
|
if (<u32>capacity > <u32>BLOCK_MAXSIZE >>> alignof<T>()) throw new RangeError(E_INVALIDLENGTH);
|
||||||
var array = NEWARRAY<T>(capacity);
|
var array = __allocArray(capacity, alignof<T>(), idof<T[]>());
|
||||||
array.length_ = 0; // safe even if T is a non-nullable reference
|
changetype<Array<T>>(array).length_ = 0; // safe even if T is a non-nullable reference
|
||||||
memory.fill(array.dataStart, 0, array.dataLength);
|
memory.fill(changetype<ArrayBufferView>(array).dataStart, 0, changetype<ArrayBufferView>(array).dataLength);
|
||||||
return array;
|
return changetype<Array<T>>(array); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(length: i32 = 0) {
|
constructor(length: i32 = 0) {
|
||||||
@ -119,27 +118,7 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
@operator("{}=") private __unchecked_set(index: i32, value: T): void {
|
@operator("{}=") private __unchecked_set(index: i32, value: T): void {
|
||||||
if (isManaged<T>()) {
|
if (isManaged<T>()) {
|
||||||
let offset = this.dataStart + (<usize>index << alignof<T>());
|
let offset = this.dataStart + (<usize>index << alignof<T>());
|
||||||
let oldValue = load<T>(offset);
|
store<usize>(offset, __retainRelease(changetype<usize>(value), load<usize>(offset)));
|
||||||
if (value !== oldValue) {
|
|
||||||
store<T>(offset, value);
|
|
||||||
if (isNullable<T>()) {
|
|
||||||
if (isDefined(__ref_link)) {
|
|
||||||
if (isDefined(__ref_unlink)) if (oldValue !== null) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
|
|
||||||
if (value !== null) __ref_link(changetype<usize>(value), changetype<usize>(this));
|
|
||||||
} else if (isDefined(__ref_retain)) {
|
|
||||||
if (oldValue !== null) __ref_release(changetype<usize>(oldValue));
|
|
||||||
if (value !== null) __ref_retain(changetype<usize>(value));
|
|
||||||
} else assert(false);
|
|
||||||
} else {
|
|
||||||
if (isDefined(__ref_link)) {
|
|
||||||
if (isDefined(__ref_unlink)) if (oldValue !== null) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
|
|
||||||
__ref_link(changetype<usize>(value), changetype<usize>(this));
|
|
||||||
} else if (__ref_retain) {
|
|
||||||
if (oldValue !== null) __ref_release(changetype<usize>(oldValue));
|
|
||||||
__ref_retain(changetype<usize>(value));
|
|
||||||
} else assert(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
store<T>(this.dataStart + (<usize>index << alignof<T>()), value);
|
store<T>(this.dataStart + (<usize>index << alignof<T>()), value);
|
||||||
}
|
}
|
||||||
@ -201,27 +180,7 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
ensureCapacity(this, newLength, alignof<T>());
|
ensureCapacity(this, newLength, alignof<T>());
|
||||||
if (isManaged<T>()) {
|
if (isManaged<T>()) {
|
||||||
let offset = this.dataStart + (<usize>length << alignof<T>());
|
let offset = this.dataStart + (<usize>length << alignof<T>());
|
||||||
let oldValue = load<T>(offset);
|
store<usize>(offset, __retainRelease(changetype<usize>(value), load<usize>(offset)));
|
||||||
if (oldValue !== value) {
|
|
||||||
store<T>(offset, value);
|
|
||||||
if (isNullable<T>()) {
|
|
||||||
if (isDefined(__ref_link)) {
|
|
||||||
if (value !== null) __ref_link(changetype<usize>(value), changetype<usize>(this));
|
|
||||||
if (isDefined(__ref_unlink)) if (oldValue !== null) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
|
|
||||||
} else if (__ref_retain) {
|
|
||||||
if (oldValue !== null) __ref_retain(changetype<usize>(value));
|
|
||||||
if (value !== null) __ref_release(changetype<usize>(oldValue));
|
|
||||||
} else assert(false);
|
|
||||||
} else {
|
|
||||||
if (isDefined(__ref_link)) {
|
|
||||||
__ref_link(changetype<usize>(value), changetype<usize>(this));
|
|
||||||
if (isDefined(__ref_unlink)) if (oldValue !== null) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
|
|
||||||
} else if (__ref_retain) {
|
|
||||||
__ref_retain(changetype<usize>(value));
|
|
||||||
if (oldValue !== null) __ref_release(changetype<usize>(oldValue));
|
|
||||||
} else assert(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
store<T>(this.dataStart + (<usize>length << alignof<T>()), value);
|
store<T>(this.dataStart + (<usize>length << alignof<T>()), value);
|
||||||
}
|
}
|
||||||
@ -233,50 +192,28 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
var thisLen = this.length_;
|
var thisLen = this.length_;
|
||||||
var otherLen = select(0, other.length_, other === null);
|
var otherLen = select(0, other.length_, other === null);
|
||||||
var outLen = thisLen + otherLen;
|
var outLen = thisLen + otherLen;
|
||||||
if (<u32>outLen > <u32>MAX_BYTELENGTH >>> alignof<T>()) throw new Error(E_INVALIDLENGTH);
|
if (<u32>outLen > <u32>BLOCK_MAXSIZE >>> alignof<T>()) throw new Error(E_INVALIDLENGTH);
|
||||||
var out = NEWARRAY<T>(outLen);
|
var out = __allocArray(outLen, alignof<T>(), idof<Array<T>>());
|
||||||
var outStart = out.dataStart;
|
var outStart = changetype<ArrayBufferView>(out).dataStart;
|
||||||
var thisSize = <usize>thisLen << alignof<T>();
|
var thisSize = <usize>thisLen << alignof<T>();
|
||||||
if (isManaged<T>()) {
|
if (isManaged<T>()) {
|
||||||
let thisStart = this.dataStart;
|
let thisStart = this.dataStart;
|
||||||
for (let offset: usize = 0; offset < thisSize; offset += sizeof<T>()) {
|
for (let offset: usize = 0; offset < thisSize; offset += sizeof<T>()) {
|
||||||
let ref = load<usize>(thisStart + offset);
|
let ref = load<usize>(thisStart + offset);
|
||||||
store<usize>(outStart + offset, ref);
|
store<usize>(outStart + offset, __retain(ref));
|
||||||
if (isNullable<T>()) {
|
|
||||||
if (ref) {
|
|
||||||
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(this));
|
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(ref);
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(this));
|
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(ref);
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
outStart += thisSize;
|
outStart += thisSize;
|
||||||
let otherStart = other.dataStart;
|
let otherStart = other.dataStart;
|
||||||
let otherSize = <usize>otherLen << alignof<T>();
|
let otherSize = <usize>otherLen << alignof<T>();
|
||||||
for (let offset: usize = 0; offset < otherSize; offset += sizeof<T>()) {
|
for (let offset: usize = 0; offset < otherSize; offset += sizeof<T>()) {
|
||||||
let ref = load<usize>(otherStart + offset);
|
let ref = load<usize>(otherStart + offset);
|
||||||
store<usize>(outStart + offset, ref);
|
store<usize>(outStart + offset, __retain(ref));
|
||||||
if (isNullable<T>()) {
|
|
||||||
if (ref) {
|
|
||||||
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(this));
|
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(ref);
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(this));
|
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(ref);
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
memory.copy(outStart, this.dataStart, thisSize);
|
memory.copy(outStart, this.dataStart, thisSize);
|
||||||
memory.copy(outStart + thisSize, other.dataStart, <usize>otherLen << alignof<T>());
|
memory.copy(outStart + thisSize, other.dataStart, <usize>otherLen << alignof<T>());
|
||||||
}
|
}
|
||||||
return out;
|
return changetype<Array<T>>(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
copyWithin(target: i32, start: i32, end: i32 = i32.MAX_VALUE): this {
|
copyWithin(target: i32, start: i32, end: i32 = i32.MAX_VALUE): this {
|
||||||
@ -322,38 +259,27 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
|
|
||||||
map<U>(callbackfn: (value: T, index: i32, array: Array<T>) => U): Array<U> {
|
map<U>(callbackfn: (value: T, index: i32, array: Array<T>) => U): Array<U> {
|
||||||
var length = this.length_;
|
var length = this.length_;
|
||||||
var out = NEWARRAY<U>(length);
|
var out = __allocArray(length, alignof<U>(), idof<Array<U>>());
|
||||||
var outStart = out.dataStart;
|
var outStart = changetype<ArrayBufferView>(out).dataStart;
|
||||||
for (let index = 0; index < min(length, this.length_); ++index) {
|
for (let index = 0; index < min(length, this.length_); ++index) {
|
||||||
let value = load<T>(this.dataStart + (<usize>index << alignof<T>()));
|
let result = callbackfn(load<T>(this.dataStart + (<usize>index << alignof<T>())), index, this); // retains
|
||||||
if (isManaged<U>()) {
|
if (isManaged<U>()) {
|
||||||
let ref = changetype<usize>(callbackfn(value, index, this));
|
store<usize>(outStart + (<usize>index << alignof<U>()), __retain(changetype<usize>(result)));
|
||||||
store<usize>(outStart + (<usize>index << alignof<U>()), ref);
|
|
||||||
if (isNullable<T>()) {
|
|
||||||
if (ref) {
|
|
||||||
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(out));
|
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(ref);
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(out));
|
store<U>(outStart + (<usize>index << alignof<U>()), result);
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(ref);
|
|
||||||
else assert(false);
|
|
||||||
}
|
}
|
||||||
} else {
|
// releases result
|
||||||
store<U>(outStart + (<usize>index << alignof<U>()), callbackfn(value, index, this));
|
|
||||||
}
|
}
|
||||||
}
|
return changetype<Array<U>>(out); // retains
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filter(callbackfn: (value: T, index: i32, array: Array<T>) => bool): Array<T> {
|
filter(callbackfn: (value: T, index: i32, array: Array<T>) => bool): Array<T> {
|
||||||
var result = NEWARRAY<T>(0);
|
var result = __allocArray(0, alignof<T>(), idof<Array<T>>());
|
||||||
for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) {
|
for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) {
|
||||||
let value = load<T>(this.dataStart + (<usize>index << alignof<T>()));
|
let value = load<T>(this.dataStart + (<usize>index << alignof<T>()));
|
||||||
if (callbackfn(value, index, this)) result.push(value);
|
if (callbackfn(value, index, this)) changetype<Array<T>>(result).push(value);
|
||||||
}
|
}
|
||||||
return result;
|
return changetype<Array<T>>(result); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
reduce<U>(
|
reduce<U>(
|
||||||
@ -413,19 +339,10 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
dataStart,
|
dataStart,
|
||||||
<usize>(newLength - 1) << alignof<T>()
|
<usize>(newLength - 1) << alignof<T>()
|
||||||
);
|
);
|
||||||
store<T>(dataStart, element);
|
|
||||||
if (isManaged<T>()) {
|
if (isManaged<T>()) {
|
||||||
if (isNullable<T>()) {
|
store<usize>(dataStart, __retain(changetype<usize>(element)));
|
||||||
if (element !== null) {
|
|
||||||
if (isDefined(__ref_link)) __ref_link(changetype<usize>(element), changetype<usize>(this));
|
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(element));
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (isDefined(__ref_link)) __ref_link(changetype<usize>(element), changetype<usize>(this));
|
store<T>(dataStart, element);
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(element));
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.length_ = newLength;
|
this.length_ = newLength;
|
||||||
return newLength;
|
return newLength;
|
||||||
@ -436,61 +353,41 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
begin = begin < 0 ? max(begin + length, 0) : min(begin, length);
|
begin = begin < 0 ? max(begin + length, 0) : min(begin, length);
|
||||||
end = end < 0 ? max(end + length, 0) : min(end , length);
|
end = end < 0 ? max(end + length, 0) : min(end , length);
|
||||||
length = max(end - begin, 0);
|
length = max(end - begin, 0);
|
||||||
var slice = NEWARRAY<T>(length);
|
var slice = __allocArray(length, alignof<T>(), idof<Array<T>>());
|
||||||
var sliceBase = slice.dataStart;
|
var sliceBase = changetype<ArrayBufferView>(slice).dataStart;
|
||||||
var thisBase = this.dataStart + (<usize>begin << alignof<T>());
|
var thisBase = this.dataStart + (<usize>begin << alignof<T>());
|
||||||
if (isManaged<T>()) {
|
if (isManaged<T>()) {
|
||||||
let off = <usize>0;
|
let off = <usize>0;
|
||||||
let end = <usize>length << alignof<usize>();
|
let end = <usize>length << alignof<usize>();
|
||||||
while (off < end) {
|
while (off < end) {
|
||||||
let ref = load<usize>(thisBase + off);
|
let ref = load<usize>(thisBase + off);
|
||||||
store<usize>(sliceBase + off, ref);
|
store<usize>(sliceBase + off, __retain(ref));
|
||||||
if (isNullable<T>()) {
|
|
||||||
if (ref) {
|
|
||||||
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(slice));
|
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(ref);
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(slice));
|
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(ref);
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
off += sizeof<usize>();
|
off += sizeof<usize>();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
memory.copy(sliceBase, thisBase, length << alignof<T>());
|
memory.copy(sliceBase, thisBase, length << alignof<T>());
|
||||||
}
|
}
|
||||||
return slice;
|
return changetype<Array<T>>(slice); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
splice(start: i32, deleteCount: i32 = i32.MAX_VALUE): Array<T> {
|
splice(start: i32, deleteCount: i32 = i32.MAX_VALUE): Array<T> {
|
||||||
var length = this.length_;
|
var length = this.length_;
|
||||||
start = start < 0 ? max<i32>(length + start, 0) : min<i32>(start, length);
|
start = start < 0 ? max<i32>(length + start, 0) : min<i32>(start, length);
|
||||||
deleteCount = max<i32>(min<i32>(deleteCount, length - start), 0);
|
deleteCount = max<i32>(min<i32>(deleteCount, length - start), 0);
|
||||||
var result = NEWARRAY<T>(deleteCount);
|
var result = __allocArray(deleteCount, alignof<T>(), idof<Array<T>>());
|
||||||
var resultStart = result.dataStart;
|
var resultStart = changetype<ArrayBufferView>(result).dataStart;
|
||||||
var thisStart = this.dataStart;
|
var thisStart = this.dataStart;
|
||||||
var thisBase = thisStart + (<usize>start << alignof<T>());
|
var thisBase = thisStart + (<usize>start << alignof<T>());
|
||||||
if (isManaged<T>()) {
|
if (isManaged<T>()) {
|
||||||
for (let i = 0; i < deleteCount; ++i) {
|
for (let i = 0; i < deleteCount; ++i) {
|
||||||
let ref = load<usize>(thisBase + (<usize>i << alignof<T>()));
|
store<usize>(resultStart + (<usize>i << alignof<T>()),
|
||||||
store<usize>(resultStart + (<usize>i << alignof<T>()), ref);
|
load<usize>(thisBase + (<usize>i << alignof<T>()))
|
||||||
if (isDefined(__ref_link)) {
|
);
|
||||||
if (isNullable<T>()) {
|
// element is moved, so refcount doesn't change
|
||||||
if (ref) {
|
|
||||||
if (isDefined(__ref_unlink)) __ref_unlink(ref, changetype<usize>(this));
|
|
||||||
__ref_link(ref, changetype<usize>(result));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isDefined(__ref_unlink)) __ref_unlink(ref, changetype<usize>(this));
|
|
||||||
__ref_link(ref, changetype<usize>(result));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
memory.copy(
|
memory.copy(
|
||||||
result.dataStart,
|
resultStart,
|
||||||
thisBase,
|
thisBase,
|
||||||
<usize>deleteCount << alignof<T>()
|
<usize>deleteCount << alignof<T>()
|
||||||
);
|
);
|
||||||
@ -504,7 +401,7 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.length_ = length - deleteCount;
|
this.length_ = length - deleteCount;
|
||||||
return result;
|
return changetype<Array<T>>(result); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
reverse(): Array<T> {
|
reverse(): Array<T> {
|
||||||
@ -563,7 +460,7 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
var sepLen = separator.length;
|
var sepLen = separator.length;
|
||||||
var valueLen = 5; // max possible length of element len("false")
|
var valueLen = 5; // max possible length of element len("false")
|
||||||
var estLen = (valueLen + sepLen) * lastIndex + valueLen;
|
var estLen = (valueLen + sepLen) * lastIndex + valueLen;
|
||||||
var result = allocate(estLen << 1);
|
var result = __alloc(estLen << 1, idof<string>());
|
||||||
var offset = 0;
|
var offset = 0;
|
||||||
var value: bool;
|
var value: bool;
|
||||||
for (let i = 0; i < lastIndex; ++i) {
|
for (let i = 0; i < lastIndex; ++i) {
|
||||||
@ -594,11 +491,9 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
offset += valueLen;
|
offset += valueLen;
|
||||||
|
|
||||||
if (estLen > offset) {
|
if (estLen > offset) {
|
||||||
let trimmed = changetype<string>(result).substring(0, offset);
|
return changetype<string>(result).substring(0, offset); // retains/releases result, retains return
|
||||||
discard(result);
|
|
||||||
return trimmed; // registered in .substring
|
|
||||||
}
|
}
|
||||||
return changetype<string>(register(result, __runtime_id<string>()));
|
return changetype<string>(result); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
private join_int(separator: string = ","): string {
|
private join_int(separator: string = ","): string {
|
||||||
@ -606,12 +501,12 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
if (lastIndex < 0) return "";
|
if (lastIndex < 0) return "";
|
||||||
var dataStart = this.dataStart;
|
var dataStart = this.dataStart;
|
||||||
// @ts-ignore: type
|
// @ts-ignore: type
|
||||||
if (!lastIndex) return changetype<string>(itoa<T>(load<T>(dataStart)));
|
if (!lastIndex) return changetype<string>(itoa<T>(load<T>(dataStart))); // retains
|
||||||
|
|
||||||
var sepLen = separator.length;
|
var sepLen = separator.length;
|
||||||
const valueLen = (sizeof<T>() <= 4 ? 10 : 20) + i32(isSigned<T>());
|
const valueLen = (sizeof<T>() <= 4 ? 10 : 20) + i32(isSigned<T>());
|
||||||
var estLen = (valueLen + sepLen) * lastIndex + valueLen;
|
var estLen = (valueLen + sepLen) * lastIndex + valueLen;
|
||||||
var result = allocate(estLen << 1);
|
var result = __alloc(estLen << 1, idof<string>());
|
||||||
var offset = 0;
|
var offset = 0;
|
||||||
var value: T;
|
var value: T;
|
||||||
for (let i = 0; i < lastIndex; ++i) {
|
for (let i = 0; i < lastIndex; ++i) {
|
||||||
@ -631,11 +526,9 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
// @ts-ignore: type
|
// @ts-ignore: type
|
||||||
offset += itoa_stream<T>(result, offset, value);
|
offset += itoa_stream<T>(result, offset, value);
|
||||||
if (estLen > offset) {
|
if (estLen > offset) {
|
||||||
let trimmed = changetype<string>(result).substring(0, offset);
|
return changetype<string>(result).substring(0, offset); // retains/releases result, retains return
|
||||||
discard(result);
|
|
||||||
return trimmed; // registered in .substring
|
|
||||||
}
|
}
|
||||||
return changetype<string>(register(result, __runtime_id<string>()));
|
return changetype<string>(result); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
private join_flt(separator: string = ","): string {
|
private join_flt(separator: string = ","): string {
|
||||||
@ -646,13 +539,13 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
return changetype<string>(dtoa(
|
return changetype<string>(dtoa(
|
||||||
// @ts-ignore: type
|
// @ts-ignore: type
|
||||||
load<T>(dataStart))
|
load<T>(dataStart))
|
||||||
);
|
); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
const valueLen = MAX_DOUBLE_LENGTH;
|
const valueLen = MAX_DOUBLE_LENGTH;
|
||||||
var sepLen = separator.length;
|
var sepLen = separator.length;
|
||||||
var estLen = (valueLen + sepLen) * lastIndex + valueLen;
|
var estLen = (valueLen + sepLen) * lastIndex + valueLen;
|
||||||
var result = allocate(estLen << 1);
|
var result = __alloc(estLen << 1, idof<string>());
|
||||||
var offset = 0;
|
var offset = 0;
|
||||||
var value: T;
|
var value: T;
|
||||||
for (let i = 0; i < lastIndex; ++i) {
|
for (let i = 0; i < lastIndex; ++i) {
|
||||||
@ -676,11 +569,9 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
value
|
value
|
||||||
);
|
);
|
||||||
if (estLen > offset) {
|
if (estLen > offset) {
|
||||||
let trimmed = changetype<string>(result).substring(0, offset);
|
return changetype<string>(result).substring(0, offset); // retains/releases result, retains return
|
||||||
discard(result);
|
|
||||||
return trimmed; // registered in .substring
|
|
||||||
}
|
}
|
||||||
return changetype<string>(register(result, __runtime_id<string>()));
|
return changetype<string>(result); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
private join_str(separator: string = ","): string {
|
private join_str(separator: string = ","): string {
|
||||||
@ -697,7 +588,7 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
if (value !== null) estLen += value.length;
|
if (value !== null) estLen += value.length;
|
||||||
}
|
}
|
||||||
var offset = 0;
|
var offset = 0;
|
||||||
var result = allocate((estLen + sepLen * lastIndex) << 1);
|
var result = __alloc((estLen + sepLen * lastIndex) << 1, idof<string>());
|
||||||
for (let i = 0; i < lastIndex; ++i) {
|
for (let i = 0; i < lastIndex; ++i) {
|
||||||
value = load<string>(dataStart + (<usize>i << alignof<T>()));
|
value = load<string>(dataStart + (<usize>i << alignof<T>()));
|
||||||
if (value !== null) {
|
if (value !== null) {
|
||||||
@ -726,7 +617,7 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
<usize>changetype<string>(value).length << 1
|
<usize>changetype<string>(value).length << 1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return changetype<string>(register(result, __runtime_id<string>()));
|
return changetype<string>(result); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
private join_arr(separator: string = ","): string {
|
private join_arr(separator: string = ","): string {
|
||||||
@ -763,7 +654,7 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
const valueLen = 15; // max possible length of element len("[object Object]")
|
const valueLen = 15; // max possible length of element len("[object Object]")
|
||||||
var sepLen = separator.length;
|
var sepLen = separator.length;
|
||||||
var estLen = (valueLen + sepLen) * lastIndex + valueLen;
|
var estLen = (valueLen + sepLen) * lastIndex + valueLen;
|
||||||
var result = allocate(estLen << 1);
|
var result = __alloc(estLen << 1, idof<string>());
|
||||||
var offset = 0;
|
var offset = 0;
|
||||||
var value: T;
|
var value: T;
|
||||||
for (let i = 0; i < lastIndex; ++i) {
|
for (let i = 0; i < lastIndex; ++i) {
|
||||||
@ -794,11 +685,9 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
offset += valueLen;
|
offset += valueLen;
|
||||||
}
|
}
|
||||||
if (estLen > offset) {
|
if (estLen > offset) {
|
||||||
let out = changetype<string>(result).substring(0, offset);
|
return changetype<string>(result).substring(0, offset); // retains/releases result, retains return
|
||||||
discard(result);
|
|
||||||
return out; // registered in .substring
|
|
||||||
}
|
}
|
||||||
return changetype<string>(register(result, __runtime_id<string>()));
|
return changetype<string>(result); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
@ -807,8 +696,8 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
|
|
||||||
// GC integration
|
// GC integration
|
||||||
|
|
||||||
@unsafe private __traverse(): void {
|
@unsafe private __traverse(cookie: u32): void {
|
||||||
__ref_mark(changetype<usize>(this.data));
|
__visit(changetype<usize>(this.data), cookie);
|
||||||
if (isManaged<T>()) {
|
if (isManaged<T>()) {
|
||||||
let cur = this.dataStart;
|
let cur = this.dataStart;
|
||||||
let end = cur + <usize>this.dataLength;
|
let end = cur + <usize>this.dataLength;
|
||||||
@ -816,12 +705,12 @@ export class Array<T> extends ArrayBufferView {
|
|||||||
let val = load<usize>(cur);
|
let val = load<usize>(cur);
|
||||||
if (isNullable<T>()) {
|
if (isNullable<T>()) {
|
||||||
if (val) {
|
if (val) {
|
||||||
__ref_mark(val);
|
__visit(val, cookie);
|
||||||
__gc_mark_members(__runtime_id<T>(), val);
|
__visit_members(val, cookie);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
__ref_mark(val);
|
__visit(val, cookie);
|
||||||
__gc_mark_members(__runtime_id<T>(), val);
|
__visit_members(val, cookie);
|
||||||
}
|
}
|
||||||
cur += sizeof<usize>();
|
cur += sizeof<usize>();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { HEADER, HEADER_SIZE, MAX_BYTELENGTH, allocate, register } from "./util/runtime";
|
/// <reference path="./rt/index.d.ts" />
|
||||||
import { __runtime_id } from "./runtime";
|
|
||||||
|
import { BLOCK, BLOCK_MAXSIZE, BLOCK_OVERHEAD } from "./rt/common";
|
||||||
|
import { idof } from "./builtins";
|
||||||
import { E_INVALIDLENGTH } from "./util/error";
|
import { E_INVALIDLENGTH } from "./util/error";
|
||||||
|
|
||||||
export abstract class ArrayBufferView {
|
export abstract class ArrayBufferView {
|
||||||
@ -9,9 +11,9 @@ export abstract class ArrayBufferView {
|
|||||||
@unsafe dataLength: u32;
|
@unsafe dataLength: u32;
|
||||||
|
|
||||||
protected constructor(length: i32, alignLog2: i32) {
|
protected constructor(length: i32, alignLog2: i32) {
|
||||||
if (<u32>length > <u32>MAX_BYTELENGTH >>> alignLog2) throw new RangeError(E_INVALIDLENGTH);
|
if (<u32>length > <u32>BLOCK_MAXSIZE >>> alignLog2) throw new RangeError(E_INVALIDLENGTH);
|
||||||
var buffer = new ArrayBuffer(length = length << alignLog2);
|
var buffer = new ArrayBuffer(length = length << alignLog2);
|
||||||
this.data = buffer;
|
this.data = buffer; // retains
|
||||||
this.dataStart = changetype<usize>(buffer);
|
this.dataStart = changetype<usize>(buffer);
|
||||||
this.dataLength = length;
|
this.dataLength = length;
|
||||||
}
|
}
|
||||||
@ -51,24 +53,24 @@ export abstract class ArrayBufferView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
constructor(length: i32) {
|
constructor(length: i32) {
|
||||||
if (<u32>length > <u32>MAX_BYTELENGTH) throw new RangeError(E_INVALIDLENGTH);
|
if (<u32>length > <u32>BLOCK_MAXSIZE) throw new RangeError(E_INVALIDLENGTH);
|
||||||
var buffer = allocate(<usize>length);
|
var buffer = __alloc(<usize>length, idof<ArrayBuffer>());
|
||||||
memory.fill(changetype<usize>(buffer), 0, <usize>length);
|
memory.fill(buffer, 0, <usize>length);
|
||||||
return changetype<ArrayBuffer>(register(buffer, __runtime_id<ArrayBuffer>()));
|
return changetype<ArrayBuffer>(buffer); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
get byteLength(): i32 {
|
get byteLength(): i32 {
|
||||||
return changetype<HEADER>(changetype<usize>(this) - HEADER_SIZE).payloadSize;
|
return changetype<BLOCK>(changetype<usize>(this) - BLOCK_OVERHEAD).rtSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
slice(begin: i32 = 0, end: i32 = MAX_BYTELENGTH): ArrayBuffer {
|
slice(begin: i32 = 0, end: i32 = BLOCK_MAXSIZE): ArrayBuffer {
|
||||||
var length = this.byteLength;
|
var length = this.byteLength;
|
||||||
begin = begin < 0 ? max(length + begin, 0) : min(begin, length);
|
begin = begin < 0 ? max(length + begin, 0) : min(begin, length);
|
||||||
end = end < 0 ? max(length + end , 0) : min(end , length);
|
end = end < 0 ? max(length + end , 0) : min(end , length);
|
||||||
var outSize = <usize>max(end - begin, 0);
|
var outSize = <usize>max(end - begin, 0);
|
||||||
var out = allocate(outSize);
|
var out = __alloc(outSize, idof<ArrayBuffer>());
|
||||||
memory.copy(out, changetype<usize>(this) + <usize>begin, outSize);
|
memory.copy(out, changetype<usize>(this) + <usize>begin, outSize);
|
||||||
return changetype<ArrayBuffer>(register(out, __runtime_id<ArrayBuffer>()));
|
return changetype<ArrayBuffer>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
|
@ -1740,3 +1740,23 @@ declare function trace(
|
|||||||
a3?: f64,
|
a3?: f64,
|
||||||
a4?: f64
|
a4?: f64
|
||||||
): void;
|
): void;
|
||||||
|
|
||||||
|
// @ts-ignore: decorator
|
||||||
|
@builtin
|
||||||
|
export declare const HEAP_BASE: usize;
|
||||||
|
|
||||||
|
// @ts-ignore: decorator
|
||||||
|
@builtin
|
||||||
|
export declare const RTTI_BASE: usize;
|
||||||
|
|
||||||
|
// @ts-ignore: decorator
|
||||||
|
@builtin
|
||||||
|
export declare function idof<T>(): u32;
|
||||||
|
|
||||||
|
// @ts-ignore: decorator
|
||||||
|
@builtin @unsafe
|
||||||
|
export declare function __visit_globals(cookie: u32): void;
|
||||||
|
|
||||||
|
// @ts-ignore: decorator
|
||||||
|
@builtin @unsafe
|
||||||
|
export declare function __visit_members(ref: usize, cookie: u32): void;
|
||||||
|
@ -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.
|
|
@ -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);
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
15
std/assembly/collector/index.d.ts
vendored
15
std/assembly/collector/index.d.ts
vendored
@ -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;
|
|
@ -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();
|
|
||||||
}
|
|
@ -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.
|
|
@ -1,4 +1,4 @@
|
|||||||
import { MAX_BYTELENGTH } from "./util/runtime";
|
import { BLOCK_MAXSIZE } from "./rt/common";
|
||||||
import { ArrayBuffer } from "./arraybuffer";
|
import { ArrayBuffer } from "./arraybuffer";
|
||||||
import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH } from "./util/error";
|
import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH } from "./util/error";
|
||||||
|
|
||||||
@ -16,10 +16,10 @@ export class DataView {
|
|||||||
byteLength: i32 = buffer.byteLength
|
byteLength: i32 = buffer.byteLength
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
i32(<u32>byteLength > <u32>MAX_BYTELENGTH) |
|
i32(<u32>byteLength > <u32>BLOCK_MAXSIZE) |
|
||||||
i32(<u32>byteOffset + byteLength > <u32>buffer.byteLength)
|
i32(<u32>byteOffset + byteLength > <u32>buffer.byteLength)
|
||||||
) throw new RangeError(E_INVALIDLENGTH);
|
) throw new RangeError(E_INVALIDLENGTH);
|
||||||
this.data = buffer; // links
|
this.data = buffer; // retains
|
||||||
var dataStart = changetype<usize>(buffer) + <usize>byteOffset;
|
var dataStart = changetype<usize>(buffer) + <usize>byteOffset;
|
||||||
this.dataStart = dataStart;
|
this.dataStart = dataStart;
|
||||||
this.dataLength = byteLength;
|
this.dataLength = byteLength;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { HEADER, HEADER_SIZE, MAX_BYTELENGTH, allocate, register } from "./util/runtime";
|
/// <reference path="./rt/index.d.ts" />
|
||||||
import { __runtime_id, __gc_mark_members } from "./runtime";
|
|
||||||
|
import { BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "./rt/common";
|
||||||
|
import { idof } from "./builtins";
|
||||||
import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH, E_HOLEYARRAY } from "./util/error";
|
import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH, E_HOLEYARRAY } from "./util/error";
|
||||||
|
|
||||||
// NOTE: DO NOT USE YET!
|
// NOTE: DO NOT USE YET!
|
||||||
@ -11,20 +13,20 @@ export class FixedArray<T> {
|
|||||||
[key: number]: T;
|
[key: number]: T;
|
||||||
|
|
||||||
constructor(length: i32) {
|
constructor(length: i32) {
|
||||||
if (<u32>length > <u32>MAX_BYTELENGTH >>> alignof<T>()) throw new RangeError(E_INVALIDLENGTH);
|
if (<u32>length > <u32>BLOCK_MAXSIZE >>> alignof<T>()) throw new RangeError(E_INVALIDLENGTH);
|
||||||
if (isReference<T>()) {
|
if (isReference<T>()) {
|
||||||
if (!isNullable<T>()) {
|
if (!isNullable<T>()) {
|
||||||
if (length) throw new Error(E_HOLEYARRAY);
|
if (length) throw new Error(E_HOLEYARRAY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var outSize = <usize>length << alignof<T>();
|
var outSize = <usize>length << alignof<T>();
|
||||||
var out = allocate(outSize);
|
var out = __alloc(outSize, idof<FixedArray<T>>());
|
||||||
memory.fill(out, 0, outSize);
|
memory.fill(out, 0, outSize);
|
||||||
return changetype<FixedArray<T>>(register(out, __runtime_id<FixedArray<T>>()));
|
return changetype<FixedArray<T>>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
get length(): i32 {
|
get length(): i32 {
|
||||||
return changetype<HEADER>(changetype<usize>(this) - HEADER_SIZE).payloadSize >>> alignof<T>();
|
return changetype<BLOCK>(changetype<usize>(this) - BLOCK_OVERHEAD).rtSize >>> alignof<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@operator("[]") private __get(index: i32): T {
|
@operator("[]") private __get(index: i32): T {
|
||||||
@ -44,26 +46,10 @@ export class FixedArray<T> {
|
|||||||
@operator("{}=") private __unchecked_set(index: i32, value: T): void {
|
@operator("{}=") private __unchecked_set(index: i32, value: T): void {
|
||||||
if (isManaged<T>()) {
|
if (isManaged<T>()) {
|
||||||
let offset = changetype<usize>(this) + (<usize>index << alignof<T>());
|
let offset = changetype<usize>(this) + (<usize>index << alignof<T>());
|
||||||
let oldValue = load<T>(offset);
|
let oldValue = load<usize>(offset);
|
||||||
if (value !== oldValue) {
|
if (changetype<usize>(value) != oldValue) {
|
||||||
store<T>(offset, value);
|
store<usize>(offset, __retain(changetype<usize>(value)));
|
||||||
if (oldValue !== null) {
|
__release(changetype<usize>(oldValue));
|
||||||
if (isDefined(__ref_link)) {
|
|
||||||
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
|
|
||||||
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldValue));
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
if (isNullable<T>()) {
|
|
||||||
if (value !== null) {
|
|
||||||
if (isDefined(__ref_link)) __ref_link(changetype<usize>(value), changetype<usize>(this));
|
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(value));
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isDefined(__ref_link)) __ref_link(changetype<usize>(value), changetype<usize>(this));
|
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(value));
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
store<T>(changetype<usize>(this) + (<usize>index << alignof<T>()), value);
|
store<T>(changetype<usize>(this) + (<usize>index << alignof<T>()), value);
|
||||||
@ -72,20 +58,20 @@ export class FixedArray<T> {
|
|||||||
|
|
||||||
// GC integration
|
// GC integration
|
||||||
|
|
||||||
@unsafe private __traverse(): void {
|
@unsafe private __traverse(cookie: u32): void {
|
||||||
if (isManaged<T>()) {
|
if (isManaged<T>()) {
|
||||||
let cur = changetype<usize>(this);
|
let cur = changetype<usize>(this);
|
||||||
let end = cur + changetype<HEADER>(changetype<usize>(this) - HEADER_SIZE).payloadSize;
|
let end = cur + changetype<BLOCK>(changetype<usize>(this) - BLOCK_OVERHEAD).rtSize;
|
||||||
while (cur < end) {
|
while (cur < end) {
|
||||||
let val = load<usize>(cur);
|
let val = load<usize>(cur);
|
||||||
if (isNullable<T>()) {
|
if (isNullable<T>()) {
|
||||||
if (val) {
|
if (val) {
|
||||||
__ref_mark(val);
|
__visit(val, cookie);
|
||||||
__gc_mark_members(__runtime_id<T>(), val);
|
__visit_members(val, cookie);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
__ref_mark(val);
|
__visit(val, cookie);
|
||||||
__gc_mark_members(__runtime_id<T>(), val);
|
__visit_members(val, cookie);
|
||||||
}
|
}
|
||||||
cur += sizeof<usize>();
|
cur += sizeof<usize>();
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
/// <reference path="./collector/index.d.ts" />
|
/// <reference path="./rt/index.d.ts" />
|
||||||
|
|
||||||
import { HASH } from "./util/hash";
|
import { HASH } from "./util/hash";
|
||||||
import { __runtime_id, __gc_mark_members } from "./runtime";
|
|
||||||
|
|
||||||
// A deterministic hash map based on CloseTable from https://github.com/jorendorff/dht
|
// A deterministic hash map based on CloseTable from https://github.com/jorendorff/dht
|
||||||
|
|
||||||
@ -83,7 +82,7 @@ export class Map<K,V> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private find(key: K, hashCode: u32): MapEntry<K,V> | null {
|
private find(key: K, hashCode: u32): MapEntry<K,V> | null {
|
||||||
var entry = load<MapEntry<K,V>>(
|
var entry = load<MapEntry<K,V>>( // unmanaged!
|
||||||
changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE
|
changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE
|
||||||
);
|
);
|
||||||
while (entry) {
|
while (entry) {
|
||||||
@ -106,35 +105,9 @@ export class Map<K,V> {
|
|||||||
var hashCode = HASH<K>(key);
|
var hashCode = HASH<K>(key);
|
||||||
var entry = this.find(key, hashCode); // unmanaged!
|
var entry = this.find(key, hashCode); // unmanaged!
|
||||||
if (entry) {
|
if (entry) {
|
||||||
if (isManaged<V>()) {
|
entry.value = isManaged<V>()
|
||||||
let oldValue = entry.value;
|
? changetype<V>(__retainRelease(changetype<usize>(value), changetype<usize>(entry.value)))
|
||||||
if (value !== oldValue) {
|
: value;
|
||||||
entry.value = value;
|
|
||||||
if (isNullable<V>()) {
|
|
||||||
if (oldValue !== null) {
|
|
||||||
if (isDefined(__ref_link)) {
|
|
||||||
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
|
|
||||||
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldValue));
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
if (value !== null) {
|
|
||||||
if (isDefined(__ref_link)) __ref_link(changetype<usize>(value), changetype<usize>(this));
|
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(value));
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isDefined(__ref_link)) {
|
|
||||||
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
|
|
||||||
__ref_link(changetype<usize>(value), changetype<usize>(this));
|
|
||||||
} else if (isDefined(__ref_retain)) {
|
|
||||||
__ref_release(changetype<usize>(oldValue));
|
|
||||||
__ref_retain(changetype<usize>(value));
|
|
||||||
} else assert(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
entry.value = value;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// check if rehashing is necessary
|
// check if rehashing is necessary
|
||||||
if (this.entriesOffset == this.entriesCapacity) {
|
if (this.entriesOffset == this.entriesCapacity) {
|
||||||
@ -147,35 +120,13 @@ export class Map<K,V> {
|
|||||||
// append new entry
|
// append new entry
|
||||||
let entries = this.entries;
|
let entries = this.entries;
|
||||||
entry = changetype<MapEntry<K,V>>(changetype<usize>(entries) + this.entriesOffset++ * ENTRY_SIZE<K,V>());
|
entry = changetype<MapEntry<K,V>>(changetype<usize>(entries) + this.entriesOffset++ * ENTRY_SIZE<K,V>());
|
||||||
entry.key = key;
|
|
||||||
entry.value = value;
|
|
||||||
// link with the map
|
// link with the map
|
||||||
if (isManaged<K>()) {
|
entry.key = isManaged<K>()
|
||||||
if (isNullable<K>()) {
|
? changetype<K>(__retain(changetype<usize>(key)))
|
||||||
if (key !== null) {
|
: key;
|
||||||
if (isDefined(__ref_link)) __ref_link(changetype<usize>(key), changetype<usize>(this));
|
entry.value = isManaged<V>()
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(key));
|
? changetype<V>(__retain(changetype<usize>(value)))
|
||||||
else assert(false);
|
: value;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isDefined(__ref_link)) __ref_link(changetype<usize>(key), changetype<usize>(this));
|
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(key));
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isManaged<V>()) {
|
|
||||||
if (isNullable<V>()) {
|
|
||||||
if (value !== null) {
|
|
||||||
if (isDefined(__ref_link)) __ref_link(changetype<usize>(value), changetype<usize>(this));
|
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(value));
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isDefined(__ref_link)) __ref_link(changetype<usize>(value), changetype<usize>(this));
|
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(value));
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
++this.entriesCount;
|
++this.entriesCount;
|
||||||
// link with previous entry in bucket
|
// link with previous entry in bucket
|
||||||
let bucketPtrBase = changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE;
|
let bucketPtrBase = changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE;
|
||||||
@ -187,38 +138,8 @@ export class Map<K,V> {
|
|||||||
delete(key: K): bool {
|
delete(key: K): bool {
|
||||||
var entry = this.find(key, HASH<K>(key));
|
var entry = this.find(key, HASH<K>(key));
|
||||||
if (!entry) return false;
|
if (!entry) return false;
|
||||||
if (isManaged<K>()) {
|
if (isManaged<K>()) __release(changetype<usize>(entry.key));
|
||||||
let oldKey = entry.key;
|
if (isManaged<V>()) __release(changetype<usize>(entry.value));
|
||||||
if (isNullable<K>()) {
|
|
||||||
if (oldKey !== null) {
|
|
||||||
if (isDefined(__ref_link)) {
|
|
||||||
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldKey), changetype<usize>(this));
|
|
||||||
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldKey));
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isDefined(__ref_link)) {
|
|
||||||
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldKey), changetype<usize>(this));
|
|
||||||
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldKey));
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isManaged<V>()) {
|
|
||||||
let oldValue = entry.key;
|
|
||||||
if (isNullable<V>()) {
|
|
||||||
if (oldValue !== null) {
|
|
||||||
if (isDefined(__ref_link)) {
|
|
||||||
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
|
|
||||||
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldValue));
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isDefined(__ref_link)) {
|
|
||||||
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
|
|
||||||
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldValue));
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
entry.taggedNext |= EMPTY;
|
entry.taggedNext |= EMPTY;
|
||||||
--this.entriesCount;
|
--this.entriesCount;
|
||||||
// check if rehashing is appropriate
|
// check if rehashing is appropriate
|
||||||
@ -268,10 +189,10 @@ export class Map<K,V> {
|
|||||||
|
|
||||||
// GC integration
|
// GC integration
|
||||||
|
|
||||||
@unsafe private __traverse(): void {
|
@unsafe private __traverse(cookie: u32): void {
|
||||||
__ref_mark(changetype<usize>(this.buckets));
|
__visit(changetype<usize>(this.buckets), cookie);
|
||||||
var entries = this.entries;
|
var entries = this.entries;
|
||||||
__ref_mark(changetype<usize>(entries));
|
__visit(changetype<usize>(entries), cookie);
|
||||||
if (isManaged<K>() || isManaged<V>()) {
|
if (isManaged<K>() || isManaged<V>()) {
|
||||||
let cur = changetype<usize>(entries);
|
let cur = changetype<usize>(entries);
|
||||||
let end = cur + <usize>this.entriesOffset * ENTRY_SIZE<K,V>();
|
let end = cur + <usize>this.entriesOffset * ENTRY_SIZE<K,V>();
|
||||||
@ -282,24 +203,24 @@ export class Map<K,V> {
|
|||||||
let val = changetype<usize>(entry.key);
|
let val = changetype<usize>(entry.key);
|
||||||
if (isNullable<K>()) {
|
if (isNullable<K>()) {
|
||||||
if (val) {
|
if (val) {
|
||||||
__ref_mark(val);
|
__visit(val, cookie);
|
||||||
__gc_mark_members(__runtime_id<K>(), val);
|
__visit_members(val, cookie);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
__ref_mark(val);
|
__visit(val, cookie);
|
||||||
__gc_mark_members(__runtime_id<K>(), val);
|
__visit_members(val, cookie);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isManaged<V>()) {
|
if (isManaged<V>()) {
|
||||||
let val = changetype<usize>(entry.value);
|
let val = changetype<usize>(entry.value);
|
||||||
if (isNullable<V>()) {
|
if (isNullable<V>()) {
|
||||||
if (val) {
|
if (val) {
|
||||||
__ref_mark(val);
|
__visit(val, cookie);
|
||||||
__gc_mark_members(__runtime_id<V>(), val);
|
__visit_members(val, cookie);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
__ref_mark(val);
|
__visit(val, cookie);
|
||||||
__gc_mark_members(__runtime_id<V>(), val);
|
__visit_members(val, cookie);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,9 @@
|
|||||||
/// <reference path="./allocator/index.d.ts" />
|
|
||||||
|
|
||||||
import { memcmp, memmove, memset } from "./util/memory";
|
import { memcmp, memmove, memset } from "./util/memory";
|
||||||
import { E_NOTIMPLEMENTED } from "./util/error";
|
import { E_NOTIMPLEMENTED } from "./util/error";
|
||||||
|
|
||||||
// @ts-ignore: decorator
|
|
||||||
@builtin
|
|
||||||
export declare const HEAP_BASE: usize;
|
|
||||||
|
|
||||||
/** Memory manager interface. */
|
/** Memory manager interface. */
|
||||||
export namespace memory {
|
export namespace memory {
|
||||||
|
|
||||||
/** Whether the memory managed interface is implemented. */
|
|
||||||
// @ts-ignore: decorator
|
|
||||||
@lazy
|
|
||||||
export const implemented: bool = isDefined(__mem_allocate);
|
|
||||||
|
|
||||||
/** Gets the size of the memory in pages. */
|
/** Gets the size of the memory in pages. */
|
||||||
// @ts-ignore: decorator
|
// @ts-ignore: decorator
|
||||||
@builtin
|
@builtin
|
||||||
@ -53,30 +42,6 @@ export namespace memory {
|
|||||||
throw new Error(E_NOTIMPLEMENTED);
|
throw new Error(E_NOTIMPLEMENTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Dynamically allocates a section of memory and returns its address. */
|
|
||||||
// @ts-ignore: decorator
|
|
||||||
@unsafe
|
|
||||||
export function allocate(size: usize): usize {
|
|
||||||
if (isDefined(__mem_allocate)) return __mem_allocate(size);
|
|
||||||
else throw new Error(E_NOTIMPLEMENTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Dynamically frees a section of memory by the previously allocated address. */
|
|
||||||
// @ts-ignore: decorator
|
|
||||||
@unsafe
|
|
||||||
export function free(ptr: usize): void {
|
|
||||||
if (isDefined(__mem_free)) __mem_free(ptr);
|
|
||||||
else throw new Error(E_NOTIMPLEMENTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Resets the memory to its initial state. Arena allocator only. */
|
|
||||||
// @ts-ignore: decorator
|
|
||||||
@unsafe
|
|
||||||
export function reset(): void {
|
|
||||||
if (isDefined(__mem_reset)) __mem_reset();
|
|
||||||
else throw new Error(E_NOTIMPLEMENTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Repeats a section of memory at a specific address. */
|
/** Repeats a section of memory at a specific address. */
|
||||||
// @ts-ignore: decorator
|
// @ts-ignore: decorator
|
||||||
@unsafe
|
@unsafe
|
||||||
|
@ -8,29 +8,68 @@ It is based on [the TLSF memory manager](./tlsf.ts) and [a pure reference counti
|
|||||||
Interface
|
Interface
|
||||||
---------
|
---------
|
||||||
|
|
||||||
* **__rt_allocate**(size: `usize`, id: `u32` = 0): `usize`<br />
|
* **__alloc**(size: `usize`, id: `u32` = 0): `usize`<br />
|
||||||
Dynamically allocates a chunk of memory of at least the specified size and returns its address.
|
Dynamically allocates a chunk of memory of at least the specified size and returns its address.
|
||||||
Alignment is guaranteed to be 16 bytes to fit up to v128 values naturally.
|
Alignment is guaranteed to be 16 bytes to fit up to v128 values naturally.
|
||||||
|
|
||||||
* **__rt_reallocate**(ref: `usize`, size: `usize`): `usize`<br />
|
* **__realloc**(ref: `usize`, size: `usize`): `usize`<br />
|
||||||
Dynamically changes the size of a chunk of memory, possibly moving it to a new address.
|
Dynamically changes the size of a chunk of memory, possibly moving it to a new address.
|
||||||
|
|
||||||
* **__rt_free**(ref: `usize`): `void`<br />
|
* **__free**(ref: `usize`): `void`<br />
|
||||||
Frees a dynamically allocated chunk of memory by its address.
|
Frees a dynamically allocated chunk of memory by its address.
|
||||||
|
|
||||||
* **__rt_retain**(ref: `usize`): `void`<br />
|
* **__retain**(ref: `usize`): `void`<br />
|
||||||
Retains a reference.
|
Retains a reference to an instance of a reference type. The instance doesn't become collected as long as there's at least one retained reference.
|
||||||
|
|
||||||
* **__rt_release**(ref: `usize`): `void`<br />
|
* **__release**(ref: `usize`): `void`<br />
|
||||||
Releases a reference.
|
Releases a reference to an instance of a reference type. The instance is considered for collection once all references to it have been released.
|
||||||
|
|
||||||
* **__rt_collect**(): `void`<br />
|
* **__collect**(): `void`<br />
|
||||||
Forces a full garbage collection cycle.
|
Forces a full garbage collection cycle. By default this means that reference cycles are resolved and possibly collected.
|
||||||
|
|
||||||
* **__rt_typeinfo**(id: `u32`): `void`<br />
|
* **__visit**(ref: `usize`, cookie: `u32`): `void`<br />
|
||||||
Obtains the runtime type information for objects of the kind represented by the specified id.
|
Concrete visitor implementation called during traversal. Cookie can be used to indicate one of multiple operations.
|
||||||
|
|
||||||
|
Built-ins
|
||||||
|
---------
|
||||||
|
|
||||||
|
The following functions are generated by the compiler based on compile-time information that wouldn't be available or inefficient to provide otherwise.
|
||||||
|
|
||||||
|
* **__info**(id: `u32`): `void`<br />
|
||||||
|
Obtains the runtime type information for objects with the specified runtime id. Runtime type information is a set of flags indicating whether a reference type is managed, an array or similar, and what the relevant alignments when creating an instance are etc.
|
||||||
|
|
||||||
|
* **__visit_globals**(cookie: `u32`)<br />
|
||||||
|
Calls `__visit` on each global that is of a reference type. Not used anymore (originally provided to support tracing GCs) but still here for possible future use.
|
||||||
|
|
||||||
|
* **__visit_members**(ref: `usize`, cookie: `u32`)<br />
|
||||||
|
Calls `__visit` on each member of the instance pointed to by `ref` that is of a reference type.
|
||||||
|
|
||||||
Stub
|
Stub
|
||||||
----
|
----
|
||||||
|
|
||||||
The fully functional yet minimal [stub implementation](./stub.ts) provides dynamic memory allocation only but doesn't include sophisticated support to deallocate objects. Useful for prototyping or very short-lived programs with hardly any memory footprint.
|
A fully functional yet minimal (as in code size) [stub implementation](./index-stub.ts) that provides dynamic memory allocation but no deallocation. Useful for prototyping or very short-lived programs with hardly any memory footprint. The [none implementation](./index-none.ts) is the same as the stub implementation without any runtime exports.
|
||||||
|
|
||||||
|
Integration notes
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Working with the runtime internals within standard library code can be tricky and requires knowledge of where the compiler will insert runtime calls automatically. For example, whenever a value of a reference type is assigned to a local, a global or a field, the compiler *might* insert a `__retain` call, respectively whenever such a value becomes unassigned from one, *might* insert a `__release` call. When a value is handled as an `usize` (i.e. when it comes from `__alloc` or is `changetype<usize>`ed), no such insertion happens (afterwards), but as soon as a `changetype<RefType>`ed (again), the side-effects introduced by automatic insertion must be understood.
|
||||||
|
|
||||||
|
A `__retain` call is inserted when a value of a reference type
|
||||||
|
* is assigned to a local, global or a field **if** the value is not already the exact same value as stored before
|
||||||
|
* is an argument to a function call, including `this` (i.e. `str.indexOf` retains `str`)
|
||||||
|
* is returned from a function (i.e. no need to manually `__retain` if explicitly `changetype`d)
|
||||||
|
|
||||||
|
A `__release` call is inserted when a value of a reference type
|
||||||
|
* becomes unassigned from a local, global or a field due to assigning a new value **if** the value is not already the exact same value as stored before
|
||||||
|
* is popped together with its local from the current scope, i.e. a local declared with `let` in a block, or otherwise at the end of a function
|
||||||
|
|
||||||
|
If not taken into account properly
|
||||||
|
* a memory leak will occur when `__retain`ed more often than intended
|
||||||
|
* a double-free will occur when `__release`d more often than intended
|
||||||
|
|
||||||
|
Also note that a `load<T>(x)` with a reference type acts like a `changetype<T>(load<usize>(x))` and does not `__retain` unless the result is assigned to a local.
|
||||||
|
|
||||||
|
Some best practices are:
|
||||||
|
* Use the fresh `__alloc`ed reference in `usize` form where possible, e.g. when just copying raw bytes is necessary, and `changetype` it once on return.
|
||||||
|
* When providing such a `usize` reference to a function, if the value isn't needed anymore afterwards, just `changetype` it on the call which will `__retain` and `__release` it automatically, including freeing it if wasn't retained before, or, if still needed afterwards, assign the `changetype`d reference to a local first and provide the local as the argument, hence keeping the reference alive as long as the local or any subsequent target is.
|
||||||
|
* If it's not avoidable to `changetype` to the actual reference type, do it inline in an expression and avoid assigning to a local.
|
||||||
|
@ -12,10 +12,22 @@
|
|||||||
// @ts-ignore: decorator
|
// @ts-ignore: decorator
|
||||||
@inline export const DEBUG = true;
|
@inline export const DEBUG = true;
|
||||||
|
|
||||||
/** Common block structure. */
|
// ╒════════════════ Common block layout (32-bit) ═════════════════╕
|
||||||
@unmanaged export class CommonBlock {
|
// 3 2 1
|
||||||
|
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits
|
||||||
|
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤
|
||||||
|
// │ MM info │ -16
|
||||||
|
// ├───────────────────────────────────────────────────────────────┤
|
||||||
|
// │ GC info │ -12
|
||||||
|
// ├───────────────────────────────────────────────────────────────┤
|
||||||
|
// │ runtime id │ -8
|
||||||
|
// ├───────────────────────────────────────────────────────────────┤
|
||||||
|
// │ runtime size │ -4
|
||||||
|
// ╞═══════════════════════════════════════════════════════════════╡
|
||||||
|
// │ ... │ ref
|
||||||
|
@unmanaged export class BLOCK {
|
||||||
/** Memory manager info. */
|
/** Memory manager info. */
|
||||||
mmInfo: usize; // WASM64 might need adaption
|
mmInfo: usize; // WASM64 needs adaption
|
||||||
/** Garbage collector info. */
|
/** Garbage collector info. */
|
||||||
gcInfo: u32;
|
gcInfo: u32;
|
||||||
/** Runtime class id. */
|
/** Runtime class id. */
|
||||||
@ -24,16 +36,53 @@
|
|||||||
rtSize: u32;
|
rtSize: u32;
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////////// Type information interface ////////////////////////////////////
|
// @ts-ignore: decorator
|
||||||
|
@inline export const BLOCK_OVERHEAD = (offsetof<BLOCK>() + AL_MASK) & ~AL_MASK;
|
||||||
import { RTTI_BASE } from "../runtime";
|
|
||||||
import { RTTIData } from "../common/rtti";
|
|
||||||
|
|
||||||
// @ts-ignore: decorator
|
// @ts-ignore: decorator
|
||||||
@global @unsafe
|
@inline export const BLOCK_MAXSIZE: usize = (1 << 30) - BLOCK_OVERHEAD;
|
||||||
function __rt_typeinfo(id: u32): u32 {
|
|
||||||
var ptr: usize = RTTI_BASE;
|
/////////////////////////////////// Type information interface ////////////////////////////////////
|
||||||
|
|
||||||
|
import { RTTI_BASE } from "builtins";
|
||||||
|
import { RTTIData, RTTIFlags } from "common/rtti";
|
||||||
|
|
||||||
|
// @ts-ignore: decorator
|
||||||
|
@unsafe @global
|
||||||
|
export function __typeinfo(id: u32): RTTIFlags {
|
||||||
|
var ptr = RTTI_BASE;
|
||||||
return !id || id > load<u32>(ptr)
|
return !id || id > load<u32>(ptr)
|
||||||
? unreachable()
|
? unreachable()
|
||||||
: changetype<RTTIData>(ptr + id * offsetof<RTTIData>()).flags;
|
: changetype<RTTIData>(ptr + id * offsetof<RTTIData>()).flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore: decorator
|
||||||
|
@unsafe @global
|
||||||
|
export function __instanceof(ref: usize, superId: u32): bool { // keyword
|
||||||
|
var id = changetype<BLOCK>(ref - BLOCK_OVERHEAD).rtId;
|
||||||
|
var ptr = RTTI_BASE;
|
||||||
|
if (id && id <= load<u32>(ptr)) {
|
||||||
|
do if (id == superId) return true;
|
||||||
|
while (id = changetype<RTTIData>(ptr + id * offsetof<RTTIData>()).base);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////// Helpers /////////////////////////////////////////////
|
||||||
|
|
||||||
|
import { idof } from "builtins";
|
||||||
|
import { ArrayBufferView } from "arraybuffer";
|
||||||
|
|
||||||
|
// @ts-ignore: decorator
|
||||||
|
@unsafe @global
|
||||||
|
export function __allocArray(length: i32, alignLog2: usize, id: u32, data: usize = 0): usize {
|
||||||
|
var array = __alloc(offsetof<i32[]>(), id);
|
||||||
|
var bufferSize = <usize>length << alignLog2;
|
||||||
|
var buffer = __alloc(bufferSize, idof<ArrayBuffer>());
|
||||||
|
changetype<ArrayBufferView>(array).data = changetype<ArrayBuffer>(buffer); // TODO/RT: retains
|
||||||
|
changetype<ArrayBufferView>(array).dataStart = buffer;
|
||||||
|
changetype<ArrayBufferView>(array).dataLength = bufferSize;
|
||||||
|
store<i32>(changetype<usize>(array), length, offsetof<i32[]>("length_"));
|
||||||
|
if (data) memory.copy(buffer, data, bufferSize);
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
3
std/assembly/rt/index-full.ts
Normal file
3
std/assembly/rt/index-full.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { __alloc, __realloc, __free } from "./tlsf";
|
||||||
|
export { __retain, __release, __collect } from "./purerc";
|
||||||
|
export { __instanceof, __typeinfo } from "./common";
|
1
std/assembly/rt/index-none.ts
Normal file
1
std/assembly/rt/index-none.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
import "rt/index-stub";
|
@ -1,12 +1,4 @@
|
|||||||
import { AL_MASK, CommonBlock } from "./common";
|
import { AL_MASK, BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "rt/common";
|
||||||
|
|
||||||
// @ts-ignore: decorator
|
|
||||||
@inline
|
|
||||||
const BLOCK_OVERHEAD = (offsetof<CommonBlock>() + AL_MASK) & ~AL_MASK;
|
|
||||||
|
|
||||||
// @ts-ignore: decorator
|
|
||||||
@inline
|
|
||||||
const BLOCK_MAXSIZE: usize = (1 << 30) - BLOCK_OVERHEAD; // match TLSF
|
|
||||||
|
|
||||||
// @ts-ignore: decorator
|
// @ts-ignore: decorator
|
||||||
@lazy
|
@lazy
|
||||||
@ -16,11 +8,9 @@ var startOffset: usize = (HEAP_BASE + AL_MASK) & ~AL_MASK;
|
|||||||
@lazy
|
@lazy
|
||||||
var offset: usize = startOffset;
|
var offset: usize = startOffset;
|
||||||
|
|
||||||
//////////////////////////////////// Memory manager interface /////////////////////////////////////
|
|
||||||
|
|
||||||
// @ts-ignore: decorator
|
// @ts-ignore: decorator
|
||||||
@unsafe @global
|
@unsafe @global
|
||||||
export function __rt_allocate(size: usize, id: u32): usize {
|
export function __alloc(size: usize, id: u32): usize {
|
||||||
if (size > BLOCK_MAXSIZE) unreachable();
|
if (size > BLOCK_MAXSIZE) unreachable();
|
||||||
var ptr = offset + BLOCK_OVERHEAD;
|
var ptr = offset + BLOCK_OVERHEAD;
|
||||||
var newPtr = (ptr + max<usize>(size, 1) + AL_MASK) & ~AL_MASK;
|
var newPtr = (ptr + max<usize>(size, 1) + AL_MASK) & ~AL_MASK;
|
||||||
@ -33,7 +23,7 @@ export function __rt_allocate(size: usize, id: u32): usize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
offset = newPtr;
|
offset = newPtr;
|
||||||
var block = changetype<CommonBlock>(ptr - BLOCK_OVERHEAD);
|
var block = changetype<BLOCK>(ptr - BLOCK_OVERHEAD);
|
||||||
block.rtId = id;
|
block.rtId = id;
|
||||||
block.rtSize = size;
|
block.rtSize = size;
|
||||||
return ptr;
|
return ptr;
|
||||||
@ -41,11 +31,11 @@ export function __rt_allocate(size: usize, id: u32): usize {
|
|||||||
|
|
||||||
// @ts-ignore: decorator
|
// @ts-ignore: decorator
|
||||||
@unsafe @global
|
@unsafe @global
|
||||||
export function __rt_reallocate(ref: usize, size: usize): usize {
|
export function __realloc(ref: usize, size: usize): usize {
|
||||||
var block = changetype<CommonBlock>(ref - BLOCK_OVERHEAD);
|
var block = changetype<BLOCK>(ref - BLOCK_OVERHEAD);
|
||||||
var oldSize = <usize>block.rtSize;
|
var oldSize = <usize>block.rtSize;
|
||||||
if (size > oldSize) {
|
if (size > oldSize) {
|
||||||
let newRef = __rt_allocate(size, block.rtId);
|
let newRef = __alloc(size, block.rtId);
|
||||||
memory.copy(newRef, ref, oldSize);
|
memory.copy(newRef, ref, oldSize);
|
||||||
ref = newRef;
|
ref = newRef;
|
||||||
} else {
|
} else {
|
||||||
@ -56,30 +46,40 @@ export function __rt_reallocate(ref: usize, size: usize): usize {
|
|||||||
|
|
||||||
// @ts-ignore: decorator
|
// @ts-ignore: decorator
|
||||||
@unsafe @global
|
@unsafe @global
|
||||||
export function __rt_free(ref: usize): void {
|
export function __free(ref: usize): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore: decorator
|
// @ts-ignore: decorator
|
||||||
@unsafe @global
|
// @unsafe @global
|
||||||
export function __rt_reset(): void { // special
|
// export function __reset(): void { // special
|
||||||
offset = startOffset;
|
// offset = startOffset;
|
||||||
}
|
// }
|
||||||
|
|
||||||
/////////////////////////////////// Garbage collector interface ///////////////////////////////////
|
|
||||||
|
|
||||||
// @ts-ignore: decorator
|
// @ts-ignore: decorator
|
||||||
@global @unsafe
|
@global @unsafe
|
||||||
export function __rt_retain(ref: usize): void {
|
export function __retain(ref: usize): usize {
|
||||||
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore: decorator
|
// @ts-ignore: decorator
|
||||||
@global @unsafe
|
@global @unsafe
|
||||||
export function __rt_release(ref: usize): void {
|
export function __release(ref: usize): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore: decorator
|
// @ts-ignore: decorator
|
||||||
@global @unsafe
|
@global @unsafe
|
||||||
export function __rt_collect(): void {
|
function __visit(ref: usize, cookie: u32): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
export { __rt_typeinfo };
|
// @ts-ignore: decorator
|
||||||
|
@global @unsafe
|
||||||
|
function __retainRelease(ref: usize, oldRef: usize): usize {
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore: decorator
|
||||||
|
@global @unsafe
|
||||||
|
export function __collect(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
export { __instanceof, __typeinfo } from "rt/common";
|
13
std/assembly/rt/index.d.ts
vendored
Normal file
13
std/assembly/rt/index.d.ts
vendored
Normal 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;
|
@ -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 };
|
|
@ -1,14 +1,10 @@
|
|||||||
import { DEBUG } from "./common";
|
import { DEBUG, BLOCK_OVERHEAD, BLOCK } from "rt/common";
|
||||||
import { Block, freeBlock, ROOT } from "./tlsf";
|
import { Block, freeBlock, ROOT } from "rt/tlsf";
|
||||||
|
import { RTTIFlags } from "common/rtti";
|
||||||
|
|
||||||
/////////////////////////// A Pure Reference Counting Garbage Collector ///////////////////////////
|
/////////////////////////// A Pure Reference Counting Garbage Collector ///////////////////////////
|
||||||
// see: https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon03Pure.pdf
|
// see: https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon03Pure.pdf
|
||||||
|
|
||||||
// TODO: make visitors eat cookies so we can compile direct calls into a switch
|
|
||||||
function __rt_visit_members(s: Block, cookie: i32): void { unreachable(); }
|
|
||||||
function __rt_flags(classId: u32): u32 { return unreachable(); }
|
|
||||||
const ACYCLIC_FLAG: u32 = 0;
|
|
||||||
|
|
||||||
// ╒══════════════════════ GC Info structure ══════════════════════╕
|
// ╒══════════════════════ GC Info structure ══════════════════════╕
|
||||||
// │ 3 2 1 │
|
// │ 3 2 1 │
|
||||||
// │1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0│
|
// │1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0│
|
||||||
@ -66,7 +62,9 @@ const ACYCLIC_FLAG: u32 = 0;
|
|||||||
|
|
||||||
// @ts-ignore: decorator
|
// @ts-ignore: decorator
|
||||||
@global
|
@global
|
||||||
function __rt_visit(s: Block, cookie: i32): void {
|
function __visit(ref: usize, cookie: i32): void {
|
||||||
|
if (ref < HEAP_BASE) return;
|
||||||
|
var s = changetype<Block>(ref - BLOCK_OVERHEAD);
|
||||||
switch (cookie) {
|
switch (cookie) {
|
||||||
case VISIT_DECREMENT: {
|
case VISIT_DECREMENT: {
|
||||||
decrement(s);
|
decrement(s);
|
||||||
@ -100,18 +98,18 @@ function __rt_visit(s: Block, cookie: i32): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Increments the reference count of the specified block by one.*/
|
/** Increments the reference count of the specified block by one.*/
|
||||||
export function increment(s: Block): void {
|
function increment(s: Block): void {
|
||||||
var info = s.gcInfo;
|
var info = s.gcInfo;
|
||||||
assert((info & ~REFCOUNT_MASK) == ((info + 1) & ~REFCOUNT_MASK)); // overflow
|
assert((info & ~REFCOUNT_MASK) == ((info + 1) & ~REFCOUNT_MASK)); // overflow
|
||||||
s.gcInfo = info + 1;
|
s.gcInfo = info + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Decrements the reference count of the specified block by one, possibly freeing it. */
|
/** Decrements the reference count of the specified block by one, possibly freeing it. */
|
||||||
export function decrement(s: Block): void {
|
function decrement(s: Block): void {
|
||||||
var info = s.gcInfo;
|
var info = s.gcInfo;
|
||||||
var rc = info & REFCOUNT_MASK;
|
var rc = info & REFCOUNT_MASK;
|
||||||
if (rc == 1) {
|
if (rc == 1) {
|
||||||
__rt_visit_members(s, VISIT_DECREMENT);
|
__visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_DECREMENT);
|
||||||
if (!(info & BUFFERED_MASK)) {
|
if (!(info & BUFFERED_MASK)) {
|
||||||
freeBlock(ROOT, s);
|
freeBlock(ROOT, s);
|
||||||
} else {
|
} else {
|
||||||
@ -119,7 +117,7 @@ export function decrement(s: Block): void {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (DEBUG) assert(rc > 0);
|
if (DEBUG) assert(rc > 0);
|
||||||
if (!(__rt_flags(s.rtId) & ACYCLIC_FLAG)) {
|
if (!(__typeinfo(s.rtId) & RTTIFlags.ACYCLIC)) {
|
||||||
s.gcInfo = BUFFERED_MASK | COLOR_PURPLE | (rc - 1);
|
s.gcInfo = BUFFERED_MASK | COLOR_PURPLE | (rc - 1);
|
||||||
if (!(info & BUFFERED_MASK)) {
|
if (!(info & BUFFERED_MASK)) {
|
||||||
appendRoot(s);
|
appendRoot(s);
|
||||||
@ -164,7 +162,9 @@ function growRoots(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Collects cyclic garbage. */
|
/** Collects cyclic garbage. */
|
||||||
export function collectCycles(): void {
|
// @ts-ignore: decorator
|
||||||
|
@global @unsafe
|
||||||
|
export function __collect(): void {
|
||||||
|
|
||||||
// markRoots
|
// markRoots
|
||||||
var roots = ROOTS;
|
var roots = ROOTS;
|
||||||
@ -205,7 +205,7 @@ function markGray(s: Block): void {
|
|||||||
var info = s.gcInfo;
|
var info = s.gcInfo;
|
||||||
if ((info & COLOR_MASK) != COLOR_GRAY) {
|
if ((info & COLOR_MASK) != COLOR_GRAY) {
|
||||||
s.gcInfo = (info & ~COLOR_MASK) | COLOR_GRAY;
|
s.gcInfo = (info & ~COLOR_MASK) | COLOR_GRAY;
|
||||||
__rt_visit_members(s, VISIT_MARKGRAY);
|
__visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_MARKGRAY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,7 +217,7 @@ function scan(s: Block): void {
|
|||||||
scanBlack(s);
|
scanBlack(s);
|
||||||
} else {
|
} else {
|
||||||
s.gcInfo = (info & ~COLOR_MASK) | COLOR_WHITE;
|
s.gcInfo = (info & ~COLOR_MASK) | COLOR_WHITE;
|
||||||
__rt_visit_members(s, VISIT_SCAN);
|
__visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_SCAN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,7 +225,7 @@ function scan(s: Block): void {
|
|||||||
/** Marks a block as black (in use) if it was found to be reachable during the collection phase. */
|
/** Marks a block as black (in use) if it was found to be reachable during the collection phase. */
|
||||||
function scanBlack(s: Block): void {
|
function scanBlack(s: Block): void {
|
||||||
s.gcInfo = (s.gcInfo & ~COLOR_MASK) | COLOR_BLACK;
|
s.gcInfo = (s.gcInfo & ~COLOR_MASK) | COLOR_BLACK;
|
||||||
__rt_visit_members(s, VISIT_SCANBLACK);
|
__visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_SCANBLACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Collects all white (member of a garbage cycle) nodes when completing the collection phase. */
|
/** Collects all white (member of a garbage cycle) nodes when completing the collection phase. */
|
||||||
@ -233,7 +233,31 @@ function collectWhite(s: Block): void {
|
|||||||
var info = s.gcInfo;
|
var info = s.gcInfo;
|
||||||
if ((info & COLOR_MASK) == COLOR_WHITE && !(info & BUFFERED_MASK)) {
|
if ((info & COLOR_MASK) == COLOR_WHITE && !(info & BUFFERED_MASK)) {
|
||||||
// s.gcInfo = (info & ~COLOR_MASK) | COLOR_BLACK;
|
// s.gcInfo = (info & ~COLOR_MASK) | COLOR_BLACK;
|
||||||
__rt_visit_members(s, VISIT_COLLECTWHITE);
|
__visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_COLLECTWHITE);
|
||||||
}
|
}
|
||||||
freeBlock(ROOT, s);
|
freeBlock(ROOT, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore: decorator
|
||||||
|
@global @unsafe
|
||||||
|
export function __retain(ref: usize): usize {
|
||||||
|
if (ref > HEAP_BASE) increment(changetype<Block>(ref - BLOCK_OVERHEAD));
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore: decorator
|
||||||
|
@global @unsafe
|
||||||
|
export function __release(ref: usize): void {
|
||||||
|
if (ref > HEAP_BASE) decrement(changetype<Block>(ref - BLOCK_OVERHEAD));
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore: decorator
|
||||||
|
@global @unsafe
|
||||||
|
export function __retainRelease(ref: usize, oldRef: usize): usize {
|
||||||
|
if (ref != oldRef) {
|
||||||
|
let heapBase = HEAP_BASE;
|
||||||
|
if (ref > heapBase) increment(changetype<Block>(ref - BLOCK_OVERHEAD));
|
||||||
|
if (oldRef > heapBase) decrement(changetype<Block>(oldRef - BLOCK_OVERHEAD));
|
||||||
|
}
|
||||||
|
return ref;
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { AL_BITS, AL_SIZE, AL_MASK, DEBUG, CommonBlock } from "./common";
|
import { AL_BITS, AL_MASK, DEBUG, BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "rt/common";
|
||||||
|
|
||||||
/////////////////////// The TLSF (Two-Level Segregate Fit) memory allocator ///////////////////////
|
/////////////////////// The TLSF (Two-Level Segregate Fit) memory allocator ///////////////////////
|
||||||
// see: http://www.gii.upv.es/tlsf/
|
// see: http://www.gii.upv.es/tlsf/
|
||||||
@ -69,7 +69,7 @@ import { AL_BITS, AL_SIZE, AL_MASK, DEBUG, CommonBlock } from "./common";
|
|||||||
// │ if free: back ▲ │ ◄─┘
|
// │ if free: back ▲ │ ◄─┘
|
||||||
// └───────────────────────────────────────────────────────────────┘ payload ┘ >= MIN SIZE
|
// └───────────────────────────────────────────────────────────────┘ payload ┘ >= MIN SIZE
|
||||||
// F: FREE, L: LEFTFREE
|
// F: FREE, L: LEFTFREE
|
||||||
@unmanaged export class Block extends CommonBlock {
|
@unmanaged export class Block extends BLOCK {
|
||||||
|
|
||||||
/** Previous free block, if any. Only valid if free, otherwise part of payload. */
|
/** Previous free block, if any. Only valid if free, otherwise part of payload. */
|
||||||
prev: Block | null;
|
prev: Block | null;
|
||||||
@ -79,15 +79,13 @@ import { AL_BITS, AL_SIZE, AL_MASK, DEBUG, CommonBlock } from "./common";
|
|||||||
// If the block is free, there is a 'back'reference at its end pointing at its start.
|
// If the block is free, there is a 'back'reference at its end pointing at its start.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Block constants. Overhead is always present, no matter if free or used. Also, a block must have
|
// Block constants. A block must have a minimum size of three pointers so it can hold `prev`,
|
||||||
// a minimum size of three pointers so it can hold `prev`, `next` and `back` if free.
|
// `next` and `back` if free.
|
||||||
|
|
||||||
// @ts-ignore: decorator
|
// @ts-ignore: decorator
|
||||||
@inline export const BLOCK_OVERHEAD: usize = (offsetof<Block>("prev") + AL_MASK) & ~AL_MASK;
|
@inline const BLOCK_MINSIZE: usize = (3 * sizeof<usize>() + AL_MASK) & ~AL_MASK; // prev + next + back
|
||||||
// @ts-ignore: decorator
|
// @ts-ignore: decorator
|
||||||
@inline const BLOCK_MINSIZE: usize = (3 * sizeof<usize>() + AL_MASK) & ~AL_MASK;// prev + next + back
|
// @inline const BLOCK_MAXSIZE: usize = 1 << (FL_BITS + SB_BITS - 1); // exclusive, lives in common.ts
|
||||||
// @ts-ignore: decorator
|
|
||||||
@inline const BLOCK_MAXSIZE: usize = 1 << (FL_BITS + SB_BITS - 1); // exclusive
|
|
||||||
|
|
||||||
/** Gets the left block of a block. Only valid if the left block is free. */
|
/** Gets the left block of a block. Only valid if the left block is free. */
|
||||||
// @ts-ignore: decorator
|
// @ts-ignore: decorator
|
||||||
@ -532,3 +530,32 @@ export function freeBlock(root: Root, block: Block): void {
|
|||||||
block.mmInfo = blockInfo | FREE;
|
block.mmInfo = blockInfo | FREE;
|
||||||
insertBlock(root, block);
|
insertBlock(root, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore: decorator
|
||||||
|
@global @unsafe
|
||||||
|
export function __alloc(size: usize, id: u32): usize {
|
||||||
|
var root = ROOT;
|
||||||
|
if (!root) {
|
||||||
|
initializeRoot();
|
||||||
|
root = ROOT;
|
||||||
|
}
|
||||||
|
var block = allocateBlock(root, size);
|
||||||
|
block.rtId = id;
|
||||||
|
return changetype<usize>(block) + BLOCK_OVERHEAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore: decorator
|
||||||
|
@global @unsafe
|
||||||
|
export function __realloc(ref: usize, size: usize): usize {
|
||||||
|
if (DEBUG) assert(ROOT); // must be initialized
|
||||||
|
assert(ref != 0 && !(ref & AL_MASK)); // must exist and be aligned
|
||||||
|
return changetype<usize>(reallocateBlock(ROOT, changetype<Block>(ref - BLOCK_OVERHEAD), size)) + BLOCK_OVERHEAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore: decorator
|
||||||
|
@global @unsafe
|
||||||
|
export function __free(ref: usize): void {
|
||||||
|
if (DEBUG) assert(ROOT); // must be initialized
|
||||||
|
assert(ref != 0 && !(ref & AL_MASK)); // must exist and be aligned
|
||||||
|
freeBlock(ROOT, changetype<Block>(ref - BLOCK_OVERHEAD));
|
||||||
|
}
|
||||||
|
@ -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();
|
|
@ -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
|
|
@ -1,3 +0,0 @@
|
|||||||
import "allocator/arena";
|
|
||||||
|
|
||||||
export { runtime as $ };
|
|
@ -1,4 +0,0 @@
|
|||||||
import "allocator/tlsf";
|
|
||||||
import "collector/itcm";
|
|
||||||
|
|
||||||
export { runtime as $ };
|
|
@ -1,7 +1,6 @@
|
|||||||
/// <reference path="./collector/index.d.ts" />
|
/// <reference path="./rt/index.d.ts" />
|
||||||
|
|
||||||
import { HASH } from "./util/hash";
|
import { HASH } from "./util/hash";
|
||||||
import { __runtime_id, __gc_mark_members } from "./runtime";
|
|
||||||
|
|
||||||
// A deterministic hash set based on CloseTable from https://github.com/jorendorff/dht
|
// A deterministic hash set based on CloseTable from https://github.com/jorendorff/dht
|
||||||
|
|
||||||
@ -79,7 +78,7 @@ export class Set<K> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private find(key: K, hashCode: u32): SetEntry<K> | null {
|
private find(key: K, hashCode: u32): SetEntry<K> | null {
|
||||||
var entry = load<SetEntry<K>>(
|
var entry = load<SetEntry<K>>( // unmanaged!
|
||||||
changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE
|
changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE
|
||||||
);
|
);
|
||||||
while (entry) {
|
while (entry) {
|
||||||
@ -106,23 +105,10 @@ export class Set<K> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
// append new entry
|
// append new entry
|
||||||
let entries = this.entries;
|
entry = changetype<SetEntry<K>>(changetype<usize>(this.entries) + this.entriesOffset++ * ENTRY_SIZE<K>());
|
||||||
entry = changetype<SetEntry<K>>(changetype<usize>(entries) + this.entriesOffset++ * ENTRY_SIZE<K>());
|
entry.key = isManaged<K>()
|
||||||
entry.key = key;
|
? changetype<K>(__retain(changetype<usize>(key)))
|
||||||
// link with the set
|
: key;
|
||||||
if (isManaged<K>()) {
|
|
||||||
if (isNullable<K>()) {
|
|
||||||
if (key !== null) {
|
|
||||||
if (isDefined(__ref_link)) __ref_link(changetype<usize>(key), changetype<usize>(this));
|
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(key));
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isDefined(__ref_link)) __ref_link(changetype<usize>(key), changetype<usize>(this));
|
|
||||||
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(key));
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
++this.entriesCount;
|
++this.entriesCount;
|
||||||
// link with previous entry in bucket
|
// link with previous entry in bucket
|
||||||
let bucketPtrBase = changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE;
|
let bucketPtrBase = changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE;
|
||||||
@ -132,24 +118,9 @@ export class Set<K> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete(key: K): bool {
|
delete(key: K): bool {
|
||||||
var entry = this.find(key, HASH<K>(key));
|
var entry = this.find(key, HASH<K>(key)); // unmanaged!
|
||||||
if (!entry) return false;
|
if (!entry) return false;
|
||||||
if (isManaged<K>()) {
|
if (isManaged<K>()) __release(changetype<usize>(entry.key)); // exact 'key'
|
||||||
key = entry.key; // exact, e.g. string
|
|
||||||
if (isNullable<K>()) {
|
|
||||||
if (key !== null) {
|
|
||||||
if (isDefined(__ref_link)) {
|
|
||||||
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(key), changetype<usize>(this));
|
|
||||||
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(key));
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isDefined(__ref_link)) {
|
|
||||||
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(key), changetype<usize>(this));
|
|
||||||
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(key));
|
|
||||||
else assert(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
entry.taggedNext |= EMPTY;
|
entry.taggedNext |= EMPTY;
|
||||||
--this.entriesCount;
|
--this.entriesCount;
|
||||||
// check if rehashing is appropriate
|
// check if rehashing is appropriate
|
||||||
@ -172,9 +143,9 @@ export class Set<K> {
|
|||||||
var oldEnd = oldPtr + <usize>this.entriesOffset * ENTRY_SIZE<K>();
|
var oldEnd = oldPtr + <usize>this.entriesOffset * ENTRY_SIZE<K>();
|
||||||
var newPtr = changetype<usize>(newEntries);
|
var newPtr = changetype<usize>(newEntries);
|
||||||
while (oldPtr != oldEnd) {
|
while (oldPtr != oldEnd) {
|
||||||
let oldEntry = changetype<SetEntry<K>>(oldPtr);
|
let oldEntry = changetype<SetEntry<K>>(oldPtr); // unmanaged!
|
||||||
if (!(oldEntry.taggedNext & EMPTY)) {
|
if (!(oldEntry.taggedNext & EMPTY)) {
|
||||||
let newEntry = changetype<SetEntry<K>>(newPtr);
|
let newEntry = changetype<SetEntry<K>>(newPtr); // unmanaged!
|
||||||
newEntry.key = oldEntry.key;
|
newEntry.key = oldEntry.key;
|
||||||
let newBucketIndex = HASH<K>(oldEntry.key) & newBucketsMask;
|
let newBucketIndex = HASH<K>(oldEntry.key) & newBucketsMask;
|
||||||
let newBucketPtrBase = changetype<usize>(newBuckets) + <usize>newBucketIndex * BUCKET_SIZE;
|
let newBucketPtrBase = changetype<usize>(newBuckets) + <usize>newBucketIndex * BUCKET_SIZE;
|
||||||
@ -198,10 +169,10 @@ export class Set<K> {
|
|||||||
|
|
||||||
// GC integration
|
// GC integration
|
||||||
|
|
||||||
@unsafe private __traverse(): void {
|
@unsafe private __traverse(cookie: u32): void {
|
||||||
__ref_mark(changetype<usize>(this.buckets));
|
__visit(changetype<usize>(this.buckets), cookie);
|
||||||
var entries = this.entries;
|
var entries = this.entries;
|
||||||
__ref_mark(changetype<usize>(entries));
|
__visit(changetype<usize>(entries), cookie);
|
||||||
if (isManaged<K>()) {
|
if (isManaged<K>()) {
|
||||||
let cur = changetype<usize>(entries);
|
let cur = changetype<usize>(entries);
|
||||||
let end = cur + <usize>this.entriesOffset * ENTRY_SIZE<K>();
|
let end = cur + <usize>this.entriesOffset * ENTRY_SIZE<K>();
|
||||||
@ -211,12 +182,12 @@ export class Set<K> {
|
|||||||
let val = changetype<usize>(entry.key);
|
let val = changetype<usize>(entry.key);
|
||||||
if (isNullable<K>()) {
|
if (isNullable<K>()) {
|
||||||
if (val) {
|
if (val) {
|
||||||
__ref_mark(val);
|
__visit(val, cookie);
|
||||||
__gc_mark_members(__runtime_id<K>(), val);
|
__visit_members(val, cookie);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
__ref_mark(val);
|
__visit(val, cookie);
|
||||||
__gc_mark_members(__runtime_id<K>(), val);
|
__visit_members(val, cookie);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cur += ENTRY_SIZE<K>();
|
cur += ENTRY_SIZE<K>();
|
||||||
|
@ -1,27 +1,26 @@
|
|||||||
/// <reference path="./collector/index.d.ts" />
|
/// <reference path="./rt/index.d.ts" />
|
||||||
|
|
||||||
import { MAX_SIZE_32 } from "./util/allocator";
|
import { BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "./rt/common";
|
||||||
import { HEADER, HEADER_SIZE, allocate, register, NEWARRAY } from "./util/runtime";
|
|
||||||
import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./util/string";
|
import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./util/string";
|
||||||
import { E_INVALIDLENGTH } from "./util/error";
|
import { E_INVALIDLENGTH } from "./util/error";
|
||||||
import { __runtime_id } from "./runtime";
|
|
||||||
import { ArrayBufferView } from "./arraybuffer";
|
import { ArrayBufferView } from "./arraybuffer";
|
||||||
|
import { idof } from "./builtins";
|
||||||
|
|
||||||
@sealed export abstract class String {
|
@sealed export abstract class String {
|
||||||
|
|
||||||
@lazy static readonly MAX_LENGTH: i32 = (MAX_SIZE_32 - HEADER_SIZE) >> alignof<u16>();
|
@lazy static readonly MAX_LENGTH: i32 = BLOCK_MAXSIZE >>> alignof<u16>();
|
||||||
|
|
||||||
// TODO Add and handle second argument
|
// TODO Add and handle second argument
|
||||||
static fromCharCode(code: i32): String {
|
static fromCharCode(code: i32): string {
|
||||||
var out = allocate(2);
|
var out = __alloc(2, idof<string>());
|
||||||
store<u16>(out, <u16>code);
|
store<u16>(out, <u16>code);
|
||||||
return changetype<String>(register(out, __runtime_id<String>()));
|
return changetype<string>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromCodePoint(code: i32): String {
|
static fromCodePoint(code: i32): string {
|
||||||
assert(<u32>code <= 0x10FFFF);
|
assert(<u32>code <= 0x10FFFF);
|
||||||
var sur = code > 0xFFFF;
|
var sur = code > 0xFFFF;
|
||||||
var out = allocate((i32(sur) + 1) << 1);
|
var out = __alloc((i32(sur) + 1) << 1, idof<string>());
|
||||||
if (!sur) {
|
if (!sur) {
|
||||||
store<u16>(out, <u16>code);
|
store<u16>(out, <u16>code);
|
||||||
} else {
|
} else {
|
||||||
@ -30,25 +29,19 @@ import { ArrayBufferView } from "./arraybuffer";
|
|||||||
let lo: u32 = (code & 0x3FF) + 0xDC00;
|
let lo: u32 = (code & 0x3FF) + 0xDC00;
|
||||||
store<u32>(out, (hi << 16) | lo);
|
store<u32>(out, (hi << 16) | lo);
|
||||||
}
|
}
|
||||||
return changetype<String>(register(out, __runtime_id<String>()));
|
return changetype<string>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore: decorator
|
|
||||||
// @unsafe
|
|
||||||
// constructor(length: i32) {
|
|
||||||
// return changetype<String>(register(allocate(<usize>length << 1), __runtime_id<String>()));
|
|
||||||
// }
|
|
||||||
|
|
||||||
get length(): i32 {
|
get length(): i32 {
|
||||||
return changetype<HEADER>(changetype<usize>(this) - HEADER_SIZE).payloadSize >> 1;
|
return changetype<BLOCK>(changetype<usize>(this) - BLOCK_OVERHEAD).rtSize >> 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@operator("[]") charAt(pos: i32): String {
|
@operator("[]") charAt(pos: i32): String {
|
||||||
assert(this !== null);
|
assert(this !== null);
|
||||||
if (<u32>pos >= <u32>this.length) return changetype<String>("");
|
if (<u32>pos >= <u32>this.length) return changetype<String>("");
|
||||||
var out = allocate(2);
|
var out = __alloc(2, idof<String>());
|
||||||
store<u16>(out, load<u16>(changetype<usize>(this) + (<usize>pos << 1)));
|
store<u16>(out, load<u16>(changetype<usize>(this) + (<usize>pos << 1)));
|
||||||
return changetype<String>(register(out, __runtime_id<String>()));
|
return changetype<String>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
charCodeAt(pos: i32): i32 {
|
charCodeAt(pos: i32): i32 {
|
||||||
@ -75,10 +68,10 @@ import { ArrayBufferView } from "./arraybuffer";
|
|||||||
var otherSize: isize = other.length << 1;
|
var otherSize: isize = other.length << 1;
|
||||||
var outSize: usize = thisSize + otherSize;
|
var outSize: usize = thisSize + otherSize;
|
||||||
if (outSize == 0) return changetype<String>("");
|
if (outSize == 0) return changetype<String>("");
|
||||||
var out = allocate(outSize);
|
var out = __alloc(outSize, idof<String>());
|
||||||
memory.copy(out, changetype<usize>(this), thisSize);
|
memory.copy(out, changetype<usize>(this), thisSize);
|
||||||
memory.copy(out + thisSize, changetype<usize>(other), otherSize);
|
memory.copy(out + thisSize, changetype<usize>(other), otherSize);
|
||||||
return changetype<String>(register(out, __runtime_id<String>()));
|
return changetype<String>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
endsWith(searchString: String, endPosition: i32 = String.MAX_LENGTH): bool {
|
endsWith(searchString: String, endPosition: i32 = String.MAX_LENGTH): bool {
|
||||||
@ -194,9 +187,9 @@ import { ArrayBufferView } from "./arraybuffer";
|
|||||||
if (intStart < 0) intStart = max(size + intStart, 0);
|
if (intStart < 0) intStart = max(size + intStart, 0);
|
||||||
var resultLength = min(max(end, 0), size - intStart);
|
var resultLength = min(max(end, 0), size - intStart);
|
||||||
if (resultLength <= 0) return changetype<String>("");
|
if (resultLength <= 0) return changetype<String>("");
|
||||||
var out = allocate(resultLength << 1);
|
var out = __alloc(resultLength << 1, idof<String>());
|
||||||
memory.copy(out, changetype<usize>(this) + intStart, resultLength);
|
memory.copy(out, changetype<usize>(this) + intStart, resultLength);
|
||||||
return changetype<String>(register(out, __runtime_id<String>()));
|
return changetype<String>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
substring(start: i32, end: i32 = i32.MAX_VALUE): String {
|
substring(start: i32, end: i32 = i32.MAX_VALUE): String {
|
||||||
@ -209,9 +202,9 @@ import { ArrayBufferView } from "./arraybuffer";
|
|||||||
len = toPos - fromPos;
|
len = toPos - fromPos;
|
||||||
if (!len) return changetype<String>("");
|
if (!len) return changetype<String>("");
|
||||||
if (!fromPos && toPos == this.length << 1) return this;
|
if (!fromPos && toPos == this.length << 1) return this;
|
||||||
var out = allocate(len);
|
var out = __alloc(len, idof<String>());
|
||||||
memory.copy(out, changetype<usize>(this) + fromPos, len);
|
memory.copy(out, changetype<usize>(this) + fromPos, len);
|
||||||
return changetype<String>(register(out, __runtime_id<String>()));
|
return changetype<String>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
trim(): String {
|
trim(): String {
|
||||||
@ -237,9 +230,9 @@ import { ArrayBufferView } from "./arraybuffer";
|
|||||||
}
|
}
|
||||||
if (!size) return changetype<String>("");
|
if (!size) return changetype<String>("");
|
||||||
if (!start && size == length << 1) return this;
|
if (!start && size == length << 1) return this;
|
||||||
var out = allocate(size);
|
var out = __alloc(size, idof<String>());
|
||||||
memory.copy(out, changetype<usize>(this) + offset, size);
|
memory.copy(out, changetype<usize>(this) + offset, size);
|
||||||
return changetype<String>(register(out, __runtime_id<String>()));
|
return changetype<String>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
@inline
|
@inline
|
||||||
@ -267,9 +260,9 @@ import { ArrayBufferView } from "./arraybuffer";
|
|||||||
if (!offset) return this;
|
if (!offset) return this;
|
||||||
size -= offset;
|
size -= offset;
|
||||||
if (!size) return changetype<String>("");
|
if (!size) return changetype<String>("");
|
||||||
var out = allocate(size);
|
var out = __alloc(size, idof<String>());
|
||||||
memory.copy(out, changetype<usize>(this) + offset, size);
|
memory.copy(out, changetype<usize>(this) + offset, size);
|
||||||
return changetype<String>(register(out, __runtime_id<String>()));
|
return changetype<String>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
trimEnd(): String {
|
trimEnd(): String {
|
||||||
@ -286,9 +279,9 @@ import { ArrayBufferView } from "./arraybuffer";
|
|||||||
}
|
}
|
||||||
if (!size) return changetype<String>("");
|
if (!size) return changetype<String>("");
|
||||||
if (size == originalSize) return this;
|
if (size == originalSize) return this;
|
||||||
var out = allocate(size);
|
var out = __alloc(size, idof<String>());
|
||||||
memory.copy(out, changetype<usize>(this), size);
|
memory.copy(out, changetype<usize>(this), size);
|
||||||
return changetype<String>(register(out, __runtime_id<String>()));
|
return changetype<String>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
padStart(targetLength: i32, padString: string = " "): String {
|
padStart(targetLength: i32, padString: string = " "): String {
|
||||||
@ -298,7 +291,7 @@ import { ArrayBufferView } from "./arraybuffer";
|
|||||||
var padSize = <usize>padString.length << 1;
|
var padSize = <usize>padString.length << 1;
|
||||||
if (targetSize < thisSize || !padSize) return this;
|
if (targetSize < thisSize || !padSize) return this;
|
||||||
var prependSize = targetSize - thisSize;
|
var prependSize = targetSize - thisSize;
|
||||||
var out = allocate(targetSize);
|
var out = __alloc(targetSize, idof<String>());
|
||||||
if (prependSize > padSize) {
|
if (prependSize > padSize) {
|
||||||
let repeatCount = (prependSize - 2) / padSize;
|
let repeatCount = (prependSize - 2) / padSize;
|
||||||
let restBase = repeatCount * padSize;
|
let restBase = repeatCount * padSize;
|
||||||
@ -309,7 +302,7 @@ import { ArrayBufferView } from "./arraybuffer";
|
|||||||
memory.copy(out, changetype<usize>(padString), prependSize);
|
memory.copy(out, changetype<usize>(padString), prependSize);
|
||||||
}
|
}
|
||||||
memory.copy(out + prependSize, changetype<usize>(this), thisSize);
|
memory.copy(out + prependSize, changetype<usize>(this), thisSize);
|
||||||
return changetype<String>(register(out, __runtime_id<String>()));
|
return changetype<String>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
padEnd(targetLength: i32, padString: string = " "): String {
|
padEnd(targetLength: i32, padString: string = " "): String {
|
||||||
@ -319,7 +312,7 @@ import { ArrayBufferView } from "./arraybuffer";
|
|||||||
var padSize = <usize>padString.length << 1;
|
var padSize = <usize>padString.length << 1;
|
||||||
if (targetSize < thisSize || !padSize) return this;
|
if (targetSize < thisSize || !padSize) return this;
|
||||||
var appendSize = targetSize - thisSize;
|
var appendSize = targetSize - thisSize;
|
||||||
var out = allocate(targetSize);
|
var out = __alloc(targetSize, idof<String>());
|
||||||
memory.copy(out, changetype<usize>(this), thisSize);
|
memory.copy(out, changetype<usize>(this), thisSize);
|
||||||
if (appendSize > padSize) {
|
if (appendSize > padSize) {
|
||||||
let repeatCount = (appendSize - 2) / padSize;
|
let repeatCount = (appendSize - 2) / padSize;
|
||||||
@ -330,7 +323,7 @@ import { ArrayBufferView } from "./arraybuffer";
|
|||||||
} else {
|
} else {
|
||||||
memory.copy(out + thisSize, changetype<usize>(padString), appendSize);
|
memory.copy(out + thisSize, changetype<usize>(padString), appendSize);
|
||||||
}
|
}
|
||||||
return changetype<String>(register(out, __runtime_id<String>()));
|
return changetype<String>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
repeat(count: i32 = 0): String {
|
repeat(count: i32 = 0): String {
|
||||||
@ -344,9 +337,9 @@ import { ArrayBufferView } from "./arraybuffer";
|
|||||||
|
|
||||||
if (count == 0 || !length) return changetype<String>("");
|
if (count == 0 || !length) return changetype<String>("");
|
||||||
if (count == 1) return this;
|
if (count == 1) return this;
|
||||||
var out = allocate((length * count) << 1);
|
var out = __alloc((length * count) << 1, idof<String>());
|
||||||
memory.repeat(out, changetype<usize>(this), <usize>length << 1, count);
|
memory.repeat(out, changetype<usize>(this), <usize>length << 1, count);
|
||||||
return changetype<String>(register(out, __runtime_id<String>()));
|
return changetype<String>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
slice(beginIndex: i32, endIndex: i32 = i32.MAX_VALUE): String {
|
slice(beginIndex: i32, endIndex: i32 = i32.MAX_VALUE): String {
|
||||||
@ -355,68 +348,64 @@ import { ArrayBufferView } from "./arraybuffer";
|
|||||||
var end = endIndex < 0 ? max(endIndex + len, 0) : min(endIndex, len);
|
var end = endIndex < 0 ? max(endIndex + len, 0) : min(endIndex, len);
|
||||||
len = end - begin;
|
len = end - begin;
|
||||||
if (len <= 0) return changetype<String>("");
|
if (len <= 0) return changetype<String>("");
|
||||||
var out = allocate(len << 1);
|
var out = __alloc(len << 1, idof<String>());
|
||||||
memory.copy(out, changetype<usize>(this) + (<usize>begin << 1), <usize>len << 1);
|
memory.copy(out, changetype<usize>(this) + (<usize>begin << 1), <usize>len << 1);
|
||||||
return changetype<String>(register(out, __runtime_id<String>()));
|
return changetype<String>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
split(separator: String | null = null, limit: i32 = i32.MAX_VALUE): String[] {
|
split(separator: String | null = null, limit: i32 = i32.MAX_VALUE): String[] {
|
||||||
assert(this !== null);
|
assert(this !== null);
|
||||||
if (!limit) return NEWARRAY<String>(0);
|
if (!limit) return changetype<Array<String>>(__allocArray(0, alignof<String>(), idof<Array<String>>())); // retains
|
||||||
if (separator === null) return <String[]>[this];
|
if (separator === null) return <String[]>[this];
|
||||||
var length: isize = this.length;
|
var length: isize = this.length;
|
||||||
var sepLen: isize = separator.length;
|
var sepLen: isize = separator.length;
|
||||||
if (limit < 0) limit = i32.MAX_VALUE;
|
if (limit < 0) limit = i32.MAX_VALUE;
|
||||||
if (!sepLen) {
|
if (!sepLen) {
|
||||||
if (!length) return NEWARRAY<String>(0);
|
if (!length) return changetype<Array<String>>(__allocArray(0, alignof<String>(), idof<Array<String>>())); // retains
|
||||||
// split by chars
|
// split by chars
|
||||||
length = min<isize>(length, <isize>limit);
|
length = min<isize>(length, <isize>limit);
|
||||||
let result = NEWARRAY<String>(length);
|
let result = __allocArray(length, alignof<String>(), idof<Array<String>>());
|
||||||
let resultStart = changetype<ArrayBufferView>(result).dataStart;
|
let resultStart = changetype<ArrayBufferView>(result).dataStart;
|
||||||
for (let i: isize = 0; i < length; ++i) {
|
for (let i: isize = 0; i < length; ++i) {
|
||||||
let charStr = allocate(2);
|
let charStr = __alloc(2, idof<String>());
|
||||||
store<u16>(charStr, load<u16>(changetype<usize>(this) + (<usize>i << 1)));
|
store<u16>(charStr, load<u16>(changetype<usize>(this) + (<usize>i << 1)));
|
||||||
store<usize>(resultStart + (<usize>i << alignof<usize>()), charStr); // result[i] = charStr
|
store<usize>(resultStart + (<usize>i << alignof<usize>()), charStr); // result[i] = charStr
|
||||||
register(charStr, __runtime_id<String>());
|
if (isManaged<String>()) __retain(charStr);
|
||||||
if (isManaged<String>()) {
|
|
||||||
if (isDefined(__ref_link)) __ref_link(changetype<usize>(charStr), changetype<usize>(result));
|
|
||||||
if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(charStr));
|
|
||||||
}
|
}
|
||||||
}
|
return changetype<Array<String>>(result); // retains
|
||||||
return result;
|
|
||||||
} else if (!length) {
|
} else if (!length) {
|
||||||
let result = NEWARRAY<String>(1);
|
let result = __allocArray(1, alignof<String>(), idof<Array<String>>());
|
||||||
store<string>(changetype<ArrayBufferView>(result).dataStart, ""); // no need to register/link
|
store<usize>(changetype<ArrayBufferView>(result).dataStart, changetype<usize>("")); // static ""
|
||||||
return result;
|
return changetype<Array<String>>(result); // retains
|
||||||
}
|
}
|
||||||
var result = NEWARRAY<String>(0);
|
var result = changetype<Array<String>>(__allocArray(0, alignof<String>(), idof<Array<String>>())); // retains
|
||||||
var end = 0, start = 0, i = 0;
|
var end = 0, start = 0, i = 0;
|
||||||
while ((end = this.indexOf(separator, start)) != -1) {
|
while ((end = this.indexOf(separator, start)) != -1) {
|
||||||
let len = end - start;
|
let len = end - start;
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
let out = allocate(<usize>len << 1);
|
let out = __alloc(<usize>len << 1, idof<String>());
|
||||||
memory.copy(out, changetype<usize>(this) + (<usize>start << 1), <usize>len << 1);
|
memory.copy(out, changetype<usize>(this) + (<usize>start << 1), <usize>len << 1);
|
||||||
result.push(changetype<String>(register(out, __runtime_id<String>())));
|
result.push(changetype<String>(out));
|
||||||
} else {
|
} else {
|
||||||
result.push(changetype<String>(""));
|
result.push(changetype<String>(""));
|
||||||
}
|
}
|
||||||
if (++i == limit) return result;
|
if (++i == limit) return changetype<Array<String>>(result); // retains
|
||||||
start = end + sepLen;
|
start = end + sepLen;
|
||||||
}
|
}
|
||||||
if (!start) {
|
if (!start) { // also means: loop above didn't do anything
|
||||||
let result = NEWARRAY<String>(1);
|
result.push(this);
|
||||||
unchecked(result[0] = this);
|
return changetype<Array<String>>(result); // retains
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
var len = length - start;
|
var len = length - start;
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
let out = allocate(<usize>len << 1);
|
let out = __alloc(<usize>len << 1, idof<String>());
|
||||||
memory.copy(out, changetype<usize>(this) + (<usize>start << 1), <usize>len << 1);
|
memory.copy(out, changetype<usize>(this) + (<usize>start << 1), <usize>len << 1);
|
||||||
result.push(changetype<String>(register(out, __runtime_id<String>())));
|
result.push(changetype<String>(out)); // retains
|
||||||
} else {
|
} else {
|
||||||
result.push(changetype<String>(""));
|
result.push(changetype<String>("")); // static ""
|
||||||
}
|
}
|
||||||
return result;
|
return changetype<Array<String>>(result); // retains
|
||||||
|
// releases result
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(): String {
|
toString(): String {
|
||||||
@ -484,10 +473,10 @@ import { ArrayBufferView } from "./arraybuffer";
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert(ptrPos == len);
|
assert(ptrPos == len);
|
||||||
var out = allocate(bufPos);
|
var out = __alloc(bufPos, idof<String>());
|
||||||
memory.copy(changetype<usize>(out), buf, bufPos);
|
memory.copy(out, buf, bufPos);
|
||||||
memory.free(buf);
|
memory.free(buf);
|
||||||
return changetype<String>(register(out, __runtime_id<String>()));
|
return changetype<String>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
toUTF8(): usize {
|
toUTF8(): usize {
|
||||||
@ -553,7 +542,7 @@ export function parseFloat(str: String): f64 {
|
|||||||
var len: i32 = str.length;
|
var len: i32 = str.length;
|
||||||
if (!len) return NaN;
|
if (!len) return NaN;
|
||||||
|
|
||||||
var ptr = changetype<usize>(str) /* + HEAD -> offset */;
|
var ptr = changetype<usize>(str);
|
||||||
var code = <i32>load<u16>(ptr);
|
var code = <i32>load<u16>(ptr);
|
||||||
|
|
||||||
// determine sign
|
// determine sign
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { allocate, register } from "./util/runtime";
|
|
||||||
import { COMPARATOR, SORT as SORT_IMPL } from "./util/sort";
|
import { COMPARATOR, SORT as SORT_IMPL } from "./util/sort";
|
||||||
import { E_INDEXOUTOFRANGE } from "./util/error";
|
import { E_INDEXOUTOFRANGE } from "./util/error";
|
||||||
import { __runtime_id } from "./runtime";
|
import { idof } from "./builtins";
|
||||||
import { ArrayBufferView } from "./arraybuffer";
|
import { ArrayBufferView } from "./arraybuffer";
|
||||||
|
|
||||||
export class Int8Array extends ArrayBufferView {
|
export class Int8Array extends ArrayBufferView {
|
||||||
@ -962,13 +961,11 @@ function SUBARRAY<TArray extends ArrayBufferView, T>(
|
|||||||
else begin = min(begin, length);
|
else begin = min(begin, length);
|
||||||
if (end < 0) end = max(length + end, begin);
|
if (end < 0) end = max(length + end, begin);
|
||||||
else end = max(min(end, length), begin);
|
else end = max(min(end, length), begin);
|
||||||
var out = allocate(offsetof<TArray>());
|
var out = __alloc(offsetof<TArray>(), idof<TArray>());
|
||||||
var data = array.data;
|
changetype<ArrayBufferView>(out).data = array.data; // retains
|
||||||
var dataStart = array.dataStart;
|
changetype<ArrayBufferView>(out).dataStart = array.dataStart + (<usize>begin << alignof<T>());
|
||||||
changetype<ArrayBufferView>(out).data = data; // links
|
|
||||||
changetype<ArrayBufferView>(out).dataStart = dataStart + (<usize>begin << alignof<T>());
|
|
||||||
changetype<ArrayBufferView>(out).dataLength = (end - begin) << alignof<T>();
|
changetype<ArrayBufferView>(out).dataLength = (end - begin) << alignof<T>();
|
||||||
return changetype<TArray>(register(out, __runtime_id<TArray>()));
|
return changetype<TArray>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore: decorator
|
// @ts-ignore: decorator
|
||||||
|
@ -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
|
|
@ -1,6 +1,7 @@
|
|||||||
import { allocate, register, discard } from "./runtime";
|
/// <reference path="../rt/index.d.ts" />
|
||||||
|
|
||||||
|
import { idof } from "../builtins";
|
||||||
import { CharCode } from "./string";
|
import { CharCode } from "./string";
|
||||||
import { __runtime_id } from "../runtime";
|
|
||||||
import { ArrayBufferView } from "../arraybuffer";
|
import { ArrayBufferView } from "../arraybuffer";
|
||||||
|
|
||||||
// @ts-ignore: decorator
|
// @ts-ignore: decorator
|
||||||
@ -264,10 +265,10 @@ export function utoa32(value: u32): String {
|
|||||||
if (!value) return "0";
|
if (!value) return "0";
|
||||||
|
|
||||||
var decimals = decimalCount32(value);
|
var decimals = decimalCount32(value);
|
||||||
var out = allocate(decimals << 1);
|
var out = __alloc(decimals << 1, idof<String>());
|
||||||
|
|
||||||
utoa32_core(changetype<usize>(out), value, decimals);
|
utoa32_core(changetype<usize>(out), value, decimals);
|
||||||
return changetype<String>(register(out, __runtime_id<String>()));
|
return changetype<String>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
export function itoa32(value: i32): String {
|
export function itoa32(value: i32): String {
|
||||||
@ -277,12 +278,12 @@ export function itoa32(value: i32): String {
|
|||||||
if (sign) value = -value;
|
if (sign) value = -value;
|
||||||
|
|
||||||
var decimals = decimalCount32(value) + u32(sign);
|
var decimals = decimalCount32(value) + u32(sign);
|
||||||
var out = allocate(decimals << 1);
|
var out = __alloc(decimals << 1, idof<String>());
|
||||||
|
|
||||||
utoa32_core(changetype<usize>(out), value, decimals);
|
utoa32_core(changetype<usize>(out), value, decimals);
|
||||||
if (sign) store<u16>(changetype<usize>(out), CharCode.MINUS);
|
if (sign) store<u16>(changetype<usize>(out), CharCode.MINUS);
|
||||||
|
|
||||||
return changetype<String>(register(out, __runtime_id<String>()));
|
return changetype<String>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
export function utoa64(value: u64): String {
|
export function utoa64(value: u64): String {
|
||||||
@ -292,14 +293,14 @@ export function utoa64(value: u64): String {
|
|||||||
if (value <= u32.MAX_VALUE) {
|
if (value <= u32.MAX_VALUE) {
|
||||||
let val32 = <u32>value;
|
let val32 = <u32>value;
|
||||||
let decimals = decimalCount32(val32);
|
let decimals = decimalCount32(val32);
|
||||||
out = allocate(decimals << 1);
|
out = __alloc(decimals << 1, idof<String>());
|
||||||
utoa32_core(out, val32, decimals);
|
utoa32_core(out, val32, decimals);
|
||||||
} else {
|
} else {
|
||||||
let decimals = decimalCount64(value);
|
let decimals = decimalCount64(value);
|
||||||
out = allocate(decimals << 1);
|
out = __alloc(decimals << 1, idof<String>());
|
||||||
utoa64_core(changetype<usize>(out), value, decimals);
|
utoa64_core(changetype<usize>(out), value, decimals);
|
||||||
}
|
}
|
||||||
return changetype<String>(register(out, __runtime_id<String>()));
|
return changetype<String>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
export function itoa64(value: i64): String {
|
export function itoa64(value: i64): String {
|
||||||
@ -312,16 +313,16 @@ export function itoa64(value: i64): String {
|
|||||||
if (<u64>value <= <u64>u32.MAX_VALUE) {
|
if (<u64>value <= <u64>u32.MAX_VALUE) {
|
||||||
let val32 = <u32>value;
|
let val32 = <u32>value;
|
||||||
let decimals = decimalCount32(val32) + u32(sign);
|
let decimals = decimalCount32(val32) + u32(sign);
|
||||||
out = allocate(decimals << 1);
|
out = __alloc(decimals << 1, idof<String>());
|
||||||
utoa32_core(changetype<usize>(out), val32, decimals);
|
utoa32_core(changetype<usize>(out), val32, decimals);
|
||||||
} else {
|
} else {
|
||||||
let decimals = decimalCount64(value) + u32(sign);
|
let decimals = decimalCount64(value) + u32(sign);
|
||||||
out = allocate(decimals << 1);
|
out = __alloc(decimals << 1, idof<String>());
|
||||||
utoa64_core(changetype<usize>(out), value, decimals);
|
utoa64_core(changetype<usize>(out), value, decimals);
|
||||||
}
|
}
|
||||||
if (sign) store<u16>(changetype<usize>(out), CharCode.MINUS);
|
if (sign) store<u16>(changetype<usize>(out), CharCode.MINUS);
|
||||||
|
|
||||||
return changetype<String>(register(out, __runtime_id<String>()));
|
return changetype<String>(out); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
export function itoa<T extends number>(value: T): String {
|
export function itoa<T extends number>(value: T): String {
|
||||||
@ -626,11 +627,12 @@ export function dtoa(value: f64): String {
|
|||||||
if (isNaN<f64>(value)) return "NaN";
|
if (isNaN<f64>(value)) return "NaN";
|
||||||
return select<String>("-Infinity", "Infinity", value < 0);
|
return select<String>("-Infinity", "Infinity", value < 0);
|
||||||
}
|
}
|
||||||
var temp = allocate(MAX_DOUBLE_LENGTH << 1);
|
var temp = __alloc(MAX_DOUBLE_LENGTH << 1, idof<String>());
|
||||||
var length = dtoa_core(temp, value);
|
var length = dtoa_core(temp, value);
|
||||||
var result = changetype<String>(temp).substring(0, length); // registers
|
if (length < MAX_DOUBLE_LENGTH) {
|
||||||
discard(temp);
|
return changetype<String>(temp).substring(0, length); // retains/releases `temp`, retains return
|
||||||
return result;
|
}
|
||||||
|
return changetype<String>(temp); // retains
|
||||||
}
|
}
|
||||||
|
|
||||||
export function itoa_stream<T extends number>(buffer: usize, offset: usize, value: T): u32 {
|
export function itoa_stream<T extends number>(buffer: usize, offset: usize, value: T): u32 {
|
||||||
@ -692,8 +694,7 @@ export function dtoa_stream(buffer: usize, offset: usize, value: f64): u32 {
|
|||||||
} else {
|
} else {
|
||||||
let sign = i32(value < 0);
|
let sign = i32(value < 0);
|
||||||
let len = 8 + sign;
|
let len = 8 + sign;
|
||||||
let source = changetype<usize>(select<String>("-Infinity", "Infinity", sign));
|
memory.copy(buffer, changetype<usize>(select<String>("-Infinity", "Infinity", sign)), len << 1);
|
||||||
memory.copy(buffer, source, len << 1);
|
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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));
|
|
||||||
}
|
|
@ -28,8 +28,8 @@ fetch("untouched.wasm").then(result =>
|
|||||||
).then(result => {
|
).then(result => {
|
||||||
exports = result.instance.exports;
|
exports = result.instance.exports;
|
||||||
U32 = new Uint32Array(exports.memory.buffer);
|
U32 = new Uint32Array(exports.memory.buffer);
|
||||||
var first = exports.__rt_allocate(255);
|
var first = exports.__alloc(255);
|
||||||
exports.__rt_free(first);
|
exports.__free(first);
|
||||||
ROOT = first - 17;
|
ROOT = first - 17;
|
||||||
while (!U32[ROOT >> 2]) --ROOT; // find tail
|
while (!U32[ROOT >> 2]) --ROOT; // find tail
|
||||||
ROOT -= (1 + FL_BITS + HL_SIZE) << 2;
|
ROOT -= (1 + FL_BITS + HL_SIZE) << 2;
|
||||||
@ -113,7 +113,7 @@ function update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function allocate(size) {
|
function allocate(size) {
|
||||||
var ptr = exports.__rt_allocate(size);
|
var ptr = exports.__alloc(size);
|
||||||
if (!ptr) {
|
if (!ptr) {
|
||||||
alert("should not happen");
|
alert("should not happen");
|
||||||
return;
|
return;
|
||||||
@ -127,7 +127,7 @@ function allocate(size) {
|
|||||||
var er = document.createElement("button");
|
var er = document.createElement("button");
|
||||||
er.innerText = "realloc";
|
er.innerText = "realloc";
|
||||||
er.onclick = function() {
|
er.onclick = function() {
|
||||||
ptr = exports.__rt_reallocate(ptr, es.value >>> 0);
|
ptr = exports.__realloc(ptr, es.value >>> 0);
|
||||||
update();
|
update();
|
||||||
};
|
};
|
||||||
el.appendChild(er);
|
el.appendChild(er);
|
||||||
@ -136,7 +136,7 @@ function allocate(size) {
|
|||||||
ef.className = "free";
|
ef.className = "free";
|
||||||
el.appendChild(ef);
|
el.appendChild(ef);
|
||||||
ef.onclick = function() {
|
ef.onclick = function() {
|
||||||
exports.__rt_free(ptr);
|
exports.__free(ptr);
|
||||||
document.getElementById("segs").removeChild(el);
|
document.getElementById("segs").removeChild(el);
|
||||||
update();
|
update();
|
||||||
};
|
};
|
||||||
|
@ -20,13 +20,13 @@
|
|||||||
(global $~lib/rt/pure/ROOTS (mut i32) (i32.const 0))
|
(global $~lib/rt/pure/ROOTS (mut i32) (i32.const 0))
|
||||||
(export "memory" (memory $0))
|
(export "memory" (memory $0))
|
||||||
(export "main" (func $assembly/index/main))
|
(export "main" (func $assembly/index/main))
|
||||||
(export "__rt_allocate" (func $~lib/rt/index/__rt_allocate))
|
(export "__alloc" (func $~lib/rt/index/__alloc))
|
||||||
(export "__rt_reallocate" (func $~lib/rt/index/__rt_reallocate))
|
(export "__realloc" (func $~lib/rt/index/__realloc))
|
||||||
(export "__rt_free" (func $~lib/rt/index/__rt_free))
|
(export "__free" (func $~lib/rt/index/__free))
|
||||||
(export "__rt_retain" (func $~lib/rt/index/__rt_retain))
|
(export "__retain" (func $~lib/rt/index/__retain))
|
||||||
(export "__rt_release" (func $~lib/rt/index/__rt_release))
|
(export "__release" (func $~lib/rt/index/__release))
|
||||||
(export "__rt_collect" (func $~lib/rt/index/__rt_collect))
|
(export "__collect" (func $~lib/rt/index/__collect))
|
||||||
(export "__rt_typeinfo" (func $~lib/rt/common/__rt_typeinfo))
|
(export "__info" (func $~lib/rt/common/__info))
|
||||||
(func $assembly/index/main (; 1 ;) (type $FUNCSIG$v)
|
(func $assembly/index/main (; 1 ;) (type $FUNCSIG$v)
|
||||||
global.get $~lib/started
|
global.get $~lib/started
|
||||||
i32.eqz
|
i32.eqz
|
||||||
@ -778,7 +778,7 @@
|
|||||||
call $~lib/rt/tlsf/prepareBlock
|
call $~lib/rt/tlsf/prepareBlock
|
||||||
local.get $2
|
local.get $2
|
||||||
)
|
)
|
||||||
(func $~lib/rt/index/__rt_allocate (; 11 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
(func $~lib/rt/index/__alloc (; 11 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
||||||
(local $2 i32)
|
(local $2 i32)
|
||||||
global.get $~lib/rt/tlsf/ROOT
|
global.get $~lib/rt/tlsf/ROOT
|
||||||
local.tee $2
|
local.tee $2
|
||||||
@ -1073,7 +1073,7 @@
|
|||||||
call $~lib/rt/tlsf/insertBlock
|
call $~lib/rt/tlsf/insertBlock
|
||||||
local.get $3
|
local.get $3
|
||||||
)
|
)
|
||||||
(func $~lib/rt/index/__rt_reallocate (; 14 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
(func $~lib/rt/index/__realloc (; 14 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
||||||
global.get $~lib/rt/tlsf/ROOT
|
global.get $~lib/rt/tlsf/ROOT
|
||||||
local.get $0
|
local.get $0
|
||||||
i32.const 16
|
i32.const 16
|
||||||
@ -1094,14 +1094,14 @@
|
|||||||
local.get $1
|
local.get $1
|
||||||
call $~lib/rt/tlsf/insertBlock
|
call $~lib/rt/tlsf/insertBlock
|
||||||
)
|
)
|
||||||
(func $~lib/rt/index/__rt_free (; 16 ;) (type $FUNCSIG$vi) (param $0 i32)
|
(func $~lib/rt/index/__free (; 16 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||||
global.get $~lib/rt/tlsf/ROOT
|
global.get $~lib/rt/tlsf/ROOT
|
||||||
local.get $0
|
local.get $0
|
||||||
i32.const 16
|
i32.const 16
|
||||||
i32.sub
|
i32.sub
|
||||||
call $~lib/rt/tlsf/freeBlock
|
call $~lib/rt/tlsf/freeBlock
|
||||||
)
|
)
|
||||||
(func $~lib/rt/index/__rt_retain (; 17 ;) (type $FUNCSIG$vi) (param $0 i32)
|
(func $~lib/rt/index/__retain (; 17 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||||
local.get $0
|
local.get $0
|
||||||
if
|
if
|
||||||
local.get $0
|
local.get $0
|
||||||
@ -1115,7 +1115,7 @@
|
|||||||
i32.store offset=4
|
i32.store offset=4
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
(func $~lib/rt/index/__rt_release (; 18 ;) (type $FUNCSIG$vi) (param $0 i32)
|
(func $~lib/rt/index/__release (; 18 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||||
local.get $0
|
local.get $0
|
||||||
if
|
if
|
||||||
local.get $0
|
local.get $0
|
||||||
@ -1333,10 +1333,10 @@
|
|||||||
local.get $5
|
local.get $5
|
||||||
global.set $~lib/rt/pure/CUR
|
global.set $~lib/rt/pure/CUR
|
||||||
)
|
)
|
||||||
(func $~lib/rt/index/__rt_collect (; 23 ;) (type $FUNCSIG$v)
|
(func $~lib/rt/index/__collect (; 23 ;) (type $FUNCSIG$v)
|
||||||
call $~lib/rt/pure/collectCycles
|
call $~lib/rt/pure/collectCycles
|
||||||
)
|
)
|
||||||
(func $~lib/rt/common/__rt_typeinfo (; 24 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
|
(func $~lib/rt/common/__info (; 24 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
|
||||||
(local $1 i32)
|
(local $1 i32)
|
||||||
i32.const 104
|
i32.const 104
|
||||||
local.set $1
|
local.set $1
|
||||||
|
Binary file not shown.
@ -26,13 +26,13 @@
|
|||||||
(global $~lib/memory/HEAP_BASE i32 (i32.const 336))
|
(global $~lib/memory/HEAP_BASE i32 (i32.const 336))
|
||||||
(export "memory" (memory $0))
|
(export "memory" (memory $0))
|
||||||
(export "main" (func $assembly/index/main))
|
(export "main" (func $assembly/index/main))
|
||||||
(export "__rt_allocate" (func $~lib/rt/index/__rt_allocate))
|
(export "__alloc" (func $~lib/rt/index/__alloc))
|
||||||
(export "__rt_reallocate" (func $~lib/rt/index/__rt_reallocate))
|
(export "__realloc" (func $~lib/rt/index/__realloc))
|
||||||
(export "__rt_free" (func $~lib/rt/index/__rt_free))
|
(export "__free" (func $~lib/rt/index/__free))
|
||||||
(export "__rt_retain" (func $~lib/rt/index/__rt_retain))
|
(export "__retain" (func $~lib/rt/index/__retain))
|
||||||
(export "__rt_release" (func $~lib/rt/index/__rt_release))
|
(export "__release" (func $~lib/rt/index/__release))
|
||||||
(export "__rt_collect" (func $~lib/rt/index/__rt_collect))
|
(export "__collect" (func $~lib/rt/index/__collect))
|
||||||
(export "__rt_typeinfo" (func $~lib/rt/common/__rt_typeinfo))
|
(export "__info" (func $~lib/rt/common/__info))
|
||||||
(func $assembly/index/main (; 1 ;) (type $FUNCSIG$v)
|
(func $assembly/index/main (; 1 ;) (type $FUNCSIG$v)
|
||||||
global.get $~lib/started
|
global.get $~lib/started
|
||||||
i32.eqz
|
i32.eqz
|
||||||
@ -1369,7 +1369,7 @@
|
|||||||
call $~lib/rt/tlsf/prepareBlock
|
call $~lib/rt/tlsf/prepareBlock
|
||||||
local.get $3
|
local.get $3
|
||||||
)
|
)
|
||||||
(func $~lib/rt/index/__rt_allocate (; 11 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
(func $~lib/rt/index/__alloc (; 11 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
||||||
(local $2 i32)
|
(local $2 i32)
|
||||||
(local $3 i32)
|
(local $3 i32)
|
||||||
global.get $~lib/rt/tlsf/ROOT
|
global.get $~lib/rt/tlsf/ROOT
|
||||||
@ -1736,13 +1736,13 @@
|
|||||||
call $~lib/rt/tlsf/insertBlock
|
call $~lib/rt/tlsf/insertBlock
|
||||||
local.get $8
|
local.get $8
|
||||||
)
|
)
|
||||||
(func $~lib/rt/index/__rt_reallocate (; 14 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
(func $~lib/rt/index/__realloc (; 14 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
||||||
global.get $~lib/rt/tlsf/ROOT
|
global.get $~lib/rt/tlsf/ROOT
|
||||||
i32.eqz
|
i32.eqz
|
||||||
if
|
if
|
||||||
i32.const 0
|
i32.const 0
|
||||||
i32.const 72
|
i32.const 72
|
||||||
i32.const 23
|
i32.const 21
|
||||||
i32.const 13
|
i32.const 13
|
||||||
call $~lib/builtins/abort
|
call $~lib/builtins/abort
|
||||||
unreachable
|
unreachable
|
||||||
@ -1762,7 +1762,7 @@
|
|||||||
if
|
if
|
||||||
i32.const 0
|
i32.const 0
|
||||||
i32.const 72
|
i32.const 72
|
||||||
i32.const 24
|
i32.const 22
|
||||||
i32.const 2
|
i32.const 2
|
||||||
call $~lib/builtins/abort
|
call $~lib/builtins/abort
|
||||||
unreachable
|
unreachable
|
||||||
@ -1803,13 +1803,13 @@
|
|||||||
local.get $1
|
local.get $1
|
||||||
call $~lib/rt/tlsf/insertBlock
|
call $~lib/rt/tlsf/insertBlock
|
||||||
)
|
)
|
||||||
(func $~lib/rt/index/__rt_free (; 16 ;) (type $FUNCSIG$vi) (param $0 i32)
|
(func $~lib/rt/index/__free (; 16 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||||
global.get $~lib/rt/tlsf/ROOT
|
global.get $~lib/rt/tlsf/ROOT
|
||||||
i32.eqz
|
i32.eqz
|
||||||
if
|
if
|
||||||
i32.const 0
|
i32.const 0
|
||||||
i32.const 72
|
i32.const 72
|
||||||
i32.const 31
|
i32.const 29
|
||||||
i32.const 13
|
i32.const 13
|
||||||
call $~lib/builtins/abort
|
call $~lib/builtins/abort
|
||||||
unreachable
|
unreachable
|
||||||
@ -1829,7 +1829,7 @@
|
|||||||
if
|
if
|
||||||
i32.const 0
|
i32.const 0
|
||||||
i32.const 72
|
i32.const 72
|
||||||
i32.const 32
|
i32.const 30
|
||||||
i32.const 2
|
i32.const 2
|
||||||
call $~lib/builtins/abort
|
call $~lib/builtins/abort
|
||||||
unreachable
|
unreachable
|
||||||
@ -1873,7 +1873,7 @@
|
|||||||
i32.add
|
i32.add
|
||||||
i32.store offset=4
|
i32.store offset=4
|
||||||
)
|
)
|
||||||
(func $~lib/rt/index/__rt_retain (; 18 ;) (type $FUNCSIG$vi) (param $0 i32)
|
(func $~lib/rt/index/__retain (; 18 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||||
local.get $0
|
local.get $0
|
||||||
if
|
if
|
||||||
local.get $0
|
local.get $0
|
||||||
@ -2046,7 +2046,7 @@
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
(func $~lib/rt/index/__rt_release (; 25 ;) (type $FUNCSIG$vi) (param $0 i32)
|
(func $~lib/rt/index/__release (; 25 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||||
local.get $0
|
local.get $0
|
||||||
if
|
if
|
||||||
local.get $0
|
local.get $0
|
||||||
@ -2309,10 +2309,10 @@
|
|||||||
local.get $0
|
local.get $0
|
||||||
global.set $~lib/rt/pure/CUR
|
global.set $~lib/rt/pure/CUR
|
||||||
)
|
)
|
||||||
(func $~lib/rt/index/__rt_collect (; 31 ;) (type $FUNCSIG$v)
|
(func $~lib/rt/index/__collect (; 31 ;) (type $FUNCSIG$v)
|
||||||
call $~lib/rt/pure/collectCycles
|
call $~lib/rt/pure/collectCycles
|
||||||
)
|
)
|
||||||
(func $~lib/rt/common/__rt_typeinfo (; 32 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
|
(func $~lib/rt/common/__info (; 32 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
|
||||||
(local $1 i32)
|
(local $1 i32)
|
||||||
global.get $~lib/runtime/RTTI_BASE
|
global.get $~lib/runtime/RTTI_BASE
|
||||||
local.set $1
|
local.set $1
|
||||||
|
Loading…
x
Reference in New Issue
Block a user