diff --git a/lib/lint/base.json b/lib/lint/base.json
index 86373db3..5fd658b4 100644
--- a/lib/lint/base.json
+++ b/lib/lint/base.json
@@ -90,11 +90,6 @@
"no-unsafe-any": {
"severity": "error"
},
- "no-unused-variable": {
- "options": [{
- "ignore-pattern": "^_"
- }]
- },
"no-void-expression": {
"severity": "error"
},
diff --git a/lib/loader/README.md b/lib/loader/README.md
index 46ddf180..cc4ebf6c 100644
--- a/lib/loader/README.md
+++ b/lib/loader/README.md
@@ -65,7 +65,7 @@ Instances are automatically populated with useful utility:
```js
import "allocator/tlsf";
- export { allocate_memory, free_memory };
+ export { memory };
```
* **getString**(ptr: `number`): `string`
@@ -126,7 +126,7 @@ var str = "Hello world!";
var ptr = module.newString(str);
// Disposing a string that is no longer needed (requires free_memory to be exported)
-module.free_memory(ptr);
+module.memory.free(ptr);
// Obtaining a string, i.e. as returned by an export
var ptrToString = ...;
diff --git a/lib/loader/index.d.ts b/lib/loader/index.d.ts
index 3bc09009..66076832 100644
--- a/lib/loader/index.d.ts
+++ b/lib/loader/index.d.ts
@@ -44,8 +44,8 @@ export declare function instantiate(module: WebAssembly.Module, im
/** Instantiates an AssemblyScript module from a buffer using the specified imports. */
export declare function instantiateBuffer(buffer: Uint8Array, imports?: ImportsObject): ASUtil & T;
-/** Instantiates an AssemblyScript module from a response using the sspecified imports. */
-export declare function instantiateStreaming(response: Response, imports?: ImportsObject): Promise;
+/** Instantiates an AssemblyScript module from a response using the specified imports. */
+export declare function instantiateStreaming(result: Promise, imports?: ImportsObject): Promise;
/** Demangles an AssemblyScript module's exports to a friendly object structure. */
export declare function demangle(exports: {}): T;
diff --git a/lib/loader/index.js b/lib/loader/index.js
index 74d37211..2f152393 100644
--- a/lib/loader/index.js
+++ b/lib/loader/index.js
@@ -45,7 +45,7 @@ function instantiate(module, imports) {
/** Allocates a new string in the module's memory and returns its pointer. */
function newString(str) {
var dataLength = str.length;
- var ptr = exports.allocate_memory(4 + (dataLength << 1));
+ var ptr = exports["memory.allocate"](4 + (dataLength << 1));
var dataOffset = (4 + ptr) >>> 1;
checkMem();
U32[ptr >>> 2] = dataLength;
diff --git a/lib/loader/package.json b/lib/loader/package.json
index 7ff06ee8..0edaf3fa 100644
--- a/lib/loader/package.json
+++ b/lib/loader/package.json
@@ -4,6 +4,7 @@
"main": "index.js",
"types": "index.d.ts",
"scripts": {
+ "test:build": "asc tests/assembly/index.ts -b tests/build/untouched.wasm",
"test": "node tests"
},
"files": [
diff --git a/lib/loader/tests/assembly/index.ts b/lib/loader/tests/assembly/index.ts
index e448e44c..71ed4548 100644
--- a/lib/loader/tests/assembly/index.ts
+++ b/lib/loader/tests/assembly/index.ts
@@ -1,5 +1,7 @@
import "allocator/arena";
+export { memory };
+
export const COLOR: string = "red";
export function strlen(str: string): i32 {
@@ -14,7 +16,7 @@ export namespace math {
export class Car {
static readonly MAX_DOORS: i32 = 5;
- static usualDoors: i32 = 3;
+ static readonly usualDoors: i32 = 3;
numDoors: i32;
private doorsOpen: bool = false;
@@ -42,5 +44,3 @@ export class Car {
memory.free(changetype(this));
}
}
-
-export { memory };
diff --git a/lib/loader/tests/build/untouched.wasm b/lib/loader/tests/build/untouched.wasm
index f1ba66e0..0c010359 100644
Binary files a/lib/loader/tests/build/untouched.wasm and b/lib/loader/tests/build/untouched.wasm differ
diff --git a/lib/loader/tests/index.js b/lib/loader/tests/index.js
index 625b420f..7aa05572 100644
--- a/lib/loader/tests/index.js
+++ b/lib/loader/tests/index.js
@@ -23,6 +23,7 @@ assert(typeof proto.getString === "function");
// should export memory
assert(module.memory instanceof WebAssembly.Memory);
+assert(typeof module.memory.free === "function");
// should be able to get an exported string
assert.strictEqual(module.getString(module.COLOR), "red");
diff --git a/package-lock.json b/package-lock.json
index 71225f67..41ded16c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -66,9 +66,9 @@
"dev": true
},
"@types/node": {
- "version": "10.5.2",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-10.5.2.tgz",
- "integrity": "sha512-m9zXmifkZsMHZBOyxZWilMwmTlpC8x5Ty360JKTiXvlXZfBWYpsg9ZZvP/Ye+iZUh+Q+MxDLjItVTWIsfwz+8Q==",
+ "version": "10.5.4",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.5.4.tgz",
+ "integrity": "sha512-8TqvB0ReZWwtcd3LXq3YSrBoLyXFgBX/sBZfGye9+YS8zH7/g+i6QRIuiDmwBoTzcQ/pk89nZYTYU4c5akKkzw==",
"dev": true
},
"@types/shelljs": {
@@ -664,9 +664,9 @@
"dev": true
},
"binaryen": {
- "version": "49.0.0-nightly.20180718",
- "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-49.0.0-nightly.20180718.tgz",
- "integrity": "sha512-r7gYKbwYYF5DBTN2fSomv1BNUNYSPros20eTng5MV/Bsv7V6DfJtrvietJ35p+jqI797P83vnJ0PEB1vTZ9qoQ=="
+ "version": "49.0.0-nightly.20180731",
+ "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-49.0.0-nightly.20180731.tgz",
+ "integrity": "sha512-uZ7bizGTMbEOzIwZmGbXIcGFy8IkZjDoNOy+nPnIv7Dy1MiURItE0PRMnpXO2GPOOq6ZALW8pb5xb9MShD0zQQ=="
},
"bluebird": {
"version": "3.5.1",
@@ -1053,9 +1053,9 @@
"dev": true
},
"commander": {
- "version": "2.13.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz",
- "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==",
+ "version": "2.16.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz",
+ "integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew==",
"dev": true
},
"commondir": {
@@ -1231,7 +1231,8 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
- "dev": true
+ "dev": true,
+ "optional": true
},
"decode-uri-component": {
"version": "0.2.0",
@@ -1382,9 +1383,9 @@
"dev": true
},
"eslint-scope": {
- "version": "3.7.3",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz",
- "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz",
+ "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==",
"dev": true,
"requires": {
"esrecurse": "^4.1.0",
@@ -1392,9 +1393,9 @@
}
},
"esprima": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
- "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true
},
"esrecurse": {
@@ -2252,9 +2253,9 @@
}
},
"get-caller-file": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
- "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
+ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
"dev": true
},
"get-stream": {
@@ -2305,9 +2306,9 @@
}
},
"global-modules-path": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/global-modules-path/-/global-modules-path-2.1.0.tgz",
- "integrity": "sha512-3DrmGj2TP+96cABk9TfMp6f3knH/Y46dqvWznTU3Tf6/bDGLDAn15tFluQ7BcloykOcdY16U0WGq0BQblYOxJQ==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/global-modules-path/-/global-modules-path-2.3.0.tgz",
+ "integrity": "sha512-HchvMJNYh9dGSCy8pOQ2O8u/hoXaL+0XhnrwH0RyLiSXMMTl9W3N6KUU73+JFOg5PGjtzl6VZzUQsnrpm7Szag==",
"dev": true
},
"graceful-fs": {
@@ -2771,9 +2772,9 @@
"dev": true
},
"js-yaml": {
- "version": "3.11.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz",
- "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==",
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz",
+ "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
@@ -3728,9 +3729,9 @@
}
},
"rxjs": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.1.tgz",
- "integrity": "sha512-OwMxHxmnmHTUpgO+V7dZChf3Tixf4ih95cmXjzzadULziVl/FKhHScGLj4goEw9weePVOH2Q0+GcCBUhKCZc/g==",
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz",
+ "integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
@@ -4074,9 +4075,9 @@
}
},
"stream-each": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz",
- "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==",
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz",
+ "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==",
"dev": true,
"requires": {
"end-of-stream": "^1.1.0",
@@ -4280,15 +4281,15 @@
}
},
"tslib": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz",
- "integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==",
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
+ "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
"dev": true
},
"tslint": {
- "version": "5.10.0",
- "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.10.0.tgz",
- "integrity": "sha1-EeJrzLiK+gLdDZlWyuPUVAtfVMM=",
+ "version": "5.11.0",
+ "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.11.0.tgz",
+ "integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=",
"dev": true,
"requires": {
"babel-code-frame": "^6.22.0",
@@ -4302,13 +4303,13 @@
"resolve": "^1.3.2",
"semver": "^5.3.0",
"tslib": "^1.8.0",
- "tsutils": "^2.12.1"
+ "tsutils": "^2.27.2"
}
},
"tsutils": {
- "version": "2.26.2",
- "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.26.2.tgz",
- "integrity": "sha512-uzwnhmrSbyinPCiwfzGsOY3IulBTwoky7r83HmZdz9QNCjhSCzavkh47KLWuU0zF2F2WbpmmzoJUIEiYyd+jEQ==",
+ "version": "2.29.0",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
+ "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
"dev": true,
"requires": {
"tslib": "^1.8.1"
@@ -4366,15 +4367,15 @@
"dev": true
},
"typedoc-plugin-external-module-name": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/typedoc-plugin-external-module-name/-/typedoc-plugin-external-module-name-1.1.1.tgz",
- "integrity": "sha512-Erc0MyKDGYIN7kSlIjgifVieZEogg8YwOH8bjW6RXb0y44hVeTER0UoW6Rh+n8leg6rvlPdnH092aFfcd8kRlA==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/typedoc-plugin-external-module-name/-/typedoc-plugin-external-module-name-1.1.3.tgz",
+ "integrity": "sha512-/VMawTW4NnUUsgq0o8O37y9MmXFaOCDrH1dvDg7SZUS5ZSpUPSILVWwGJP+7g4I8vKZ5bBKZKHfPIEA4xUC+PQ==",
"dev": true
},
"typescript": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
- "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.1.tgz",
+ "integrity": "sha512-zQIMOmC+372pC/CCVLqnQ0zSBiY7HHodU7mpQdjiZddek4GMj31I3dUJ7gAs9o65X7mnRma6OokOkc6f9jjfBg==",
"dev": true
},
"uglify-es": {
@@ -4387,6 +4388,12 @@
"source-map": "~0.6.1"
},
"dependencies": {
+ "commander": {
+ "version": "2.13.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz",
+ "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==",
+ "dev": true
+ },
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -4615,9 +4622,9 @@
}
},
"webpack": {
- "version": "4.16.0",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.16.0.tgz",
- "integrity": "sha512-oNx9djAd6uAcccyfqN3hyXLNMjZHiRySZmBQ4c8FNmf1SNJGhx7n9TSvHNyXxgToRdH65g/Q97s94Ip9N6F7xg==",
+ "version": "4.16.3",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.16.3.tgz",
+ "integrity": "sha512-3VcrVoFgzSz1IYgga71YpU3HO89Al5bSnDOj9RJQPsy+FNyI1sFsUyJITn3pktNuaRBlQT0usvKZE3GgkPGAIw==",
"dev": true,
"requires": {
"@webassemblyjs/ast": "1.5.13",
@@ -4631,7 +4638,7 @@
"ajv-keywords": "^3.1.0",
"chrome-trace-event": "^1.0.0",
"enhanced-resolve": "^4.1.0",
- "eslint-scope": "^3.7.1",
+ "eslint-scope": "^4.0.0",
"json-parse-better-errors": "^1.0.2",
"loader-runner": "^2.3.0",
"loader-utils": "^1.1.0",
@@ -4661,9 +4668,9 @@
}
},
"webpack-cli": {
- "version": "3.0.8",
- "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.0.8.tgz",
- "integrity": "sha512-KnRLJ0BUaYRqrhAMb9dv3gzdmhmgIMKo0FmdsnmfqbPGtLnnZ6tORZAvmmKfr+A0VgiVpqC60Gv7Ofg0R2CHtQ==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.1.0.tgz",
+ "integrity": "sha512-p5NeKDtYwjZozUWq6kGNs9w+Gtw/CPvyuXjXn2HMdz8Tie+krjEg8oAtonvIyITZdvpF7XG9xDHwscLr2c+ugQ==",
"dev": true,
"requires": {
"chalk": "^2.4.1",
@@ -4676,7 +4683,7 @@
"loader-utils": "^1.1.0",
"supports-color": "^5.4.0",
"v8-compile-cache": "^2.0.0",
- "yargs": "^11.1.0"
+ "yargs": "^12.0.1"
}
},
"webpack-sources": {
@@ -4772,6 +4779,12 @@
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
+ "xregexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz",
+ "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==",
+ "dev": true
+ },
"xtend": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
@@ -4791,14 +4804,14 @@
"dev": true
},
"yargs": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz",
- "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==",
+ "version": "12.0.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.1.tgz",
+ "integrity": "sha512-B0vRAp1hRX4jgIOWFtjfNjd9OA9RWYZ6tqGA9/I/IrTMsxmKvtWy+ersM+jzpQqbC3YfLzeABPdeTgcJ9eu1qQ==",
"dev": true,
"requires": {
"cliui": "^4.0.0",
- "decamelize": "^1.1.1",
- "find-up": "^2.1.0",
+ "decamelize": "^2.0.0",
+ "find-up": "^3.0.0",
"get-caller-file": "^1.0.1",
"os-locale": "^2.0.0",
"require-directory": "^2.1.1",
@@ -4806,22 +4819,68 @@
"set-blocking": "^2.0.0",
"string-width": "^2.0.0",
"which-module": "^2.0.0",
- "y18n": "^3.2.1",
- "yargs-parser": "^9.0.2"
+ "y18n": "^3.2.1 || ^4.0.0",
+ "yargs-parser": "^10.1.0"
},
"dependencies": {
- "y18n": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
- "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
+ "decamelize": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz",
+ "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==",
+ "dev": true,
+ "requires": {
+ "xregexp": "4.0.0"
+ }
+ },
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "p-limit": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz",
+ "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "p-try": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
+ "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==",
"dev": true
}
}
},
"yargs-parser": {
- "version": "9.0.2",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz",
- "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=",
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz",
+ "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==",
"dev": true,
"requires": {
"camelcase": "^4.1.0"
diff --git a/package.json b/package.json
index b8e03dc6..26878f52 100644
--- a/package.json
+++ b/package.json
@@ -12,22 +12,22 @@
},
"dependencies": {
"@protobufjs/utf8": "^1.1.0",
- "binaryen": "49.0.0-nightly.20180718",
+ "binaryen": "49.0.0-nightly.20180731",
"long": "^4.0.0"
},
"devDependencies": {
- "@types/node": "^10.5.2",
+ "@types/node": "^10.5.4",
"browser-process-hrtime": "^0.1.2",
"diff": "^3.5.0",
"glob": "^7.1.2",
"ts-loader": "^4.4.2",
"ts-node": "^6.2.0",
- "tslint": "^5.10.0",
+ "tslint": "^5.11.0",
"typedoc": "^0.11.1",
- "typedoc-plugin-external-module-name": "^1.1.1",
- "typescript": "^2.9.2",
- "webpack": "^4.16.0",
- "webpack-cli": "^3.0.8"
+ "typedoc-plugin-external-module-name": "^1.1.3",
+ "typescript": "^3.0.1",
+ "webpack": "^4.16.3",
+ "webpack-cli": "^3.1.0"
},
"main": "index.js",
"types": "index.d.ts",
diff --git a/src/builtins.ts b/src/builtins.ts
index 2c246026..f8ea4ddb 100644
--- a/src/builtins.ts
+++ b/src/builtins.ts
@@ -134,7 +134,7 @@ export function compileCall(
compiler.currentType = Type.bool;
if (!type) return module.createUnreachable();
let classType = type.classReference;
- return classType != null && classType.lookupOverload(OperatorKind.INDEXED_GET) != null
+ return classType !== null && classType.lookupOverload(OperatorKind.INDEXED_GET) !== null
? module.createI32(1)
: module.createI32(0);
}
@@ -175,6 +175,19 @@ export function compileCall(
compiler.currentType = Type.bool;
return module.createI32(getExpressionId(expr) == ExpressionId.Const ? 1 : 0);
}
+ case "isManaged": { // isManaged() -> bool
+ if (!compiler.program.hasGC) {
+ compiler.currentType = Type.bool;
+ return module.createI32(0);
+ }
+ let type = evaluateConstantType(compiler, typeArguments, operands, reportNode);
+ compiler.currentType = Type.bool;
+ if (!type) return module.createUnreachable();
+ let classType = type.classReference;
+ return classType !== null && !classType.hasDecorator(DecoratorFlags.UNMANAGED)
+ ? module.createI32(1)
+ : module.createI32(0);
+ }
// math
@@ -2881,25 +2894,46 @@ export function compileAllocate(
assert(classInstance.program == program);
var module = compiler.module;
var options = compiler.options;
- var allocateInstance = program.memoryAllocateInstance;
- if (!allocateInstance) {
- program.error(
- DiagnosticCode.Cannot_find_name_0,
- reportNode.range, "memory.allocate"
- );
- return module.createUnreachable();
- }
- if (!compiler.compileFunction(allocateInstance)) return module.createUnreachable();
- compiler.currentType = classInstance.type;
- return module.createCall(
- allocateInstance.internalName, [
- options.isWasm64
- ? module.createI64(classInstance.currentMemoryOffset)
- : module.createI32(classInstance.currentMemoryOffset)
- ],
- options.nativeSizeType
- );
+ // __gc_allocate(size, markFn)
+ if (program.hasGC && classInstance.type.isManaged(program)) {
+ let allocateInstance = assert(program.gcAllocateInstance);
+ if (!compiler.compileFunction(allocateInstance)) return module.createUnreachable();
+ compiler.currentType = classInstance.type;
+ return module.createCall(
+ allocateInstance.internalName, [
+ options.isWasm64
+ ? module.createI64(classInstance.currentMemoryOffset)
+ : module.createI32(classInstance.currentMemoryOffset),
+ module.createI32(
+ ensureGCHook(compiler, classInstance)
+ )
+ ],
+ options.nativeSizeType
+ );
+
+ // memory.allocate(size)
+ } else {
+ let allocateInstance = program.memoryAllocateInstance;
+ if (!allocateInstance) {
+ program.error(
+ DiagnosticCode.Cannot_find_name_0,
+ reportNode.range, "memory.allocate"
+ );
+ return module.createUnreachable();
+ }
+ if (!compiler.compileFunction(allocateInstance)) return module.createUnreachable();
+
+ compiler.currentType = classInstance.type;
+ return module.createCall(
+ allocateInstance.internalName, [
+ options.isWasm64
+ ? module.createI64(classInstance.currentMemoryOffset)
+ : module.createI32(classInstance.currentMemoryOffset)
+ ],
+ options.nativeSizeType
+ );
+ }
}
/** Compiles an abort wired to the conditionally imported 'abort' function. */
@@ -2921,7 +2955,7 @@ export function compileAbort(
? compiler.compileExpression(message, stringType, ConversionKind.IMPLICIT, WrapMode.NONE)
: stringType.toNativeZero(module);
- var filenameArg = compiler.compileStaticString(reportNode.range.source.normalizedPath);
+ var filenameArg = compiler.ensureStaticString(reportNode.range.source.normalizedPath);
compiler.currentType = Type.void;
return module.createBlock(null, [
@@ -2988,3 +3022,122 @@ export function compileIterateRoots(compiler: Compiler): void {
: module.createNop()
);
}
+
+/** Ensures that the specified class's GC hook exists and returns its function table index. */
+export function ensureGCHook(
+ compiler: Compiler,
+ classInstance: Class
+): u32 {
+ var program = compiler.program;
+ assert(classInstance.type.isManaged(program));
+
+ // check if the GC hook has already been created
+ {
+ let existingIndex = classInstance.gcHookIndex;
+ if (existingIndex != -1) return existingIndex;
+ }
+
+ // check if the class implements a custom GC function (only valid for internals)
+ var members = classInstance.members;
+ if (classInstance.prototype.declaration.range.source.isLibrary) {
+ if (members !== null && members.has("__gc")) {
+ let gcPrototype = assert(members.get("__gc"));
+ assert(gcPrototype.kind == ElementKind.FUNCTION_PROTOTYPE);
+ let gcInstance = assert(program.resolver.resolveFunction(gcPrototype, null));
+ assert(gcInstance.is(CommonFlags.PRIVATE | CommonFlags.INSTANCE));
+ assert(!gcInstance.isAny(CommonFlags.AMBIENT | CommonFlags.VIRTUAL));
+ assert(gcInstance.signature.parameterTypes.length == 0);
+ assert(gcInstance.signature.returnType == Type.void);
+ gcInstance.internalName = classInstance.internalName + "~gc";
+ assert(compiler.compileFunction(gcInstance));
+ let index = compiler.ensureFunctionTableEntry(gcInstance);
+ classInstance.gcHookIndex = index;
+ return index;
+ }
+ }
+
+ var module = compiler.module;
+ var options = compiler.options;
+ var nativeSizeType = options.nativeSizeType;
+ var nativeSizeSize = options.usizeType.byteSize;
+ var body = new Array();
+
+ // nothing to mark if 'this' is null
+ body.push(
+ module.createIf(
+ module.createUnary(
+ options.isWasm64
+ ? UnaryOp.EqzI64
+ : UnaryOp.EqzI32,
+ module.createGetLocal(0, nativeSizeType)
+ ),
+ module.createReturn()
+ )
+ );
+
+ // remember the function index so we don't recurse infinitely
+ var functionTable = compiler.functionTable;
+ var gcHookIndex = functionTable.length;
+ functionTable.push(0);
+ classInstance.gcHookIndex = gcHookIndex;
+
+ // if the class extends a base class, call its hook first (calls mark)
+ var baseInstance = classInstance.base;
+ if (baseInstance) {
+ assert(baseInstance.type.isManaged(program));
+ body.push(
+ module.createCallIndirect(
+ module.createI32(
+ ensureGCHook(compiler, baseInstance.type.classReference)
+ ),
+ [
+ module.createGetLocal(0, nativeSizeType)
+ ],
+ nativeSizeType == NativeType.I64 ? "Iv" : "iv"
+ )
+ );
+
+ // if this class is the top-most base class, mark the instance
+ } else {
+ body.push(
+ module.createCall(assert(program.gcMarkInstance).internalName, [
+ module.createGetLocal(0, nativeSizeType)
+ ], NativeType.None)
+ );
+ }
+
+ // mark instances assigned to own fields that are again references
+ if (members) {
+ for (let member of members.values()) {
+ if (member.kind == ElementKind.FIELD) {
+ if ((member).parent === classInstance) {
+ let type = (member).type;
+ if (type.isManaged(program)) {
+ let offset = (member).memoryOffset;
+ assert(offset >= 0);
+ body.push(
+ module.createCall(assert(program.gcMarkInstance).internalName, [
+ module.createLoad(
+ nativeSizeSize,
+ false,
+ module.createGetLocal(0, nativeSizeType),
+ nativeSizeType,
+ offset
+ )
+ ], NativeType.None)
+ );
+ }
+ }
+ }
+ }
+ }
+
+ // add the function to the module and return its table index
+ functionTable[gcHookIndex] = module.addFunction(
+ classInstance.internalName + "~gc",
+ compiler.ensureFunctionType(null, Type.void, options.usizeType),
+ null,
+ module.createBlock(null, body)
+ );
+ return gcHookIndex;
+}
diff --git a/src/compiler.ts b/src/compiler.ts
index 1156a734..5872754c 100644
--- a/src/compiler.ts
+++ b/src/compiler.ts
@@ -36,7 +36,8 @@ import {
getGetLocalIndex,
getBlockChildCount,
getBlockChild,
- getBlockName
+ getBlockName,
+ needsExplicitUnreachable
} from "./module";
import {
@@ -74,7 +75,7 @@ import {
} from "./program";
import {
- Resolver
+ Resolver, ReportMode
} from "./resolver";
import {
@@ -155,6 +156,8 @@ import {
} from "./types";
import {
+ writeI8,
+ writeI16,
writeI32,
writeI64,
writeF32,
@@ -282,7 +285,7 @@ export class Compiler extends DiagnosticEmitter {
/** Map of already compiled static string segments. */
stringSegments: Map = new Map();
/** Function table being compiled. */
- functionTable: Function[] = [];
+ functionTable: FunctionRef[] = [];
/** Argument count helper global. */
argcVar: GlobalRef = 0;
/** Argument count helper setter. */
@@ -391,11 +394,7 @@ export class Compiler extends DiagnosticEmitter {
var functionTableSize = functionTable.length;
var functionTableExported = false;
if (functionTableSize) {
- let entries = new Array(functionTableSize);
- for (let i = 0; i < functionTableSize; ++i) {
- entries[i] = functionTable[i].ref;
- }
- module.setFunctionTable(entries);
+ module.setFunctionTable(functionTable);
module.addTableExport("0", "table");
functionTableExported = true;
}
@@ -1474,7 +1473,7 @@ export class Compiler extends DiagnosticEmitter {
// insert the trampoline if the function has optional parameters
func = this.ensureTrampoline(func);
}
- functionTable.push(func);
+ functionTable.push(func.ref);
func.functionTableIndex = index;
return index;
}
@@ -1581,7 +1580,10 @@ export class Compiler extends DiagnosticEmitter {
default: stmts.push(stmt);
case ExpressionId.Nop:
}
- if (flow.isAny(FlowFlags.ANY_TERMINATING)) break;
+ if (flow.isAny(FlowFlags.ANY_TERMINATING)) {
+ if (needsExplicitUnreachable(stmt)) stmts.push(this.module.createUnreachable());
+ break;
+ }
}
return stmts;
}
@@ -1682,7 +1684,7 @@ export class Compiler extends DiagnosticEmitter {
);
parentFlow.inherit(flow);
- return module.createBlock(breakLabel, [
+ var block: ExpressionRef[] = [
module.createLoop(continueLabel,
terminated
? body // skip trailing continue if unnecessary
@@ -1691,7 +1693,9 @@ export class Compiler extends DiagnosticEmitter {
module.createBreak(continueLabel, condExpr)
], NativeType.None)
)
- ], terminated ? NativeType.Unreachable : NativeType.None);
+ ];
+ if (terminated) block.push(module.createUnreachable());
+ return module.createBlock(breakLabel, block);
}
compileEmptyStatement(statement: EmptyStatement): ExpressionRef {
@@ -1754,7 +1758,10 @@ export class Compiler extends DiagnosticEmitter {
var incrExpr = statement.incrementor
? this.compileExpression(statement.incrementor, Type.void, ConversionKind.IMPLICIT, WrapMode.NONE)
: 0;
- var bodyExpr = this.compileStatement(statement.statement);
+ var bodyStatement = statement.statement;
+ var bodyExpr = bodyStatement.kind == NodeKind.BLOCK && (bodyStatement).statements.length == 1
+ ? this.compileStatement((bodyStatement).statements[0])
+ : this.compileStatement(bodyStatement);
// Switch back to the parent flow
currentFunction.flow = flow.free();
@@ -1766,7 +1773,6 @@ export class Compiler extends DiagnosticEmitter {
FlowFlags.CONTINUES |
FlowFlags.CONDITIONALLY_CONTINUES
);
- var terminated = alwaysTrue && flow.isAny(FlowFlags.ANY_TERMINATING);
if (alwaysTrue) parentFlow.inherit(flow);
else parentFlow.inheritConditional(flow);
@@ -1793,18 +1799,10 @@ export class Compiler extends DiagnosticEmitter {
);
breakBlock.push(
- module.createLoop(repeatLabel,
- module.createBlock(null, repeatBlock, NativeType.None)
- )
+ module.createLoop(repeatLabel, module.createBlock(null, repeatBlock, NativeType.None))
);
- return module.createBlock(
- breakLabel,
- breakBlock,
- terminated
- ? NativeType.Unreachable
- : NativeType.None
- );
+ return module.createBlock(breakLabel, breakBlock);
}
compileIfStatement(statement: IfStatement): ExpressionRef {
@@ -2249,7 +2247,6 @@ export class Compiler extends DiagnosticEmitter {
var body = this.compileStatement(statement.statement);
var alwaysTrue = false; // TODO
- var alwaysReturns = alwaysTrue && flow.is(FlowFlags.RETURNS);
var terminated = flow.isAny(FlowFlags.ANY_TERMINATING);
// Switch back to the parent flow
@@ -2264,7 +2261,7 @@ export class Compiler extends DiagnosticEmitter {
if (alwaysTrue) parentFlow.inherit(flow);
else parentFlow.inheritConditional(flow);
- var expr = module.createBlock(breakLabel, [
+ return module.createBlock(breakLabel, [
module.createLoop(continueLabel,
module.createIf(condExpr,
terminated
@@ -2275,8 +2272,7 @@ export class Compiler extends DiagnosticEmitter {
], NativeType.None)
)
)
- ], alwaysReturns ? NativeType.Unreachable : NativeType.None);
- return expr;
+ ]);
}
// expressions
@@ -4808,6 +4804,7 @@ export class Compiler extends DiagnosticEmitter {
);
let tempLocalIndex = tempLocal.index;
// TODO: simplify if valueWithCorrectType has no side effects
+ // TODO: call __gc_link here if a GC is present
return module.createBlock(null, [
module.createSetLocal(tempLocalIndex, valueWithCorrectType),
module.createStore(
@@ -4820,6 +4817,7 @@ export class Compiler extends DiagnosticEmitter {
module.createGetLocal(tempLocalIndex, nativeType)
], nativeType);
} else {
+ // TODO: call __gc_link here if a GC is present
return module.createStore(
type.byteSize,
thisExpr,
@@ -5107,7 +5105,11 @@ export class Compiler extends DiagnosticEmitter {
// indirect call: index argument with signature (non-generic, can't be inlined)
case ElementKind.LOCAL: {
if (signature = (target).type.signatureReference) {
- indexArg = module.createGetLocal((target).index, NativeType.I32);
+ if ((target).is(CommonFlags.INLINED)) {
+ indexArg = module.createI32(i64_low((target).constantIntegerValue));
+ } else {
+ indexArg = module.createGetLocal((target).index, NativeType.I32);
+ }
break;
} else {
this.error(
@@ -6094,6 +6096,7 @@ export class Compiler extends DiagnosticEmitter {
return this.compileArrayLiteral(
assert(classType.typeArguments)[0],
(expression).elementExpressions,
+ false, // TODO: isConst?
expression
);
}
@@ -6204,7 +6207,7 @@ export class Compiler extends DiagnosticEmitter {
}
case LiteralKind.STRING: {
assert(!implicitNegate);
- return this.compileStaticString((expression).value);
+ return this.compileStringLiteral(expression);
}
case LiteralKind.OBJECT: {
assert(!implicitNegate);
@@ -6220,241 +6223,282 @@ export class Compiler extends DiagnosticEmitter {
return module.createUnreachable();
}
- compileStaticString(stringValue: string): ExpressionRef {
+ /** Ensures that the specified string exists in static memory and returns a pointer to it. */
+ ensureStaticString(stringValue: string): ExpressionRef {
+ var program = this.program;
var module = this.module;
var options = this.options;
var stringSegments = this.stringSegments;
+ var needsGCHeader = program.hasGC;
- var stringSegment: MemorySegment | null = stringSegments.get(stringValue);
- if (!stringSegment) {
+ var stringSegment: MemorySegment;
+ var stringOffset: I64;
+ if (!stringSegments.has(stringValue)) {
let stringLength = stringValue.length;
- let stringBuffer = new Uint8Array(4 + stringLength * 2);
- stringBuffer[0] = stringLength & 0xff;
- stringBuffer[1] = (stringLength >>> 8) & 0xff;
- stringBuffer[2] = (stringLength >>> 16) & 0xff;
- stringBuffer[3] = (stringLength >>> 24) & 0xff;
+ let stringSize = 4 + stringLength * 2;
+ let offset = 0;
+ let gcHeaderSize = program.gcHeaderSize;
+ if (needsGCHeader) {
+ stringSize += gcHeaderSize;
+ offset += gcHeaderSize;
+ }
+ let stringBuffer = new Uint8Array(stringSize);
+ stringBuffer[offset ] = stringLength & 0xff;
+ stringBuffer[offset + 1] = (stringLength >>> 8) & 0xff;
+ stringBuffer[offset + 2] = (stringLength >>> 16) & 0xff;
+ stringBuffer[offset + 3] = (stringLength >>> 24) & 0xff;
for (let i = 0; i < stringLength; ++i) {
- stringBuffer[4 + i * 2] = stringValue.charCodeAt(i) & 0xff;
- stringBuffer[5 + i * 2] = (stringValue.charCodeAt(i) >>> 8) & 0xff;
+ stringBuffer[offset + 4 + i * 2] = stringValue.charCodeAt(i) & 0xff;
+ stringBuffer[offset + 5 + i * 2] = (stringValue.charCodeAt(i) >>> 8) & 0xff;
}
stringSegment = this.addMemorySegment(stringBuffer, options.usizeType.byteSize);
stringSegments.set(stringValue, stringSegment);
+ if (needsGCHeader) {
+ stringOffset = i64_add(stringSegment.offset, i64_new(gcHeaderSize, 0));
+ } else {
+ stringOffset = stringSegment.offset;
+ }
+ } else {
+ stringSegment = stringSegments.get(stringValue);
+ stringOffset = stringSegment.offset;
+ }
+ if (program.typesLookup.has("string")) {
+ let stringType = program.typesLookup.get("string");
+ this.currentType = stringType;
+ } else {
+ this.currentType = options.usizeType;
}
- var stringOffset = stringSegment.offset;
- var stringType = this.program.typesLookup.get("string");
- this.currentType = stringType ? stringType : options.usizeType;
if (options.isWasm64) {
return module.createI64(i64_low(stringOffset), i64_high(stringOffset));
+ } else {
+ assert(i64_is_i32(stringOffset));
+ return module.createI32(i64_low(stringOffset));
}
- assert(i64_is_i32(stringOffset));
- return module.createI32(i64_low(stringOffset));
}
- compileArrayLiteral(elementType: Type, expressions: (Expression | null)[], reportNode: Node): ExpressionRef {
- var isStatic = true;
+ compileStringLiteral(expression: StringLiteralExpression): ExpressionRef {
+ return this.ensureStaticString(expression.value);
+ }
+
+ /** Ensures that the specified array exists in static memory and returns a pointer to it. */
+ ensureStaticArray(elementType: Type, values: ExpressionRef[]): ExpressionRef {
+ var length = values.length;
+ var byteSize = elementType.byteSize;
+ var byteLength = length * byteSize;
+ var usizeTypeSize = this.options.usizeType.byteSize;
+
+ // determine the size of the Array header
+ var arrayHeaderSize = (usizeTypeSize + 4 + 7) & ~7; // .buffer_ + .length_ + alignment
+ var arrayTotalSize = arrayHeaderSize;
+
+ // determine the size of the ArrayBuffer
+ var bufferHeaderSize = (4 + 7) & ~7; // .byteLength + alignment
+ var bufferTotalSize = 1 << (32 - clz(byteLength + bufferHeaderSize - 1)); // see internals
+
+ var program = this.program;
+ var needsGC = program.hasGC;
+ var gcHeaderSize = program.gcHeaderSize;
+
+ var offset = 0;
+ if (needsGC) {
+ offset += gcHeaderSize; // start writing after GC header
+ arrayTotalSize += gcHeaderSize;
+ bufferTotalSize += gcHeaderSize;
+ }
+
+ // create a compound segment holding both the the Array header and the ArrayBuffer
+ var buffer = new Uint8Array(arrayHeaderSize + bufferTotalSize);
+ var segment = this.addMemorySegment(buffer);
+
+ // write the Array header first
+ if (usizeTypeSize == 8) {
+ writeI64(i64_add(segment.offset, i64_new(arrayHeaderSize)), buffer, offset); // .buffer_
+ offset += 8;
+ } else {
+ assert(i64_is_u32(segment.offset));
+ writeI32(i64_low(segment.offset) + arrayHeaderSize, buffer, offset); // .buffer_
+ offset += 4;
+ }
+ writeI32(length, buffer, offset); // .length_
+ offset += 4;
+ assert(((offset + 7) & ~7) == arrayTotalSize); // incl. GC header if applicable
+
+ // append the ArrayBuffer
+ offset = arrayTotalSize;
+ if (needsGC) offset += gcHeaderSize;
+ writeI32(byteLength, buffer, offset); // .byteLength
+ offset += bufferHeaderSize; // align
+ var nativeType = elementType.toNativeType();
+ switch (nativeType) {
+ case NativeType.I32: {
+ switch (byteSize) {
+ case 1: {
+ for (let i = 0; i < length; ++i) {
+ let value = values[i];
+ assert(getExpressionType(value) == nativeType);
+ assert(getExpressionId(value) == ExpressionId.Const);
+ writeI8(getConstValueI32(value), buffer, offset);
+ offset += 1;
+ }
+ break;
+ }
+ case 2: {
+ for (let i = 0; i < length; ++i) {
+ let value = values[i];
+ assert(getExpressionType(value) == nativeType);
+ assert(getExpressionId(value) == ExpressionId.Const);
+ writeI16(getConstValueI32(value), buffer, offset);
+ offset += 2;
+ }
+ break;
+ }
+ case 4: {
+ for (let i = 0; i < length; ++i) {
+ let value = values[i];
+ assert(getExpressionType(value) == nativeType);
+ assert(getExpressionId(value) == ExpressionId.Const);
+ writeI32(getConstValueI32(value), buffer, offset);
+ offset += 4;
+ }
+ break;
+ }
+ default: assert(false);
+ }
+ break;
+ }
+ case NativeType.I64: {
+ for (let i = 0; i < length; ++i) {
+ let value = values[i];
+ assert(getExpressionType(value) == nativeType);
+ assert(getExpressionId(value) == ExpressionId.Const);
+ writeI64(i64_new(getConstValueI64Low(value), getConstValueI64High(value)), buffer, offset);
+ offset += 8;
+ }
+ break;
+ }
+ case NativeType.F32: {
+ for (let i = 0; i < length; ++i) {
+ let value = values[i];
+ assert(getExpressionType(value) == nativeType);
+ assert(getExpressionId(value) == ExpressionId.Const);
+ writeF32(getConstValueF32(value), buffer, offset);
+ offset += 4;
+ }
+ break;
+ }
+ case NativeType.F64: {
+ for (let i = 0; i < length; ++i) {
+ let value = values[i];
+ assert(getExpressionType(value) == nativeType);
+ assert(getExpressionId(value) == ExpressionId.Const);
+ writeF64(getConstValueF64(value), buffer, offset);
+ offset += 8;
+ }
+ break;
+ }
+ default: assert(false);
+ }
+ assert(offset <= arrayTotalSize + bufferTotalSize); // might have empty trailing space
+
+ var arrayPrototype = this.program.arrayPrototype;
+ if (arrayPrototype) {
+ let arrayInstance = this.resolver.resolveClass(arrayPrototype, [ elementType ], null, ReportMode.REPORT);
+ if (!arrayInstance) {
+ this.currentType = this.options.usizeType;
+ return this.module.createUnreachable();
+ }
+ this.currentType = arrayInstance.type;
+ } else {
+ this.currentType = this.options.usizeType;
+ }
+
+ // return a pointer at the array header (skip GC header if present)
+ var address = segment.offset;
+ if (needsGC) address = i64_add(address, i64_new(gcHeaderSize, 0));
+ if (usizeTypeSize == 8) {
+ return this.module.createI64(i64_low(address), i64_high(address));
+ } else {
+ assert(i64_is_u32(address));
+ return this.module.createI32(i64_low(address));
+ }
+ }
+
+ compileArrayLiteral(
+ elementType: Type,
+ expressions: (Expression | null)[],
+ isConst: bool,
+ reportNode: Node
+ ): ExpressionRef {
var module = this.module;
- // obtain the array type
+ // find out whether all elements are constant (array is static)
+ var length = expressions.length;
+ var values = new Array(length);
+ var nativeElementType = elementType.toNativeType();
+ var isStatic = true;
+ for (let i = 0; i < length; ++i) {
+ values[i] = expressions[i]
+ ? this.compileExpression(expressions[i], elementType, ConversionKind.IMPLICIT, WrapMode.NONE)
+ : elementType.toNativeZero(module);
+ if (isStatic) {
+ let expr = module.precomputeExpression(values[i]);
+ if (getExpressionId(expr) == ExpressionId.Const) {
+ assert(getExpressionType(expr) == nativeElementType);
+ } else {
+ if (isConst) {
+ this.warning(
+ DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable,
+ reportNode.range
+ );
+ }
+ isStatic = false;
+ }
+ }
+ }
+
+ // make a static array if possible
+ if (isStatic) return this.ensureStaticArray(elementType, values);
+
+ // otherwise obtain the array type
var arrayPrototype = assert(this.program.arrayPrototype);
if (!arrayPrototype || arrayPrototype.kind != ElementKind.CLASS_PROTOTYPE) return module.createUnreachable();
var arrayInstance = this.resolver.resolveClass(arrayPrototype, [ elementType ]);
if (!arrayInstance) return module.createUnreachable();
var arrayType = arrayInstance.type;
- var elementCount = expressions.length;
- if (elementCount) { // non-empty static or dynamic
- let nativeElementType = elementType.toNativeType();
- let values: usize;
- let byteLength: usize;
- switch (nativeElementType) {
- case NativeType.I32: {
- values = changetype(new Int32Array(elementCount));
- byteLength = elementCount * 4;
- break;
- }
- case NativeType.I64: {
- values = changetype(new Array(elementCount));
- byteLength = elementCount * 8;
- break;
- }
- case NativeType.F32: {
- values = changetype(new Float32Array(elementCount));
- byteLength = elementCount * 4;
- break;
- }
- case NativeType.F64: {
- values = changetype(new Float64Array(elementCount));
- byteLength = elementCount * 8;
- break;
- }
- default: {
- assert(false);
- return module.createUnreachable();
- }
- }
-
- // precompute value expressions
- let exprs = new Array(elementCount);
- let expr: BinaryenExpressionRef;
- for (let i = 0; i < elementCount; ++i) {
- exprs[i] = expressions[i]
- ? this.compileExpression(expressions[i], elementType, ConversionKind.IMPLICIT, WrapMode.NONE)
- : elementType.toNativeZero(module);
- if (isStatic) {
- expr = module.precomputeExpression(exprs[i]);
- if (getExpressionId(expr) == ExpressionId.Const) {
- assert(getExpressionType(expr) == nativeElementType);
- switch (nativeElementType) {
- case NativeType.I32: {
- changetype(values)[i] = getConstValueI32(expr);
- break;
- }
- case NativeType.I64: {
- changetype(values)[i] = i64_new(
- getConstValueI64Low(expr),
- getConstValueI64High(expr)
- );
- break;
- }
- case NativeType.F32: {
- changetype(values)[i] = getConstValueF32(expr);
- break;
- }
- case NativeType.F64: {
- changetype(values)[i] = getConstValueF64(expr);
- break;
- }
- default: assert(false); // checked above
- }
- } else {
- // TODO: emit a warning if declared 'const'
- // if (isConst) {
- // this.warn(
- // DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable,
- // reportNode.range
- // );
- // }
- isStatic = false;
- }
- }
- }
-
- let usizeTypeSize = this.options.usizeType.byteSize;
- if (isStatic) { // non-empty, all elements can be precomputed
-
- // Create a combined static memory segment composed of:
- // Array struct + ArrayBuffer struct + aligned ArrayBuffer data
-
- let arraySize = usizeTypeSize + 4; // buffer_ & length_
- let bufferHeaderSize = (4 + 7) & ~7; // aligned byteLength (8)
- let bufferTotalSize = 1 << (32 - clz(byteLength + bufferHeaderSize - 1)); // see internals
- let data = new Uint8Array(arraySize + bufferTotalSize);
- let segment = this.addMemorySegment(data);
- let offset = 0;
-
- // write Array struct
- if (usizeTypeSize == 8) {
- writeI64(i64_add(segment.offset, i64_new(arraySize)), data, offset); // buffer_ @ segment[arSize]
- offset += 8;
- } else {
- assert(i64_high(segment.offset) == 0);
- writeI32(i64_low(segment.offset) + arraySize, data, offset); // buffer_ @ segment[arSize]
- offset += 4;
- }
- writeI32(elementCount, data, offset); // length_
- offset += 4;
- assert(offset == arraySize);
-
- // write ArrayBuffer struct
- writeI32(byteLength, data, offset);
- offset += bufferHeaderSize; // incl. alignment
-
- // write ArrayBuffer data
- switch (nativeElementType) {
- case NativeType.I32: {
- for (let i = 0; i < elementCount; ++i) {
- writeI32(changetype(values)[i], data, offset); offset += 4;
- }
- break;
- }
- case NativeType.I64: {
- for (let i = 0; i < elementCount; ++i) {
- writeI64(changetype(values)[i], data, offset); offset += 8;
- }
- break;
- }
- case NativeType.F32: {
- for (let i = 0; i < elementCount; ++i) {
- writeF32(changetype(values)[i], data, offset); offset += 4;
- }
- break;
- }
- case NativeType.F64: {
- for (let i = 0; i < elementCount; ++i) {
- writeF64(changetype(values)[i], data, offset); offset += 8;
- }
- break;
- }
- default: {
- assert(false);
- return module.createUnreachable();
- }
- }
- assert(offset <= arraySize + bufferTotalSize);
-
- this.currentType = arrayType;
- return usizeTypeSize == 8
- ? module.createI64(
- i64_low(segment.offset),
- i64_high(segment.offset)
- )
- : module.createI32(
- i64_low(segment.offset)
- );
-
- } else { // non-empty, some elements can't be precomputed
-
- this.currentType = arrayType;
- let setter = arrayInstance.lookupOverload(OperatorKind.INDEXED_SET, true);
- if (!setter) {
- this.error(
- DiagnosticCode.Index_signature_in_type_0_only_permits_reading,
- reportNode.range, arrayInstance.internalName
- );
- return module.createUnreachable();
- }
- let nativeArrayType = arrayType.toNativeType();
- let currentFunction = this.currentFunction;
- let tempLocal = currentFunction.getTempLocal(arrayType, false);
- let stmts = new Array(2 + elementCount);
- let index = 0;
- stmts[index++] = module.createSetLocal(tempLocal.index,
- this.makeCallDirect(assert(arrayInstance.constructorInstance), [
- module.createI32(0), // this
- module.createI32(elementCount)
- ])
- );
- for (let i = 0; i < elementCount; ++i) {
- stmts[index++] = this.makeCallDirect(setter, [
- module.createGetLocal(tempLocal.index, nativeArrayType), // this
- module.createI32(i),
- exprs[i]
- ]);
- }
- assert(index + 1 == stmts.length);
- stmts[index] = module.createGetLocal(tempLocal.index, nativeArrayType);
- currentFunction.freeTempLocal(tempLocal);
- this.currentType = arrayType;
- return module.createBlock(null, stmts, nativeArrayType);
- }
-
- } else { // empty, TBD: cache this somehow?
- this.currentType = arrayType;
- return this.makeCallDirect(assert(arrayInstance.constructorInstance), [
+ // and compile an explicit instantiation
+ this.currentType = arrayType;
+ var setter = arrayInstance.lookupOverload(OperatorKind.INDEXED_SET, true);
+ if (!setter) {
+ this.error(
+ DiagnosticCode.Index_signature_in_type_0_only_permits_reading,
+ reportNode.range, arrayInstance.internalName
+ );
+ return module.createUnreachable();
+ }
+ var nativeArrayType = arrayType.toNativeType();
+ var currentFunction = this.currentFunction;
+ var tempLocal = currentFunction.getTempLocal(arrayType, false);
+ var stmts = new Array(2 + length);
+ var index = 0;
+ stmts[index++] = module.createSetLocal(tempLocal.index,
+ this.makeCallDirect(assert(arrayInstance.constructorInstance), [
module.createI32(0), // this
- module.createI32(0)
+ module.createI32(length)
+ ])
+ );
+ for (let i = 0; i < length; ++i) {
+ stmts[index++] = this.makeCallDirect(setter, [
+ module.createGetLocal(tempLocal.index, nativeArrayType), // this
+ module.createI32(i),
+ values[i]
]);
}
+ assert(index + 1 == stmts.length);
+ stmts[index] = module.createGetLocal(tempLocal.index, nativeArrayType);
+ currentFunction.freeTempLocal(tempLocal);
+ this.currentType = arrayType;
+ return module.createBlock(null, stmts, nativeArrayType);
}
compileObjectLiteral(expression: ObjectLiteralExpression, contextualType: Type): ExpressionRef {
diff --git a/src/module.ts b/src/module.ts
index ec8a4521..306a2504 100644
--- a/src/module.ts
+++ b/src/module.ts
@@ -16,14 +16,14 @@ export type ImportRef = usize;
export type ExportRef = usize;
export type Index = u32;
-export enum NativeType {
- None = _BinaryenTypeNone(),
- I32 = _BinaryenTypeInt32(),
- I64 = _BinaryenTypeInt64(),
- F32 = _BinaryenTypeFloat32(),
- F64 = _BinaryenTypeFloat64(),
- Unreachable = _BinaryenTypeUnreachable(),
- Auto = _BinaryenTypeAuto()
+export const enum NativeType {
+ None = 0, // _BinaryenTypeNone(),
+ I32 = 1, // _BinaryenTypeInt32(),
+ I64 = 2, // _BinaryenTypeInt64(),
+ F32 = 3, // _BinaryenTypeFloat32(),
+ F64 = 4, // _BinaryenTypeFloat64(),
+ Unreachable = 5, // _BinaryenTypeUnreachable(),
+ Auto = -1 // _BinaryenTypeAuto()
}
export enum ExpressionId {
@@ -1580,3 +1580,26 @@ export class BinaryModule {
/** Source map, if generated. */
sourceMap: string | null;
}
+
+/** Tests if an expression needs an explicit 'unreachable' when it is the terminating statement. */
+export function needsExplicitUnreachable(expr: ExpressionRef): bool {
+ // not applicable if pushing a value to the stack
+ switch (_BinaryenExpressionGetType(expr)) {
+ case NativeType.I32:
+ case NativeType.I64:
+ case NativeType.F32:
+ case NativeType.F64: return false;
+ }
+ switch (_BinaryenExpressionGetId(expr)) {
+ case ExpressionId.Unreachable:
+ case ExpressionId.Return: return false;
+ case ExpressionId.Break: return _BinaryenBreakGetCondition(expr) != 0;
+ case ExpressionId.Block: {
+ if (!_BinaryenBlockGetName(expr)) { // can't break out of it
+ let numChildren = _BinaryenBlockGetNumChildren(expr); // last child needs unreachable
+ return numChildren > 0 && needsExplicitUnreachable(_BinaryenBlockGetChild(expr, numChildren - 1));
+ }
+ }
+ }
+ return true;
+}
diff --git a/src/program.ts b/src/program.ts
index 4b53dacd..35f6b172 100644
--- a/src/program.ts
+++ b/src/program.ts
@@ -341,6 +341,17 @@ export class Program extends DiagnosticEmitter {
/** Memory allocation function. */
memoryAllocateInstance: Function | null = null;
+ /** Whether a garbage collector is present or not. */
+ hasGC: bool = false;
+ /** Garbage collector allocation function. */
+ gcAllocateInstance: Function | null = null;
+ /** Garbage collector link function called when a managed object is referenced from a parent. */
+ gcLinkInstance: Function | null = null;
+ /** Garbage collector mark function called to on reachable managed objects. */
+ gcMarkInstance: Function | null = null;
+ /** Size of a managed object header. */
+ gcHeaderSize: u32 = 0;
+
/** Currently processing filespace. */
currentFilespace: Filespace;
@@ -658,6 +669,48 @@ export class Program extends DiagnosticEmitter {
}
}
}
+
+ // register GC hooks if present
+ if (
+ this.elementsLookup.has("__gc_allocate") &&
+ this.elementsLookup.has("__gc_link") &&
+ this.elementsLookup.has("__gc_mark")
+ ) {
+ // __gc_allocate(usize, (ref: usize) => void): usize
+ let element = this.elementsLookup.get("__gc_allocate");
+ assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
+ let gcAllocateInstance = assert(this.resolver.resolveFunction(element, null));
+ let signature = gcAllocateInstance.signature;
+ assert(signature.parameterTypes.length == 2);
+ assert(signature.parameterTypes[0] == this.options.usizeType);
+ assert(signature.parameterTypes[1].signatureReference);
+ assert(signature.returnType == this.options.usizeType);
+
+ // __gc_link(usize, usize): void
+ element = this.elementsLookup.get("__gc_link");
+ assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
+ let gcLinkInstance = assert(this.resolver.resolveFunction(element, null));
+ signature = gcLinkInstance.signature;
+ assert(signature.parameterTypes.length == 2);
+ assert(signature.parameterTypes[0] == this.options.usizeType);
+ assert(signature.parameterTypes[1] == this.options.usizeType);
+ assert(signature.returnType == Type.void);
+
+ // __gc_mark(usize): void
+ element = this.elementsLookup.get("__gc_mark");
+ assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
+ let gcMarkInstance = assert(this.resolver.resolveFunction(element, null));
+ signature = gcMarkInstance.signature;
+ assert(signature.parameterTypes.length == 1);
+ assert(signature.parameterTypes[0] == this.options.usizeType);
+ assert(signature.returnType == Type.void);
+
+ this.gcAllocateInstance = gcAllocateInstance;
+ this.gcLinkInstance = gcLinkInstance;
+ this.gcMarkInstance = gcMarkInstance;
+ this.gcHeaderSize = (2 * options.usizeType.byteSize + 4 + 7) & ~7; // TODO: hardcoded atm
+ this.hasGC = true;
+ }
}
/** Sets a constant integer value. */
@@ -2771,6 +2824,8 @@ export class Class extends Element {
constructorInstance: Function | null = null;
/** Operator overloads. */
overloads: Map | null = null;
+ /** Function index of the GC hook. */
+ gcHookIndex: u32 = -1;
/** Constructs a new class. */
constructor(
diff --git a/src/resolver.ts b/src/resolver.ts
index b37a96b4..7d23410e 100644
--- a/src/resolver.ts
+++ b/src/resolver.ts
@@ -377,7 +377,9 @@ export class Resolver extends DiagnosticEmitter {
case ElementKind.GLOBAL:
case ElementKind.LOCAL:
case ElementKind.FIELD: {
- let classReference = (target).type.classReference;
+ let type = (target).type;
+ assert(type != Type.void);
+ let classReference = type.classReference;
if (!classReference) {
this.error(
DiagnosticCode.Property_0_does_not_exist_on_type_1,
diff --git a/src/types.ts b/src/types.ts
index 9e287097..a73310c8 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -5,7 +5,9 @@
import {
Class,
- FunctionTarget
+ FunctionTarget,
+ Program,
+ DecoratorFlags
} from "./program";
import {
@@ -134,6 +136,15 @@ export class Type {
}
}
+ /** Tests if this is a managed type that needs GC hooks. */
+ isManaged(program: Program): bool {
+ if (program.hasGC) {
+ let classReference = this.classReference;
+ return classReference !== null && !classReference.hasDecorator(DecoratorFlags.UNMANAGED);
+ }
+ return false;
+ }
+
/** Computes the sign-extending shift in the target type. */
computeSmallIntegerShift(targetType: Type): u32 {
return targetType.size - this.size;
diff --git a/src/util/binary.ts b/src/util/binary.ts
index ffff64f0..83e0aff0 100644
--- a/src/util/binary.ts
+++ b/src/util/binary.ts
@@ -1,5 +1,27 @@
/** @module util *//***/
+/** Reads an 8-bit integer from the specified buffer. */
+export function readI8(buffer: Uint8Array, offset: i32): i32 {
+ return buffer[offset];
+}
+
+/** Writes an 8-bit integer to the specified buffer. */
+export function writeI8(value: i32, buffer: Uint8Array, offset: i32): void {
+ buffer[offset] = value;
+}
+
+/** Reads a 16-bit integer from the specified buffer. */
+export function readI16(buffer: Uint8Array, offset: i32): i32 {
+ return buffer[offset ]
+ | buffer[offset + 1] << 8;
+}
+
+/** Writes a 16-bit integer to the specified buffer. */
+export function writeI16(value: i32, buffer: Uint8Array, offset: i32): void {
+ buffer[offset ] = value;
+ buffer[offset + 1] = value >>> 8;
+}
+
/** Reads a 32-bit integer from the specified buffer. */
export function readI32(buffer: Uint8Array, offset: i32): i32 {
return buffer[offset ]
diff --git a/std/assembly/array.ts b/std/assembly/array.ts
index 45a6c375..6ee16b40 100644
--- a/std/assembly/array.ts
+++ b/std/assembly/array.ts
@@ -1,8 +1,8 @@
import {
MAX_BLENGTH,
- HEADER_SIZE as HEADER_SIZE_AB,
- allocUnsafe,
- reallocUnsafe,
+ HEADER_SIZE,
+ allocateUnsafe,
+ reallocateUnsafe,
loadUnsafe,
storeUnsafe
} from "./internal/arraybuffer";
@@ -22,11 +22,11 @@ export class Array {
const MAX_LENGTH = MAX_BLENGTH >>> alignof();
if (length > MAX_LENGTH) throw new RangeError("Invalid array length");
var byteLength = length << alignof();
- var buffer = allocUnsafe(byteLength);
+ var buffer = allocateUnsafe(byteLength);
this.buffer_ = buffer;
this.length_ = length;
memory.fill(
- changetype(buffer) + HEADER_SIZE_AB,
+ changetype(buffer) + HEADER_SIZE,
0,
byteLength
);
@@ -42,7 +42,7 @@ export class Array {
if (length > capacity) {
const MAX_LENGTH = MAX_BLENGTH >>> alignof();
if (length > MAX_LENGTH) throw new RangeError("Invalid array length");
- buffer = reallocUnsafe(buffer, length << alignof());
+ buffer = reallocateUnsafe(buffer, length << alignof());
this.buffer_ = buffer;
}
this.length_ = length;
@@ -84,16 +84,18 @@ export class Array {
if (index >= capacity) {
const MAX_LENGTH = MAX_BLENGTH >>> alignof();
if (index >= MAX_LENGTH) throw new Error("Invalid array length");
- buffer = reallocUnsafe(buffer, (index + 1) << alignof());
+ buffer = reallocateUnsafe(buffer, (index + 1) << alignof());
this.buffer_ = buffer;
this.length_ = index + 1;
}
storeUnsafe(buffer, index, value);
+ if (isManaged()) __gc_link(changetype(this), changetype(value)); // tslint:disable-line
}
@operator("{}=")
private __unchecked_set(index: i32, value: T): void {
storeUnsafe(this.buffer_, index, value);
+ if (isManaged()) __gc_link(changetype(this), changetype(value)); // tslint:disable-line
}
includes(searchElement: T, fromIndex: i32 = 0): bool {
@@ -141,11 +143,12 @@ export class Array {
if (length >= capacity) {
const MAX_LENGTH = MAX_BLENGTH >>> alignof();
if (length >= MAX_LENGTH) throw new Error("Invalid array length");
- buffer = reallocUnsafe(buffer, newLength << alignof());
+ buffer = reallocateUnsafe(buffer, newLength << alignof());
this.buffer_ = buffer;
}
this.length_ = newLength;
storeUnsafe(buffer, length, element);
+ if (isManaged()) __gc_link(changetype(this), changetype(element)); // tslint:disable-line
return newLength;
}
@@ -217,8 +220,8 @@ export class Array {
var element = loadUnsafe(buffer, 0);
var lastIndex = length - 1;
memory.copy(
- changetype(buffer) + HEADER_SIZE_AB,
- changetype(buffer) + HEADER_SIZE_AB + sizeof(),
+ changetype(buffer) + HEADER_SIZE,
+ changetype(buffer) + HEADER_SIZE + sizeof(),
lastIndex << alignof()
);
storeUnsafe(buffer, lastIndex, null);
@@ -242,17 +245,18 @@ export class Array {
if (length >= capacity) {
const MAX_LENGTH = MAX_BLENGTH >>> alignof();
if (length >= MAX_LENGTH) throw new Error("Invalid array length");
- buffer = reallocUnsafe(buffer, newLength << alignof());
+ buffer = reallocateUnsafe(buffer, newLength << alignof());
capacity = buffer.byteLength >>> alignof();
this.buffer_ = buffer;
}
memory.copy(
- changetype(buffer) + HEADER_SIZE_AB + sizeof(),
- changetype(buffer) + HEADER_SIZE_AB,
+ changetype(buffer) + HEADER_SIZE + sizeof(),
+ changetype(buffer) + HEADER_SIZE,
(capacity - 1) << alignof()
);
storeUnsafe(buffer, 0, element);
this.length_ = newLength;
+ if (isManaged()) __gc_link(changetype(this), changetype(element)); // tslint:disable-line
return newLength;
}
@@ -268,8 +272,8 @@ export class Array {
var sliced = new Array(newLength);
if (newLength) {
memory.copy(
- changetype(sliced.buffer_) + HEADER_SIZE_AB,
- changetype(this.buffer_) + HEADER_SIZE_AB + (begin << alignof()),
+ changetype(sliced.buffer_) + HEADER_SIZE,
+ changetype(this.buffer_) + HEADER_SIZE + (begin << alignof()),
newLength << alignof()
);
}
@@ -284,8 +288,8 @@ export class Array {
deleteCount = min(deleteCount, length - start);
var buffer = this.buffer_;
memory.copy(
- changetype(buffer) + HEADER_SIZE_AB + (start << alignof()),
- changetype(buffer) + HEADER_SIZE_AB + ((start + deleteCount) << alignof()),
+ changetype(buffer) + HEADER_SIZE + (start << alignof()),
+ changetype(buffer) + HEADER_SIZE + ((start + deleteCount) << alignof()),
deleteCount << alignof()
);
this.length_ = length - deleteCount;
@@ -328,4 +332,16 @@ export class Array {
);
}
}
+
+ private __gc(): void {
+ if (isManaged()) {
+ let buffer = this.buffer_;
+ let offset: usize = 0;
+ let end = this.length_ << alignof();
+ while (offset < end) {
+ __gc_mark(load(changetype(buffer) + offset, HEADER_SIZE)); // tslint:disable-line
+ offset += sizeof();
+ }
+ }
+ }
}
diff --git a/std/assembly/arraybuffer.ts b/std/assembly/arraybuffer.ts
index d165960f..1c741556 100644
--- a/std/assembly/arraybuffer.ts
+++ b/std/assembly/arraybuffer.ts
@@ -1,7 +1,7 @@
import {
HEADER_SIZE,
MAX_BLENGTH,
- allocUnsafe
+ allocateUnsafe
} from "./internal/arraybuffer";
@sealed
@@ -11,7 +11,7 @@ export class ArrayBuffer {
constructor(length: i32, unsafe: bool = false) {
if (length > MAX_BLENGTH) throw new RangeError("Invalid array buffer length");
- var buffer = allocUnsafe(length);
+ var buffer = allocateUnsafe(length);
if (!unsafe) memory.fill(changetype(buffer) + HEADER_SIZE, 0, length);
return buffer;
}
@@ -23,7 +23,7 @@ export class ArrayBuffer {
if (end < 0) end = max(len + end, 0);
else end = min(end, len);
var newLen = max(end - begin, 0);
- var buffer = allocUnsafe(newLen);
+ var buffer = allocateUnsafe(newLen);
memory.copy(changetype(buffer) + HEADER_SIZE, changetype(this) + HEADER_SIZE + begin, newLen);
return buffer;
}
diff --git a/std/assembly/builtins.ts b/std/assembly/builtins.ts
index a8fbe294..94e8b19c 100644
--- a/std/assembly/builtins.ts
+++ b/std/assembly/builtins.ts
@@ -2,7 +2,6 @@
@builtin @inline export const NaN: f64 = 0 / 0;
@builtin @inline export const Infinity: f64 = 1 / 0;
-@builtin export declare const HEAP_BASE: usize;
@builtin export declare function isInteger(value?: T): bool;
@builtin export declare function isFloat(value?: T): bool;
@@ -12,6 +11,7 @@
@builtin export declare function isArray(value?: T): bool;
@builtin export declare function isDefined(expression: void): bool;
@builtin export declare function isConstant(expression: void): bool;
+@builtin export declare function isManaged(value?: T): bool;
@inline export function isNaN(value: T): bool { return value != value; }
@inline export function isFinite(value: T): bool { return value - value == 0; }
@@ -190,9 +190,3 @@ export namespace f64 {
}
@builtin export declare function start(): void;
-
-@builtin export declare function ERROR(message?: void): void;
-@builtin export declare function WARNING(message?: void): void;
-@builtin export declare function INFO(message?: void): void;
-
-@builtin export declare function __gc_iterate_roots(fn: (ref: usize) => void): void;
diff --git a/std/assembly/collector/itcm.ts b/std/assembly/collector/itcm.ts
index 921465cb..3b35ac97 100644
--- a/std/assembly/collector/itcm.ts
+++ b/std/assembly/collector/itcm.ts
@@ -4,18 +4,15 @@
* @module std/assembly/collector/itcm
*//***/
-// Largely based on the Bach Le's μgc, see: https://github.com/bullno1/ugc
+// Largely based on Bach Le's μgc, see: https://github.com/bullno1/ugc
const TRACE = false;
-import {
- AL_MASK,
- MAX_SIZE_32
-} from "../internal/allocator";
+/** Size of a managed object header. */
+export const HEADER_SIZE: usize = (offsetof() + AL_MASK) & ~AL_MASK;
-import {
- iterateRoots
-} from "../gc";
+import { AL_MASK, MAX_SIZE_32 } from "../internal/allocator";
+import { iterateRoots } from "../gc";
/** Collector states. */
const enum State {
@@ -35,8 +32,8 @@ var state = State.INIT;
var white = 0;
// From and to spaces
-var from: ManagedObjectList;
-var to: ManagedObjectList;
+var fromSpace: ManagedObjectList;
+var toSpace: ManagedObjectList;
var iter: ManagedObject;
// ╒═══════════════ Managed object layout (32-bit) ════════════════╕
@@ -47,15 +44,14 @@ var iter: ManagedObject;
// ├─────────────────────────────────────────────────────────┴─┴───┤ │ usize
// │ prev │ ◄─┘
// ├───────────────────────────────────────────────────────────────┤
-// │ visitFn │
+// │ 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 {
+@unmanaged class ManagedObject {
/** Pointer to the next object with color flags stored in the alignment bits. */
nextWithColor: usize;
@@ -63,11 +59,8 @@ class ManagedObject {
/** Pointer to the previous object. */
prev: ManagedObject;
- /** Visitor function called with the user-space reference. */
- visitFn: (ref: usize) => void;
-
- /** Size of a managed object after alignment. */
- static readonly SIZE: usize = (offsetof() + AL_MASK) & ~AL_MASK;
+ /** Class-specific hook function called with the user-space reference. */
+ hookFn: (ref: usize) => void;
/** Gets the pointer to the next object. */
get next(): ManagedObject {
@@ -104,14 +97,13 @@ class ManagedObject {
const gray = 2;
if (this == iter) iter = this.prev;
this.unlink();
- to.push(this);
+ 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 {
+@unmanaged class ManagedObjectList extends ManagedObject {
/** Inserts an object. */
push(obj: ManagedObject): void {
@@ -137,13 +129,13 @@ function step(): void {
switch (state) {
case State.INIT: {
if (TRACE) trace("gc~step/INIT");
- from = changetype(memory.allocate(ManagedObject.SIZE));
- from.visitFn = changetype<(ref: usize) => void>(-1); // would error
- from.clear();
- to = changetype(memory.allocate(ManagedObject.SIZE));
- to.visitFn = changetype<(ref: usize) => void>(-1); // would error
- to.clear();
- iter = to;
+ fromSpace = changetype(memory.allocate(HEADER_SIZE));
+ fromSpace.hookFn = changetype<(ref: usize) => void>(-1); // would error
+ fromSpace.clear();
+ toSpace = changetype(memory.allocate(HEADER_SIZE));
+ toSpace.hookFn = changetype<(ref: usize) => void>(-1); // would error
+ toSpace.clear();
+ iter = toSpace;
state = State.IDLE;
if (TRACE) trace("gc~state = IDLE");
// fall-through
@@ -157,21 +149,21 @@ function step(): void {
}
case State.MARK: {
obj = iter.next;
- if (obj !== to) {
+ if (obj !== toSpace) {
if (TRACE) trace("gc~step/MARK iterate", 1, objToRef(obj));
iter = obj;
obj.color = !white;
- obj.visitFn(objToRef(obj));
+ obj.hookFn(objToRef(obj));
} else {
if (TRACE) trace("gc~step/MARK finish");
iterateRoots(__gc_mark);
obj = iter.next;
- if (obj === to) {
- let prevFrom = from;
- from = to;
- to = prevFrom;
+ if (obj === toSpace) {
+ let from = fromSpace;
+ fromSpace = toSpace;
+ toSpace = from;
white = !white;
- iter = prevFrom.next;
+ iter = from.next;
state = State.SWEEP;
if (TRACE) trace("gc~state = SWEEP");
}
@@ -180,13 +172,13 @@ function step(): void {
}
case State.SWEEP: {
obj = iter;
- if (obj !== to) {
+ if (obj !== toSpace) {
if (TRACE) trace("gc~step/SWEEP free", 1, objToRef(obj));
iter = obj.next;
- memory.free(changetype(obj));
+ if (changetype(obj) >= HEAP_BASE) memory.free(changetype(obj));
} else {
if (TRACE) trace("gc~step/SWEEP finish");
- to.clear();
+ toSpace.clear();
state = State.IDLE;
if (TRACE) trace("gc~state = IDLE");
}
@@ -196,26 +188,26 @@ function step(): void {
}
@inline function refToObj(ref: usize): ManagedObject {
- return changetype(ref - ManagedObject.SIZE);
+ return changetype(ref - HEADER_SIZE);
}
@inline function objToRef(obj: ManagedObject): usize {
- return changetype(obj) + ManagedObject.SIZE;
+ return changetype(obj) + HEADER_SIZE;
}
// Garbage collector interface
@global export function __gc_allocate(
size: usize,
- visitFn: (ref: usize) => void
+ markFn: (ref: usize) => void
): usize {
if (TRACE) trace("gc.allocate", 1, size);
- if (size > MAX_SIZE_32 - ManagedObject.SIZE) unreachable();
+ if (size > MAX_SIZE_32 - HEADER_SIZE) unreachable();
step(); // also makes sure it's initialized
- var obj = changetype(memory.allocate(ManagedObject.SIZE + size));
- obj.visitFn = visitFn;
+ var obj = changetype(memory.allocate(HEADER_SIZE + size));
+ obj.hookFn = markFn;
obj.color = white;
- from.push(obj);
+ fromSpace.push(obj);
return objToRef(obj);
}
diff --git a/std/assembly/diagnostics.ts b/std/assembly/diagnostics.ts
new file mode 100644
index 00000000..9e797ad1
--- /dev/null
+++ b/std/assembly/diagnostics.ts
@@ -0,0 +1,5 @@
+/* tslint:disable */
+
+@builtin export declare function ERROR(message?: void): void;
+@builtin export declare function WARNING(message?: void): void;
+@builtin export declare function INFO(message?: void): void;
diff --git a/std/assembly/gc.ts b/std/assembly/gc.ts
index fa1f827b..ceaf6d98 100644
--- a/std/assembly/gc.ts
+++ b/std/assembly/gc.ts
@@ -1,28 +1,12 @@
-@builtin export declare function iterateRoots(fn: (ref: usize) => void): void; // tslint:disable-line
+/* tslint:disable */
+
+@builtin export declare function iterateRoots(fn: (ref: usize) => void): void;
export namespace gc {
- export function allocate(size: usize, visitFn: (ref: usize) => void): usize {
- if (isDefined(__gc_allocate)) return __gc_allocate(size, visitFn); // tslint:disable-line
- WARNING("Calling 'gc.allocate' requires a garbage collector to be present.");
- return unreachable();
- }
-
export function collect(): void {
- if (isDefined(__gc_collect)) { __gc_collect(); return; } // tslint:disable-line
+ if (isDefined(__gc_collect)) { __gc_collect(); return; }
WARNING("Calling 'gc.collect' requires a garbage collector to be present.");
unreachable();
}
-
- export function link(parentRef: usize, childRef: usize): void {
- if (isDefined(__gc_link)) { __gc_link(parentRef, childRef); return; } // tslint:disable-line
- WARNING("Calling 'gc.link' requires a garbage collector to be present.");
- unreachable();
- }
-
- export function mark(ref: usize): void {
- if (isDefined(__gc_mark)) { __gc_mark(ref); return; } // tslint:disable-line
- WARNING("Calling 'gc.mark' requires a garbage collector to be present.");
- unreachable();
- }
}
diff --git a/std/assembly/index.d.ts b/std/assembly/index.d.ts
index 0516bde4..ab54cb4b 100644
--- a/std/assembly/index.d.ts
+++ b/std/assembly/index.d.ts
@@ -132,6 +132,8 @@ declare function isArray(value?: any): value is Array;
declare function isDefined(expression: any): bool;
/** Tests if the specified expression evaluates to a constant value. Compiles to a constant. */
declare function isConstant(expression: any): bool;
+/** Tests if the specified type *or* expression is of a managed type. Compiles to a constant. */
+declare function isManaged(value?: any): bool;
/** Traps if the specified value is not true-ish, otherwise returns the (non-nullable) value. */
declare function assert(isTrueish: T, message?: string): T & object; // any better way to model `: T != null`?
/** Parses an integer string to a 64-bit float. */
@@ -354,10 +356,6 @@ declare namespace gc {
export function allocate(size: usize, visitFn: (ref: usize) => void): usize;
/** Performs a full garbage collection cycle. */
export function collect(): void;
- /** Must be called when a managed object becomes a child of another one. */
- export function link(parentRef: usize, childRef: usize): void;
- /** Must be called when a managed object is found reachable. */
- export function mark(ref: usize): void;
}
/** Table operations. */
diff --git a/std/assembly/internal/allocator.ts b/std/assembly/internal/allocator.ts
index e478e6eb..04248ad9 100644
--- a/std/assembly/internal/allocator.ts
+++ b/std/assembly/internal/allocator.ts
@@ -1,11 +1,8 @@
/** Number of alignment bits. */
export const AL_BITS: u32 = 3;
-
/** Number of possible alignment values. */
export const AL_SIZE: usize = 1 << AL_BITS;
-
/** Mask to obtain just the alignment bits. */
export const AL_MASK: usize = AL_SIZE - 1;
-
/** Maximum 32-bit allocation size. */
export const MAX_SIZE_32: usize = 1 << 30; // 1GB
diff --git a/std/assembly/internal/array.ts b/std/assembly/internal/array.ts
index ef0a46a7..7c30f799 100644
--- a/std/assembly/internal/array.ts
+++ b/std/assembly/internal/array.ts
@@ -1,19 +1,12 @@
-import {
- loadUnsafe,
- storeUnsafe
-} from "./arraybuffer";
+import { loadUnsafe, storeUnsafe } from "./arraybuffer";
+import { Array } from "../array";
-import {
- Array
-} from "../array";
-
-/** Obtains the default comparator for the specified type. */
-@inline
export function defaultComparator(): (a: T, b: T) => i32 {
- return (a: T, b: T): i32 => ((a > b) - (a < b)); // compiles to a constant table index
+ return function compare(a: T, b: T): i32 {
+ return ((a > b) - (a < b));
+ };
}
-/** Sorts an Array with the 'Insertion Sort' algorithm. */
export function insertionSort(arr: Array, comparator: (a: T, b: T) => i32): Array {
var buffer = arr.buffer_;
for (let i: i32 = 0, length: i32 = arr.length; i < length; i++) {
@@ -30,7 +23,6 @@ export function insertionSort(arr: Array, comparator: (a: T, b: T) => i32)
return arr;
}
-/** Sorts an Array with the 'Weak Heap Sort' algorithm. */
export function weakHeapSort(arr: Array, comparator: (a: T, b: T) => i32): Array {
const shift32 = alignof();
diff --git a/std/assembly/internal/arraybuffer.ts b/std/assembly/internal/arraybuffer.ts
index 266e27fe..8484e8f6 100644
--- a/std/assembly/internal/arraybuffer.ts
+++ b/std/assembly/internal/arraybuffer.ts
@@ -2,12 +2,10 @@ import { AL_MASK, MAX_SIZE_32 } from "./allocator";
/** Size of an ArrayBuffer header. */
export const HEADER_SIZE: usize = (offsetof() + AL_MASK) & ~AL_MASK;
-
/** Maximum byte length of an ArrayBuffer. */
export const MAX_BLENGTH: i32 = MAX_SIZE_32 - HEADER_SIZE;
-/** Computes an ArrayBuffer's size in memory. */
-export function computeSize(byteLength: i32): usize {
+function computeSize(byteLength: i32): usize {
// round up to power of 2, with HEADER_SIZE=8:
// 0 -> 2^3 = 8
// 1..8 -> 2^4 = 16
@@ -17,16 +15,23 @@ export function computeSize(byteLength: i32): usize {
return 1 << (32 - clz(byteLength + HEADER_SIZE - 1));
}
-/** Allocates a raw ArrayBuffer. Contents remain uninitialized. */
-export function allocUnsafe(byteLength: i32): ArrayBuffer {
+// Low-level utility
+
+function __gc(ref: usize): void {}
+
+export function allocateUnsafe(byteLength: i32): ArrayBuffer {
assert(byteLength <= MAX_BLENGTH);
- var buffer = memory.allocate(computeSize(byteLength));
+ var buffer: usize;
+ if (isManaged()) {
+ buffer = __gc_allocate(computeSize(byteLength), __gc); // tslint:disable-line
+ } else {
+ buffer = memory.allocate(computeSize(byteLength));
+ }
store(buffer, byteLength, offsetof("byteLength"));
return changetype(buffer);
}
-/** Reallocates an ArrayBuffer, resizing it as requested. Tries to modify the buffer in place. */
-export function reallocUnsafe(buffer: ArrayBuffer, newByteLength: i32): ArrayBuffer {
+export function reallocateUnsafe(buffer: ArrayBuffer, newByteLength: i32): ArrayBuffer {
var oldByteLength = buffer.byteLength;
if (newByteLength > oldByteLength) {
assert(newByteLength <= MAX_BLENGTH);
@@ -38,7 +43,7 @@ export function reallocUnsafe(buffer: ArrayBuffer, newByteLength: i32): ArrayBuf
(newByteLength - oldByteLength)
);
} else { // slow path: copy to new buffer
- let newBuffer = allocUnsafe(newByteLength);
+ let newBuffer = allocateUnsafe(newByteLength);
memory.copy(
changetype(newBuffer) + HEADER_SIZE,
changetype(buffer) + HEADER_SIZE,
@@ -59,22 +64,18 @@ export function reallocUnsafe(buffer: ArrayBuffer, newByteLength: i32): ArrayBuf
return buffer;
}
-@inline
-export function loadUnsafe(buffer: ArrayBuffer, index: i32): V {
+@inline export function loadUnsafe(buffer: ArrayBuffer, index: i32): V {
return load(changetype(buffer) + (index << alignof()), HEADER_SIZE);
}
-@inline
-export function storeUnsafe(buffer: ArrayBuffer, index: i32, value: V): void {
+@inline export function storeUnsafe(buffer: ArrayBuffer, index: i32, value: V): void {
store(changetype(buffer) + (index << alignof()), value, HEADER_SIZE);
}
-@inline
-export function loadUnsafeWithOffset