mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-06-18 17:31:29 +00:00
Implement reference counting (#592)
This commit is contained in:
@ -1,43 +1,75 @@
|
||||
"use strict";
|
||||
|
||||
const hasBigInt64 = typeof BigUint64Array !== "undefined";
|
||||
const thisPtr = Symbol();
|
||||
// Runtime header offsets
|
||||
const ID_OFFSET = -8;
|
||||
const SIZE_OFFSET = -4;
|
||||
|
||||
// Runtime ids
|
||||
const ARRAYBUFFER_ID = 0;
|
||||
const STRING_ID = 1;
|
||||
const ARRAYBUFFERVIEW_ID = 2;
|
||||
|
||||
// Runtime type information
|
||||
const ARRAYBUFFERVIEW = 1 << 0;
|
||||
const ARRAY = 1 << 1;
|
||||
const SET = 1 << 2;
|
||||
const MAP = 1 << 3;
|
||||
const VAL_ALIGN = 1 << 5;
|
||||
const VAL_SIGNED = 1 << 10;
|
||||
const VAL_FLOAT = 1 << 11;
|
||||
const VAL_NULLABLE = 1 << 12;
|
||||
const VAL_MANAGED = 1 << 13;
|
||||
const KEY_ALIGN = 1 << 14;
|
||||
const KEY_SIGNED = 1 << 19;
|
||||
const KEY_FLOAT = 1 << 20;
|
||||
const KEY_NULLABLE = 1 << 21;
|
||||
const KEY_MANAGED = 1 << 22;
|
||||
|
||||
// Array(BufferView) layout
|
||||
const ARRAYBUFFERVIEW_BUFFER_OFFSET = 0;
|
||||
const ARRAYBUFFERVIEW_DATASTART_OFFSET = 4;
|
||||
const ARRAYBUFFERVIEW_DATALENGTH_OFFSET = 8;
|
||||
const ARRAYBUFFERVIEW_SIZE = 12;
|
||||
const ARRAY_LENGTH_OFFSET = 12;
|
||||
const ARRAY_SIZE = 16;
|
||||
|
||||
const BIGINT = typeof BigUint64Array !== "undefined";
|
||||
const THIS = Symbol();
|
||||
const CHUNKSIZE = 1024;
|
||||
|
||||
/** Gets a string from an U32 and an U16 view on a memory. */
|
||||
function getStringImpl(U32, U16, ptr) {
|
||||
var dataLength = U32[ptr >>> 2];
|
||||
var dataOffset = (ptr + 4) >>> 1;
|
||||
var dataRemain = dataLength;
|
||||
var parts = [];
|
||||
const chunkSize = 1024;
|
||||
while (dataRemain > chunkSize) {
|
||||
let last = U16[dataOffset + chunkSize - 1];
|
||||
let size = last >= 0xD800 && last < 0xDC00 ? chunkSize - 1 : chunkSize;
|
||||
let part = U16.subarray(dataOffset, dataOffset += size);
|
||||
parts.push(String.fromCharCode.apply(String, part));
|
||||
dataRemain -= size;
|
||||
}
|
||||
return parts.join("") + String.fromCharCode.apply(String, U16.subarray(dataOffset, dataOffset + dataRemain));
|
||||
function getStringImpl(U32, U16, ref) {
|
||||
var length = U32[(ref + SIZE_OFFSET) >>> 2] >>> 1;
|
||||
var offset = ref >>> 1;
|
||||
if (length <= CHUNKSIZE) return String.fromCharCode.apply(String, U16.subarray(offset, offset + length));
|
||||
const parts = [];
|
||||
do {
|
||||
const last = U16[offset + CHUNKSIZE - 1];
|
||||
const size = last >= 0xD800 && last < 0xDC00 ? CHUNKSIZE - 1 : CHUNKSIZE;
|
||||
parts.push(String.fromCharCode.apply(String, U16.subarray(offset, offset += size)));
|
||||
length -= size;
|
||||
} while (length > CHUNKSIZE);
|
||||
return parts.join("") + String.fromCharCode.apply(String, U16.subarray(offset, offset + length));
|
||||
}
|
||||
|
||||
/** Prepares the base module prior to instantiation. */
|
||||
function preInstantiate(imports) {
|
||||
var baseModule = {};
|
||||
const baseModule = {};
|
||||
|
||||
function getString(memory, ptr) {
|
||||
function getString(memory, ref) {
|
||||
if (!memory) return "<yet unknown>";
|
||||
var buffer = memory.buffer;
|
||||
return getStringImpl(new Uint32Array(buffer), new Uint16Array(buffer), ptr);
|
||||
const buffer = memory.buffer;
|
||||
return getStringImpl(new Uint32Array(buffer), new Uint16Array(buffer), ref);
|
||||
}
|
||||
|
||||
// add common imports used by stdlib for convenience
|
||||
var env = (imports.env = imports.env || {});
|
||||
const env = (imports.env = imports.env || {});
|
||||
env.abort = env.abort || function abort(mesg, file, line, colm) {
|
||||
var memory = baseModule.memory || env.memory; // prefer exported, otherwise try imported
|
||||
const memory = baseModule.memory || env.memory; // prefer exported, otherwise try imported
|
||||
throw Error("abort: " + getString(memory, mesg) + " at " + getString(memory, file) + ":" + line + ":" + colm);
|
||||
}
|
||||
env.trace = env.trace || function trace(mesg, n) {
|
||||
var memory = baseModule.memory || env.memory;
|
||||
const memory = baseModule.memory || env.memory;
|
||||
console.log("trace: " + getString(memory, mesg) + (n ? " " : "") + Array.prototype.slice.call(arguments, 2, 2 + n).join(", "));
|
||||
}
|
||||
imports.Math = imports.Math || Math;
|
||||
@ -48,13 +80,12 @@ function preInstantiate(imports) {
|
||||
|
||||
/** Prepares the final module once instantiation is complete. */
|
||||
function postInstantiate(baseModule, instance) {
|
||||
var rawExports = instance.exports;
|
||||
var memory = rawExports.memory;
|
||||
var memory_allocate = rawExports["memory.allocate"];
|
||||
var memory_fill = rawExports["memory.fill"];
|
||||
var memory_free = rawExports["memory.free"];
|
||||
var table = rawExports.table;
|
||||
var setargc = rawExports._setargc || function() {};
|
||||
const rawExports = instance.exports;
|
||||
const memory = rawExports.memory;
|
||||
const table = rawExports.table;
|
||||
const alloc = rawExports["__alloc"];
|
||||
const retain = rawExports["__retain"];
|
||||
const rttiBase = rawExports["__rtti_base"] || ~0; // oob if not present
|
||||
|
||||
// Provide views for all sorts of basic values
|
||||
var buffer, I8, U8, I16, U16, I32, U32, F32, F64, I64, U64;
|
||||
@ -70,7 +101,7 @@ function postInstantiate(baseModule, instance) {
|
||||
U16 = new Uint16Array(buffer);
|
||||
I32 = new Int32Array(buffer);
|
||||
U32 = new Uint32Array(buffer);
|
||||
if (hasBigInt64) {
|
||||
if (BIGINT) {
|
||||
I64 = new BigInt64Array(buffer);
|
||||
U64 = new BigUint64Array(buffer);
|
||||
}
|
||||
@ -80,109 +111,120 @@ function postInstantiate(baseModule, instance) {
|
||||
}
|
||||
checkMem();
|
||||
|
||||
/** Allocates a new string in the module's memory and returns its pointer. */
|
||||
function newString(str) {
|
||||
var dataLength = str.length;
|
||||
var ptr = memory_allocate(4 + (dataLength << 1));
|
||||
var dataOffset = (4 + ptr) >>> 1;
|
||||
checkMem();
|
||||
U32[ptr >>> 2] = dataLength;
|
||||
for (let i = 0; i < dataLength; ++i) U16[dataOffset + i] = str.charCodeAt(i);
|
||||
return ptr;
|
||||
/** Gets the runtime type info for the given id. */
|
||||
function getInfo(id) {
|
||||
const count = U32[rttiBase >>> 2];
|
||||
if ((id >>>= 0) >= count) throw Error("invalid id: " + id);
|
||||
return U32[(rttiBase + 4 >>> 2) + id * 2];
|
||||
}
|
||||
|
||||
baseModule.newString = newString;
|
||||
|
||||
/** Gets a string from the module's memory by its pointer. */
|
||||
function getString(ptr) {
|
||||
checkMem();
|
||||
return getStringImpl(U32, U16, ptr);
|
||||
/** Gets the runtime base id for the given id. */
|
||||
function getBase(id) {
|
||||
const count = U32[rttiBase >>> 2];
|
||||
if ((id >>>= 0) >= count) throw Error("invalid id: " + id);
|
||||
return U32[(rttiBase + 4 >>> 2) + id * 2 + 1];
|
||||
}
|
||||
|
||||
baseModule.getString = getString;
|
||||
|
||||
function computeBufferSize(byteLength) {
|
||||
const HEADER_SIZE = 8;
|
||||
return 1 << (32 - Math.clz32(byteLength + HEADER_SIZE - 1));
|
||||
/** Gets the runtime alignment of a collection's values or keys. */
|
||||
function getAlign(which, info) {
|
||||
return 31 - Math.clz32((info / which) & 31); // -1 if none
|
||||
}
|
||||
|
||||
/** Creates a new typed array in the module's memory and returns its pointer. */
|
||||
function newArray(view, length, unsafe) {
|
||||
var ctor = view.constructor;
|
||||
if (ctor === Function) { // TypedArray constructor created in memory
|
||||
ctor = view;
|
||||
view = null;
|
||||
} else { // TypedArray instance copied into memory
|
||||
if (length === undefined) length = view.length;
|
||||
}
|
||||
var elementSize = ctor.BYTES_PER_ELEMENT;
|
||||
if (!elementSize) throw Error("not a typed array");
|
||||
var byteLength = elementSize * length;
|
||||
var ptr = memory_allocate(12); // TypedArray header
|
||||
var buf = memory_allocate(computeBufferSize(byteLength)); // ArrayBuffer
|
||||
/** Allocates a new string in the module's memory and returns its retained pointer. */
|
||||
function __allocString(str) {
|
||||
const length = str.length;
|
||||
const ref = alloc(length << 1, STRING_ID);
|
||||
checkMem();
|
||||
U32[ ptr >>> 2] = buf; // .buffer
|
||||
U32[(ptr + 4) >>> 2] = 0; // .byteOffset
|
||||
U32[(ptr + 8) >>> 2] = byteLength; // .byteLength
|
||||
U32[ buf >>> 2] = byteLength; // .byteLength
|
||||
U32[(buf + 4) >>> 2] = 0; // 0
|
||||
if (view) {
|
||||
new ctor(buffer, buf + 8, length).set(view);
|
||||
if (view.length < length && !unsafe) {
|
||||
let setLength = elementSize * view.length;
|
||||
memory_fill(buf + 8 + setLength, 0, byteLength - setLength);
|
||||
for (let i = 0, j = ref >>> 1; i < length; ++i) U16[j + i] = str.charCodeAt(i);
|
||||
return ref;
|
||||
}
|
||||
|
||||
baseModule.__allocString = __allocString;
|
||||
|
||||
/** Reads a string from the module's memory by its pointer. */
|
||||
function __getString(ref) {
|
||||
checkMem();
|
||||
const id = U32[ref + ID_OFFSET >>> 2];
|
||||
if (id !== STRING_ID) throw Error("not a string: " + ref);
|
||||
return getStringImpl(U32, U16, ref);
|
||||
}
|
||||
|
||||
baseModule.__getString = __getString;
|
||||
|
||||
/** Gets the view matching the specified alignment, signedness and floatness. */
|
||||
function getView(align, signed, float) {
|
||||
if (float) {
|
||||
switch (align) {
|
||||
case 2: return F32;
|
||||
case 3: return F64;
|
||||
}
|
||||
} else {
|
||||
switch (align) {
|
||||
case 0: return signed ? I8 : U8;
|
||||
case 1: return signed ? I16 : U16;
|
||||
case 2: return signed ? I32 : U32;
|
||||
case 3: return signed ? I64 : U64;
|
||||
}
|
||||
} else if (!unsafe) {
|
||||
memory_fill(buf + 8, 0, byteLength);
|
||||
}
|
||||
return ptr;
|
||||
throw Error("unsupported align: " + align);
|
||||
}
|
||||
|
||||
baseModule.newArray = newArray;
|
||||
|
||||
/** Gets a view on a typed array in the module's memory by its pointer. */
|
||||
function getArray(ctor, ptr) {
|
||||
var elementSize = ctor.BYTES_PER_ELEMENT;
|
||||
if (!elementSize) throw Error("not a typed array");
|
||||
/** Allocates a new array in the module's memory and returns its retained pointer. */
|
||||
function __allocArray(id, values) {
|
||||
const info = getInfo(id);
|
||||
if (!(info & (ARRAYBUFFERVIEW | ARRAY))) throw Error("not an array: " + id + " @ " + info);
|
||||
const align = getAlign(VAL_ALIGN, info);
|
||||
const length = values.length;
|
||||
const buf = alloc(length << align, ARRAYBUFFER_ID);
|
||||
const arr = alloc(info & ARRAY ? ARRAY_SIZE : ARRAYBUFFERVIEW_SIZE, id);
|
||||
checkMem();
|
||||
var buf = U32[ ptr >>> 2];
|
||||
var byteOffset = U32[(ptr + 4) >>> 2];
|
||||
var byteLength = U32[(ptr + 8) >>> 2];
|
||||
return new ctor(buffer, buf + 8 + byteOffset, (byteLength - byteOffset) / elementSize);
|
||||
U32[arr + ARRAYBUFFERVIEW_BUFFER_OFFSET >>> 2] = retain(buf);
|
||||
U32[arr + ARRAYBUFFERVIEW_DATASTART_OFFSET >>> 2] = buf;
|
||||
U32[arr + ARRAYBUFFERVIEW_DATALENGTH_OFFSET >>> 2] = length << align;
|
||||
if (info & ARRAY) U32[arr + ARRAY_LENGTH_OFFSET >>> 2] = length;
|
||||
const view = getView(align, info & VAL_SIGNED, info & VAL_FLOAT);
|
||||
for (let i = 0; i < length; ++i) view[(buf >> align) + i] = values[i];
|
||||
if (info & VAL_MANAGED) for (let i = 0; i < length; ++i) retain(values[i]);
|
||||
return arr;
|
||||
}
|
||||
|
||||
baseModule.getArray = getArray;
|
||||
baseModule.__allocArray = __allocArray;
|
||||
|
||||
/** Frees a typed array in the module's memory. Must not be accessed anymore afterwards. */
|
||||
function freeArray(ptr) {
|
||||
/** Gets a view on the values of an array in the module's memory. */
|
||||
function __getArrayView(arr) {
|
||||
checkMem();
|
||||
var buf = U32[ptr >>> 2];
|
||||
memory_free(buf);
|
||||
memory_free(ptr);
|
||||
const id = U32[arr + ID_OFFSET >>> 2];
|
||||
const info = getInfo(id);
|
||||
if (!(info & ARRAYBUFFERVIEW)) throw Error("not an array: " + id);
|
||||
const align = getAlign(VAL_ALIGN, info);
|
||||
var buf = U32[arr + ARRAYBUFFERVIEW_DATASTART_OFFSET >>> 2];
|
||||
const length = info & ARRAY
|
||||
? U32[arr + ARRAY_LENGTH_OFFSET >>> 2]
|
||||
: U32[buf + SIZE_OFFSET >>> 2] >>> align;
|
||||
return getView(align, info & VAL_SIGNED, info & VAL_FLOAT)
|
||||
.slice(buf >>>= align, buf + length);
|
||||
}
|
||||
|
||||
baseModule.freeArray = freeArray;
|
||||
baseModule.__getArrayView = __getArrayView;
|
||||
|
||||
/**
|
||||
* Creates a new function in the module's table and returns its pointer. Note that only actual
|
||||
* WebAssembly functions, i.e. as exported by the module, are supported.
|
||||
*/
|
||||
function newFunction(fn) {
|
||||
if (typeof fn.original === "function") fn = fn.original;
|
||||
var index = table.length;
|
||||
table.grow(1);
|
||||
table.set(index, fn);
|
||||
return index;
|
||||
/** Reads (copies) the values of an array from the module's memory. */
|
||||
function __getArray(arr) {
|
||||
return Array.from(__getArrayView(arr));
|
||||
}
|
||||
|
||||
baseModule.newFunction = newFunction;
|
||||
baseModule.__getArray = __getArray;
|
||||
|
||||
/** Gets a function by its pointer. */
|
||||
function getFunction(ptr) {
|
||||
return wrapFunction(table.get(ptr), setargc);
|
||||
/** Tests whether an object is an instance of the class represented by the specified base id. */
|
||||
function __instanceof(ref, baseId) {
|
||||
var id = U32[(ref + ID_OFFSET) >>> 2];
|
||||
if (id <= U32[rttiBase >>> 2]) {
|
||||
do if (id == baseId) return true;
|
||||
while (id = getBase(id));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
baseModule.getFunction = getFunction;
|
||||
baseModule.__instanceof = __instanceof;
|
||||
|
||||
// Pull basic exports to baseModule so code in preInstantiate can use them
|
||||
baseModule.memory = baseModule.memory || memory;
|
||||
@ -209,8 +251,6 @@ function wrapFunction(fn, setargc) {
|
||||
setargc(args.length);
|
||||
return fn(...args);
|
||||
}
|
||||
// adding a function to the table with `newFunction` is limited to actual WebAssembly functions,
|
||||
// hence we can't use the wrapper and instead need to provide a reference to the original
|
||||
wrap.original = fn;
|
||||
return wrap;
|
||||
}
|
||||
@ -245,7 +285,7 @@ exports.instantiateStreaming = instantiateStreaming;
|
||||
/** Demangles an AssemblyScript module's exports to a friendly object structure. */
|
||||
function demangle(exports, baseModule) {
|
||||
var module = baseModule ? Object.create(baseModule) : {};
|
||||
var setargc = exports._setargc || function() {};
|
||||
var setargc = exports["__setargc"] || function() {};
|
||||
function hasOwnProperty(elem, prop) {
|
||||
return Object.prototype.hasOwnProperty.call(elem, prop);
|
||||
}
|
||||
@ -268,9 +308,13 @@ function demangle(exports, baseModule) {
|
||||
let ctor = function(...args) {
|
||||
return ctor.wrap(ctor.prototype.constructor(0, ...args));
|
||||
};
|
||||
ctor.prototype = {};
|
||||
ctor.prototype = {
|
||||
valueOf: function valueOf() {
|
||||
return this[THIS];
|
||||
}
|
||||
};
|
||||
ctor.wrap = function(thisValue) {
|
||||
return Object.create(ctor.prototype, { [thisPtr]: { value: thisValue, writable: false } });
|
||||
return Object.create(ctor.prototype, { [THIS]: { value: thisValue, writable: false } });
|
||||
};
|
||||
if (classElem) Object.getOwnPropertyNames(classElem).forEach(name =>
|
||||
Object.defineProperty(ctor, name, Object.getOwnPropertyDescriptor(classElem, name))
|
||||
@ -284,8 +328,8 @@ function demangle(exports, baseModule) {
|
||||
let getter = exports[internalName.replace("set:", "get:")];
|
||||
let setter = exports[internalName.replace("get:", "set:")];
|
||||
Object.defineProperty(curr, name, {
|
||||
get: function() { return getter(this[thisPtr]); },
|
||||
set: function(value) { setter(this[thisPtr], value); },
|
||||
get: function() { return getter(this[THIS]); },
|
||||
set: function(value) { setter(this[THIS], value); },
|
||||
enumerable: true
|
||||
});
|
||||
}
|
||||
@ -296,7 +340,7 @@ function demangle(exports, baseModule) {
|
||||
Object.defineProperty(curr, name, {
|
||||
value: function (...args) {
|
||||
setargc(args.length);
|
||||
return elem(this[thisPtr], ...args);
|
||||
return elem(this[THIS], ...args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user