Skip to content

Rewrite runtime, switch to tracing GC and bootstrap #1559

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 98 commits into from
Jan 28, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
ac1430d
Switch to tracing GC
dcodeIO Nov 24, 2020
fd9d212
Merge branch 'master' into stw
dcodeIO Nov 24, 2020
52e9a84
remove tcms tracing
dcodeIO Nov 24, 2020
7de8b18
update dependencies
dcodeIO Nov 24, 2020
81851d0
lint loader
dcodeIO Nov 24, 2020
93ada87
save
dcodeIO Dec 2, 2020
caf0d3c
just do it
dcodeIO Dec 5, 2020
93d7361
remove custom Binaryen build
dcodeIO Dec 5, 2020
f60b5d1
Merge branch 'master' into stw
dcodeIO Dec 5, 2020
b2b2070
can't run bootstrap with rtrace in CI
dcodeIO Dec 5, 2020
4362b89
asc.wasm
dcodeIO Dec 6, 2020
249f8f2
fix
dcodeIO Dec 6, 2020
12e39d5
eternal glory
dcodeIO Dec 6, 2020
349e291
eternal glory
dcodeIO Dec 6, 2020
d6e81d9
eternal glory, shared with Max
dcodeIO Dec 7, 2020
f2c9bfb
new bootstrap test
dcodeIO Dec 7, 2020
fe933bd
Merge branch 'master' into stw
dcodeIO Dec 7, 2020
3b6294d
rewrite builtin visitor logic
dcodeIO Dec 8, 2020
b2bc0bf
add Wasm using Wasm build step
dcodeIO Dec 8, 2020
7c0252a
add celebratory build steps
dcodeIO Dec 8, 2020
d684c05
Merge branch 'master' into stw
dcodeIO Dec 8, 2020
9944b2c
port terminal utility from JS to AS
dcodeIO Dec 8, 2020
a180fd2
Merge branch 'master' into stw
dcodeIO Dec 8, 2020
34e2a45
Merge branch 'master' into stw
dcodeIO Dec 11, 2020
e36ea1f
Merge branch 'master' into stw
dcodeIO Dec 14, 2020
3e2d7a2
Merge branch 'master' into stw
dcodeIO Dec 16, 2020
290de3d
fix static memory segment/realloc weirdness
dcodeIO Dec 17, 2020
91c3dd9
Merge branch 'master' into stw
dcodeIO Dec 17, 2020
13a5627
more
dcodeIO Dec 18, 2020
a1d6603
update fixtures
dcodeIO Dec 18, 2020
bf3c4ed
Merge branch 'master' into stw
dcodeIO Dec 19, 2020
e4c4f3f
Merge branch 'master' into stw
dcodeIO Dec 19, 2020
f5466cc
new bootstrap scripts, use asconfig
dcodeIO Dec 20, 2020
cdc9f98
hmm
dcodeIO Dec 21, 2020
93ea7f9
fix
dcodeIO Dec 21, 2020
c8fecca
simplify to two-color mark & sweep
dcodeIO Dec 21, 2020
5bd70cc
hand-optimize
dcodeIO Dec 22, 2020
afa5262
build instructions, fix a potential memory leak
dcodeIO Dec 22, 2020
e374459
remove 'eventually'
dcodeIO Dec 22, 2020
3863e54
fix
dcodeIO Dec 22, 2020
b9f6fe5
pass infra, initial shadow stack pass
dcodeIO Dec 25, 2020
93d96d5
implement TODOs
dcodeIO Dec 25, 2020
d453e97
fix a memory leak
dcodeIO Dec 25, 2020
221703c
handle top-level, use errors, simple test
dcodeIO Dec 25, 2020
419756e
document
dcodeIO Dec 26, 2020
dadaf92
more
dcodeIO Dec 26, 2020
4eb20dd
memory.fill large frames only
dcodeIO Dec 26, 2020
7a39106
forward the good parts from failed attempt no. 591
dcodeIO Dec 30, 2020
89a2b8c
fix dts weirdness
dcodeIO Dec 30, 2020
9112dd2
give it 16mb initial mem
dcodeIO Dec 30, 2020
36090af
Merge branch 'master' into stw
dcodeIO Dec 30, 2020
937bfa5
switch to pin/unpin
dcodeIO Dec 30, 2020
d8b0c60
noExportRuntime -> exportRuntime
dcodeIO Dec 30, 2020
eb3aaab
update related tests
dcodeIO Dec 30, 2020
00804d7
fix ordering bug, fast-forward binaryen to 2021
dcodeIO Jan 1, 2021
835514a
don't try to lint custom binaryen builds
dcodeIO Jan 1, 2021
c584539
console & process WASI experiment
dcodeIO Jan 1, 2021
bea6c23
fix, opt
dcodeIO Jan 1, 2021
ff75803
fix
dcodeIO Jan 2, 2021
b032216
Update std/assembly/console.ts
dcodeIO Jan 2, 2021
8128f66
Update std/assembly/console.ts
dcodeIO Jan 2, 2021
410092a
Update std/assembly/console.ts
dcodeIO Jan 2, 2021
b10261c
fast writes up to 4
dcodeIO Jan 2, 2021
dac6447
incremental gc with a mini stack
dcodeIO Jan 3, 2021
1bcab43
finally
dcodeIO Jan 3, 2021
4d1ed91
clean
dcodeIO Jan 4, 2021
2c4e486
bring back stub (becomes 'minimal') and runtime option
dcodeIO Jan 5, 2021
be16df2
hint at --exportRuntime in the loader
dcodeIO Jan 5, 2021
7733d3b
Merge branch 'master' into stw
dcodeIO Jan 5, 2021
33d750e
add crypto.getRandomValues, categorize std-wasi tests
dcodeIO Jan 5, 2021
e2c6f9d
add definitions for console, process, crypto
dcodeIO Jan 6, 2021
0a843a4
use upstream Binaryen
dcodeIO Jan 6, 2021
11dd40e
update fixtures
dcodeIO Jan 6, 2021
6ef7ace
avoid GC object when writing a string to console
dcodeIO Jan 6, 2021
d9cbfe1
keep 'stub' runtime
dcodeIO Jan 8, 2021
0464bd8
update fixture
dcodeIO Jan 8, 2021
0ae612e
cleanup
dcodeIO Jan 8, 2021
2b297a6
fix a typo
dcodeIO Jan 10, 2021
e0ab920
properly report closed-over locals
dcodeIO Jan 10, 2021
3cb250f
update closure test stderr patterns
dcodeIO Jan 10, 2021
cf92771
initial shadow stack
dcodeIO Jan 14, 2021
02ce6b1
solidify
dcodeIO Jan 15, 2021
3dccc7d
base on total mem
dcodeIO Jan 15, 2021
4ca5c88
stack check downwards only
dcodeIO Jan 16, 2021
908ab4c
fix, tweak
dcodeIO Jan 16, 2021
bb8a292
tweak
dcodeIO Jan 18, 2021
6dae8ae
fix typo
dcodeIO Jan 18, 2021
ab59b92
naming things is hard
dcodeIO Jan 19, 2021
8fd0f94
pointer free objects, profiling
dcodeIO Jan 20, 2021
0d46e3b
incremental by default, fixes, runtime README
dcodeIO Jan 21, 2021
d550a23
gc plotting
dcodeIO Jan 23, 2021
5e190f5
naming
dcodeIO Jan 23, 2021
a445f91
Merge branch 'master' into stw
dcodeIO Jan 23, 2021
bfc7e42
remove outdated tests
dcodeIO Jan 23, 2021
47e4b8e
more
dcodeIO Jan 27, 2021
5b51a13
fix
dcodeIO Jan 28, 2021
6b8e6e5
Expose program diagnostics in cli main callback (#1625)
piotr-oles Jan 28, 2021
ae6f9fe
wording
dcodeIO Jan 28, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
12 changes: 6 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,12 @@ jobs:
npm run build
cd ..
npm test rt-full
- name: Test stub runtime
run: |
cd tests/allocators/rt-stub
npm run build
cd ..
npm test rt-stub
# - name: Test stub runtime
# run: |
# cd tests/allocators/rt-stub
# npm run build
# cd ..
# npm test rt-stub
test-loader:
name: "Loader"
runs-on: ubuntu-latest
Expand Down
24 changes: 11 additions & 13 deletions cli/asc.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ const fs = require("fs");
const path = require("path");
const process = require("process"); // ensure shim

process.exit = ((exit) => function(code) {
if (code) console.log(new Error("exit " + code.toString()).stack);
exit(code);
})(process.exit);

const utf8 = require("./util/utf8");
const colorsUtil = require("./util/colors");
const optionsUtil = require("./util/options");
Expand Down Expand Up @@ -360,6 +365,7 @@ exports.main = function main(argv, options, callback) {
assemblyscript.setNoUnsafe(compilerOptions, opts.noUnsafe);
assemblyscript.setPedantic(compilerOptions, opts.pedantic);
assemblyscript.setLowMemoryLimit(compilerOptions, opts.lowMemoryLimit >>> 0);
assemblyscript.setNoExportRuntime(compilerOptions, opts.noExportRuntime);

// Add or override aliases if specified
if (opts.use) {
Expand Down Expand Up @@ -624,18 +630,11 @@ exports.main = function main(argv, options, callback) {
}
}

// Include runtime template before entry files so its setup runs first
// Include runtime before entry files so its setup runs first
{
let runtimeName = String(opts.runtime);
let runtimePath = "rt/index-" + runtimeName;
let runtimePath = opts.noExportRuntime ? "rt/index-noexport" : "rt/index";
let runtimeText = exports.libraryFiles[runtimePath];
if (runtimeText == null) {
runtimePath = runtimeName;
runtimeText = readFile(runtimePath + extension.ext, baseDir);
if (runtimeText == null) return callback(Error("Runtime '" + runtimeName + "' not found."));
} else {
runtimePath = "~lib/" + runtimePath;
}
if (runtimeText == null) return callback(Error("Runtime entry not found."));
stats.parseCount++;
stats.parseTime += measure(() => {
assemblyscript.parse(program, runtimeText, runtimePath + extension.ext, true);
Expand Down Expand Up @@ -754,7 +753,6 @@ exports.main = function main(argv, options, callback) {

// Optimize the module
const debugInfo = opts.debug;
const usesARC = opts.runtime == "half" || opts.runtime == "full";
const converge = opts.converge;
const runPasses = [];
if (opts.runPasses) {
Expand All @@ -771,13 +769,13 @@ exports.main = function main(argv, options, callback) {

stats.optimizeTime += measure(() => {
stats.optimizeCount++;
module.optimize(optimizeLevel, shrinkLevel, debugInfo, usesARC);
module.optimize(optimizeLevel, shrinkLevel, debugInfo);
module.runPasses(runPasses);
if (converge) {
let last = module.toBinary();
do {
stats.optimizeCount++;
module.optimize(optimizeLevel, shrinkLevel, debugInfo, usesARC);
module.optimize(optimizeLevel, shrinkLevel, debugInfo);
module.runPasses(runPasses);
let next = module.toBinary();
if (next.output.length >= last.output.length) {
Expand Down
16 changes: 4 additions & 12 deletions cli/asc.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,11 @@
"type": "b",
"default": false
},
"runtime": {
"noExportRuntime": {
"category": "Optimization",
"description": [
"Specifies the runtime variant to include in the program.",
"",
" full Default runtime based on TLSF and reference counting.",
" half Same as 'full', but not exported to the host.",
" stub Minimal stub implementation without free/GC support.",
" none Same as 'stub', but not exported to the host.",
""
],
"type": "s",
"default": "full"
"description": "Does not export the runtime helpers (e.g. __new).",
"type": "b",
"default": false
},

"outFile": {
Expand Down
13 changes: 9 additions & 4 deletions lib/loader/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type Imports = {
seed?(): number;
abort?(msg: number, file: number, line: number, column: number): void;
trace?(msg: number, numArgs?: number, ...args: number[]): void;
mark?(): void;
};
};

Expand Down Expand Up @@ -87,14 +88,18 @@ export interface ASUtil {
__newString(str: string): number;
/** Allocates a new array in the module's memory and returns a reference (pointer) to it. */
__newArray(id: number, values: ArrayLike<number>): number;
/** Retains a reference to a managed object externally, making sure that it doesn't become collected prematurely. Returns the pointer. */
/** Retains a reference to a managed object externally, preventing it from becoming collected. */
__retain(ptr: number): number;
/** Releases a previously retained reference to a managed object, allowing the runtime to collect it once its reference count reaches zero. */
/** Releases a reference to a managed object, allowing it to become collected. */
__release(ptr: number): void;
/** Forcefully resets the heap to its initial offset, effectively clearing dynamic memory. Stub runtime only. */
__reset?(): void;
/** Keeps a reference to a managed object alive externally while it is reachable. */
__keepalive(ptr: number): number;
/** Tests whether a managed object is an instance of the class represented by the specified base id. */
__instanceof(ptr: number, baseId: number): boolean;
/** Introduces a link from a parent object to a child object, i.e. upon `parent.field = child`. */
__link(parentPtr: number, childPtr: number, expectMultiple: boolean): void;
/** Marks an externally retained value as reachable during the collection phase. */
__mark(ptr: number): void;
/** Forces a cycle collection. Only relevant if objects potentially forming reference cycles are used. */
__collect(): void;
}
Expand Down
84 changes: 69 additions & 15 deletions lib/loader/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const THIS = Symbol();

const STRING_DECODE_THRESHOLD = 32;
const decoder = new TextDecoder("utf-16le");
const rootsSym = Symbol();

/** Gets a string from an U32 and an U16 view on a memory. */
function getStringImpl(buffer, ptr) {
Expand All @@ -54,6 +55,8 @@ function getStringImpl(buffer, ptr) {
function preInstantiate(imports) {
const extendedExports = {};

extendedExports[rootsSym] = new Map();

function getString(memory, ptr) {
if (!memory) return "<yet unknown>";
return getStringImpl(memory.buffer, ptr);
Expand All @@ -70,6 +73,11 @@ function preInstantiate(imports) {
console.log(`trace: ${getString(memory, msg)}${n ? " " : ""}${args.slice(0, n).join(", ")}`);
};
env.seed = env.seed || Date.now;
env.mark = env.mark || function() {
for (const ptr of extendedExports[rootsSym].keys()) {
extendedExports.__mark(ptr);
}
};
imports.Math = imports.Math || Math;
imports.Date = imports.Date || Date;

Expand All @@ -81,16 +89,16 @@ function postInstantiate(extendedExports, instance) {
const exports = instance.exports;
const memory = exports.memory;
const table = exports.table;
const new_ = exports["__new"];
const retain = exports["__retain"];
const rttiBase = exports["__rtti_base"] || ~0; // oob if not present
const __new = exports.__new;
const __link = exports.__link;
const __rtti_base = exports.__rtti_base || ~0; // oob if not present

/** Gets the runtime type info for the given id. */
function getInfo(id) {
const U32 = new Uint32Array(memory.buffer);
const count = U32[rttiBase >>> 2];
const count = U32[__rtti_base >>> 2];
if ((id >>>= 0) >= count) throw Error(`invalid id: ${id}`);
return U32[(rttiBase + 4 >>> 2) + id * 2];
return U32[(__rtti_base + 4 >>> 2) + id * 2];
}

/** Gets and validate runtime type info for the given id for array like objects */
Expand All @@ -103,9 +111,9 @@ function postInstantiate(extendedExports, instance) {
/** Gets the runtime base id for the given id. */
function getBase(id) {
const U32 = new Uint32Array(memory.buffer);
const count = U32[rttiBase >>> 2];
const count = U32[__rtti_base >>> 2];
if ((id >>>= 0) >= count) throw Error(`invalid id: ${id}`);
return U32[(rttiBase + 4 >>> 2) + id * 2 + 1];
return U32[(__rtti_base + 4 >>> 2) + id * 2 + 1];
}

/** Gets the runtime alignment of a collection's values. */
Expand All @@ -118,10 +126,10 @@ function postInstantiate(extendedExports, instance) {
// return 31 - Math.clz32((info >>> KEY_ALIGN_OFFSET) & 31); // -1 if none
// }

/** Allocates a new string in the module's memory and returns its retained pointer. */
/** Allocates a new string in the module's memory and returns its pointer. */
function __newString(str) {
const length = str.length;
const ptr = new_(length << 1, STRING_ID);
const ptr = __new(length << 1, STRING_ID);
const U16 = new Uint16Array(memory.buffer);
for (var i = 0, p = ptr >>> 1; i < length; ++i) U16[p + i] = str.charCodeAt(i);
return ptr;
Expand Down Expand Up @@ -158,27 +166,32 @@ function postInstantiate(extendedExports, instance) {
throw Error(`unsupported align: ${alignLog2}`);
}

/** Allocates a new array in the module's memory and returns its retained pointer. */
/** Allocates a new array in the module's memory and returns its pointer. */
function __newArray(id, values) {
const info = getArrayInfo(id);
const align = getValueAlign(info);
const length = values.length;
const buf = new_(length << align, info & STATICARRAY ? id : ARRAYBUFFER_ID);
const buf = __new(length << align, info & STATICARRAY ? id : ARRAYBUFFER_ID);
let result;
if (info & STATICARRAY) {
result = buf;
} else {
const arr = new_(info & ARRAY ? ARRAY_SIZE : ARRAYBUFFERVIEW_SIZE, id);
const arr = __new(info & ARRAY ? ARRAY_SIZE : ARRAYBUFFERVIEW_SIZE, id);
const U32 = new Uint32Array(memory.buffer);
U32[arr + ARRAYBUFFERVIEW_BUFFER_OFFSET >>> 2] = retain(buf);
U32[arr + ARRAYBUFFERVIEW_BUFFER_OFFSET >>> 2] = 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;
__link(arr, buf, 1);
result = arr;
}
const view = getView(align, info & VAL_SIGNED, info & VAL_FLOAT);
if (info & VAL_MANAGED) {
for (let i = 0; i < length; ++i) view[(buf >>> align) + i] = retain(values[i]);
for (let i = 0; i < length; ++i) {
const value = values[i];
view[(buf >>> align) + i] = value;
__link(result, value, 1);
}
} else {
view.set(values, buf >>> align);
}
Expand Down Expand Up @@ -224,6 +237,47 @@ function postInstantiate(extendedExports, instance) {

extendedExports.__getArrayBuffer = __getArrayBuffer;

/** Retains a reference to a managed object externally, preventing it from becoming collected. */
function __retain(ptr) {
// Must coerce `ptr` to a number in case it is a shadow object
const roots = extendedExports[rootsSym];
roots.set(+ptr, (roots.get(+ptr) | 0) + 1);
return ptr; // ...but return the potential shadow object again
}

extendedExports.__retain = __retain;

/** Releases a reference to a managed object, allowing it to become collected. */
function __release(ptr) {
// Must coerce `ptr` to a number in case it is a shadow object
const roots = extendedExports[rootsSym];
const count = roots.get(+ptr);
if (count === 1) {
roots.delete(+ptr);
} else {
if (!count) throw Error(`Superfluous release on object ${+ptr}`);
roots.set(+ptr, count - 1);
}
}

extendedExports.__release = __release;

// At the time of this comment, FinalizationRegistry is supported in latest V8
// and SpiderMonkey but not yet supported in JavaScriptCore.
if (typeof FinalizationRegistry !== "undefined") {
// eslint-disable-next-line no-undef
const registry = new FinalizationRegistry(ptr => { __release(ptr); });

/** Keeps a reference to a managed object alive externally while it is reachable. */
function __keepalive(ptr) {
const shadowObj = new Number(__retain(+ptr));
registry.register(shadowObj, +ptr);
return shadowObj;
}

extendedExports.__keepalive = __keepalive;
}

/** Copies a typed array's values from the module's memory. */
function getTypedArray(Type, alignLog2, ptr) {
return new Type(getTypedArrayView(Type, alignLog2, ptr));
Expand Down Expand Up @@ -267,7 +321,7 @@ function postInstantiate(extendedExports, instance) {
function __instanceof(ptr, baseId) {
const U32 = new Uint32Array(memory.buffer);
let id = U32[ptr + ID_OFFSET >>> 2];
if (id <= U32[rttiBase >>> 2]) {
if (id <= U32[__rtti_base >>> 2]) {
do {
if (id == baseId) return true;
id = getBase(id);
Expand Down
Binary file modified lib/loader/tests/build/default.wasm
Binary file not shown.
Binary file modified lib/loader/tests/build/legacy.wasm
Binary file not shown.
Loading