Skip to content

Commit 53af0e0

Browse files
authored
feat: Automatically export runtime when necessary (AssemblyScript#2383)
1 parent 1983883 commit 53af0e0

14 files changed

+5225
-26
lines changed

cli/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ export async function main(argv, options) {
328328
opts.stackSize = assemblyscript.DEFAULT_STACK_SIZE;
329329
}
330330
assemblyscript.setStackSize(compilerOptions, opts.stackSize);
331+
assemblyscript.setBindingsHint(compilerOptions, opts.bindings && opts.bindings.length > 0);
331332

332333
// Instrument callback to perform GC
333334
// prepareResult = (original => {

src/bindings/js.ts

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -941,23 +941,6 @@ export class JSBuilder extends ExportsWalker {
941941
return moduleId;
942942
}
943943

944-
isPlainObject(clazz: Class): bool {
945-
// A plain object does not inherit and does not have a constructor or private properties
946-
if (clazz.base) return false;
947-
var members = clazz.members;
948-
if (members) {
949-
for (let _values = Map_values(members), i = 0, k = _values.length; i < k; ++i) {
950-
let member = _values[i];
951-
if (member.isAny(CommonFlags.PRIVATE | CommonFlags.PROTECTED)) return false;
952-
if (member.is(CommonFlags.CONSTRUCTOR)) {
953-
// a generated constructor is ok
954-
if (member.declaration.range != this.program.nativeRange) return false;
955-
}
956-
}
957-
}
958-
return true;
959-
}
960-
961944
/** Lifts a WebAssembly value to a JavaScript value. */
962945
makeLiftFromValue(name: string, type: Type, sb: string[] = this.sb): void {
963946
if (type.isInternalReference) {
@@ -996,7 +979,7 @@ export class JSBuilder extends ExportsWalker {
996979
}
997980
sb.push(", ");
998981
this.needsLiftTypedArray = true;
999-
} else if (this.isPlainObject(clazz)) {
982+
} else if (isPlainObject(clazz)) {
1000983
sb.push("__liftRecord");
1001984
sb.push(clazz.id.toString());
1002985
sb.push("(");
@@ -1076,7 +1059,7 @@ export class JSBuilder extends ExportsWalker {
10761059
sb.push(clazz.getArrayValueType().alignLog2.toString());
10771060
sb.push(", ");
10781061
this.needsLowerTypedArray = true;
1079-
} else if (this.isPlainObject(clazz)) {
1062+
} else if (isPlainObject(clazz)) {
10801063
sb.push("__lowerRecord");
10811064
sb.push(clazz.id.toString());
10821065
sb.push("(");
@@ -1237,7 +1220,7 @@ export class JSBuilder extends ExportsWalker {
12371220
}
12381221

12391222
makeLiftRecord(clazz: Class): string {
1240-
assert(this.isPlainObject(clazz));
1223+
assert(isPlainObject(clazz));
12411224
var sb = new Array<string>();
12421225
indent(sb, this.indentLevel);
12431226
sb.push("function __liftRecord");
@@ -1275,7 +1258,7 @@ export class JSBuilder extends ExportsWalker {
12751258
}
12761259

12771260
makeLowerRecord(clazz: Class): string {
1278-
assert(this.isPlainObject(clazz));
1261+
assert(isPlainObject(clazz));
12791262
var sb = new Array<string>();
12801263
indent(sb, this.indentLevel);
12811264
sb.push("function __lowerRecord");
@@ -1350,6 +1333,23 @@ function isPlainFunction(signature: Signature, mode: Mode): bool {
13501333
return true;
13511334
}
13521335

1336+
function isPlainObject(clazz: Class): bool {
1337+
// A plain object does not inherit and does not have a constructor or private properties
1338+
if (clazz.base) return false;
1339+
var members = clazz.members;
1340+
if (members) {
1341+
for (let _values = Map_values(members), i = 0, k = _values.length; i < k; ++i) {
1342+
let member = _values[i];
1343+
if (member.isAny(CommonFlags.PRIVATE | CommonFlags.PROTECTED)) return false;
1344+
if (member.is(CommonFlags.CONSTRUCTOR)) {
1345+
// a generated constructor is ok
1346+
if (member.declaration.range != member.program.nativeRange) return false;
1347+
}
1348+
}
1349+
}
1350+
return true;
1351+
}
1352+
13531353
function indentText(text: string, indentLevel: i32, sb: string[], butFirst: bool = false): void {
13541354
var lineStart = 0;
13551355
var length = text.length;
@@ -1367,3 +1367,56 @@ function indentText(text: string, indentLevel: i32, sb: string[], butFirst: bool
13671367
sb.push(text.substring(lineStart));
13681368
}
13691369
}
1370+
1371+
export function liftRequiresExportRuntime(type: Type): bool {
1372+
if (!type.isInternalReference) return false;
1373+
let clazz = type.classReference;
1374+
if (!clazz) {
1375+
// functions lift as internref using __pin
1376+
assert(type.signatureReference);
1377+
return true;
1378+
}
1379+
let program = clazz.program;
1380+
// flat collections lift via memory copy
1381+
if (
1382+
clazz.extends(program.arrayBufferInstance.prototype) ||
1383+
clazz.extends(program.stringInstance.prototype) ||
1384+
clazz.extends(program.arrayBufferViewInstance.prototype)
1385+
) {
1386+
return false;
1387+
}
1388+
// nested collections lift depending on element type
1389+
if (
1390+
clazz.extends(program.arrayPrototype) ||
1391+
clazz.extends(program.staticArrayPrototype)
1392+
) {
1393+
return liftRequiresExportRuntime(clazz.getArrayValueType());
1394+
}
1395+
// complex objects lift as internref using __pin. plain objects may or may not
1396+
// involve the runtime: assume that they do to avoid potentially costly checks
1397+
return true;
1398+
}
1399+
1400+
export function lowerRequiresExportRuntime(type: Type): bool {
1401+
if (!type.isInternalReference) return false;
1402+
let clazz = type.classReference;
1403+
if (!clazz) {
1404+
// lowers by reference
1405+
assert(type.signatureReference);
1406+
return false;
1407+
}
1408+
// lowers using __new
1409+
let program = clazz.program;
1410+
if (
1411+
clazz.extends(program.arrayBufferInstance.prototype) ||
1412+
clazz.extends(program.stringInstance.prototype) ||
1413+
clazz.extends(program.arrayBufferViewInstance.prototype) ||
1414+
clazz.extends(program.arrayPrototype) ||
1415+
clazz.extends(program.staticArrayPrototype)
1416+
) {
1417+
return true;
1418+
}
1419+
// complex objects lower via internref by reference,
1420+
// while plain objects lower using __new
1421+
return isPlainObject(clazz);
1422+
}

src/compiler.ts

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,11 @@ import {
211211
ShadowStackPass
212212
} from "./passes/shadowstack";
213213

214+
import {
215+
liftRequiresExportRuntime,
216+
lowerRequiresExportRuntime
217+
} from "./bindings/js";
218+
214219
/** Compiler options. */
215220
export class Options {
216221
constructor() { /* as internref */ }
@@ -275,6 +280,8 @@ export class Options {
275280
shrinkLevelHint: i32 = 0;
276281
/** Hinted basename. */
277282
basenameHint: string = "output";
283+
/** Hinted bindings generation. */
284+
bindingsHint: bool = false;
278285

279286
/** Tests if the target is WASM64 or, otherwise, WASM32. */
280287
get isWasm64(): bool {
@@ -410,6 +417,8 @@ export class Compiler extends DiagnosticEmitter {
410417
shadowStack!: ShadowStackPass;
411418
/** Whether the module has custom function exports. */
412419
hasCustomFunctionExports: bool = false;
420+
/** Whether the module would use the exported runtime to lift/lower. */
421+
desiresExportRuntime: bool = false;
413422

414423
/** Compiles a {@link Program} to a {@link Module} using the specified options. */
415424
static compile(program: Program): Module {
@@ -500,8 +509,8 @@ export class Compiler extends DiagnosticEmitter {
500509
}
501510
}
502511

503-
// compile and export runtime if requested
504-
if (this.options.exportRuntime) {
512+
// compile and export runtime if requested or necessary
513+
if (this.options.exportRuntime || (this.options.bindingsHint && this.desiresExportRuntime)) {
505514
for (let i = 0, k = runtimeFunctions.length; i < k; ++i) {
506515
let name = runtimeFunctions[i];
507516
let instance = program.requireFunction(name);
@@ -853,9 +862,27 @@ export class Compiler extends DiagnosticEmitter {
853862
if (!module.hasExport(exportName)) {
854863
module.addFunctionExport(functionInstance.internalName, exportName);
855864
this.hasCustomFunctionExports = true;
856-
if (signature.hasManagedOperands) {
865+
let hasManagedOperands = signature.hasManagedOperands;
866+
if (hasManagedOperands) {
857867
this.shadowStack.noteExport(exportName, signature.getManagedOperandIndices());
858868
}
869+
if (!this.desiresExportRuntime) {
870+
let thisType = signature.thisType;
871+
if (
872+
thisType && lowerRequiresExportRuntime(thisType) ||
873+
liftRequiresExportRuntime(signature.returnType)
874+
) {
875+
this.desiresExportRuntime = true;
876+
} else {
877+
let parameterTypes = signature.parameterTypes;
878+
for (let i = 0, k = parameterTypes.length; i < k; ++i) {
879+
if (lowerRequiresExportRuntime(parameterTypes[i])) {
880+
this.desiresExportRuntime = true;
881+
break;
882+
}
883+
}
884+
}
885+
}
859886
}
860887
return;
861888
}
@@ -877,6 +904,15 @@ export class Compiler extends DiagnosticEmitter {
877904
let exportName = prefix + name;
878905
if (!module.hasExport(exportName)) {
879906
module.addGlobalExport(element.internalName, exportName);
907+
if (!this.desiresExportRuntime) {
908+
let type = global.type;
909+
if (
910+
liftRequiresExportRuntime(type) ||
911+
!global.is(CommonFlags.CONST) && lowerRequiresExportRuntime(type)
912+
) {
913+
this.desiresExportRuntime = true;
914+
}
915+
}
880916
}
881917
return;
882918
}
@@ -1087,6 +1123,9 @@ export class Compiler extends DiagnosticEmitter {
10871123
!isDeclaredConstant
10881124
);
10891125
pendingElements.delete(global);
1126+
if (!this.desiresExportRuntime && lowerRequiresExportRuntime(type)) {
1127+
this.desiresExportRuntime = true;
1128+
}
10901129
return true;
10911130
}
10921131

@@ -1441,6 +1480,23 @@ export class Compiler extends DiagnosticEmitter {
14411480
signature.resultRefs
14421481
);
14431482
funcRef = module.getFunction(instance.internalName);
1483+
if (!this.desiresExportRuntime) {
1484+
let thisType = signature.thisType;
1485+
if (
1486+
thisType && liftRequiresExportRuntime(thisType) ||
1487+
lowerRequiresExportRuntime(signature.returnType)
1488+
) {
1489+
this.desiresExportRuntime = true;
1490+
} else {
1491+
let parameterTypes = signature.parameterTypes;
1492+
for (let i = 0, k = parameterTypes.length; i < k; ++i) {
1493+
if (liftRequiresExportRuntime(parameterTypes[i])) {
1494+
this.desiresExportRuntime = true;
1495+
break;
1496+
}
1497+
}
1498+
}
1499+
}
14441500

14451501
// abstract or interface function
14461502
} else if (instance.is(CommonFlags.ABSTRACT) || instance.parent.kind == ElementKind.INTERFACE) {

src/index-wasm.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,11 @@ export function setBasenameHint(options: Options, basename: string): void {
199199
options.basenameHint = basename;
200200
}
201201

202+
/** Gives the compiler a hint that bindings will be generated. */
203+
export function setBindingsHint(options: Options, bindings: bool): void {
204+
options.bindingsHint = bindings;
205+
}
206+
202207
/** Sets the `pedantic` option. */
203208
export function setPedantic(options: Options, pedantic: bool): void {
204209
options.pedantic = pedantic;

tests/compiler/bindings/esm.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"asc_flags": [
3-
"--exportRuntime",
43
"--exportStart _start",
54
"--bindings esm"
65
],
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/** Exported memory */
2+
export declare const memory: WebAssembly.Memory;
3+
/** bindings/noExportRuntime/isBasic */
4+
export declare const isBasic: {
5+
/** @type `i32` */
6+
get value(): number;
7+
set value(value: number);
8+
};
9+
/**
10+
* bindings/noExportRuntime/takesReturnsBasic
11+
* @param a `i32`
12+
* @returns `i32`
13+
*/
14+
export declare function takesReturnsBasic(a: number): number;
15+
/** bindings/noExportRuntime/isString */
16+
export declare const isString: {
17+
/** @type `~lib/string/String` */
18+
get value(): string
19+
};
20+
/**
21+
* bindings/noExportRuntime/returnsString
22+
* @returns `~lib/string/String`
23+
*/
24+
export declare function returnsString(): string;
25+
/** bindings/noExportRuntime/isBuffer */
26+
export declare const isBuffer: {
27+
/** @type `~lib/arraybuffer/ArrayBuffer` */
28+
get value(): ArrayBuffer
29+
};
30+
/**
31+
* bindings/noExportRuntime/returnsBuffer
32+
* @returns `~lib/arraybuffer/ArrayBuffer`
33+
*/
34+
export declare function returnsBuffer(): ArrayBuffer;
35+
/** bindings/noExportRuntime/isTypedArray */
36+
export declare const isTypedArray: {
37+
/** @type `~lib/typedarray/Int32Array` */
38+
get value(): Int32Array
39+
};
40+
/**
41+
* bindings/noExportRuntime/returnsTypedArray
42+
* @returns `~lib/typedarray/Int32Array`
43+
*/
44+
export declare function returnsTypedArray(): Int32Array;
45+
/** bindings/noExportRuntime/isArrayOfBasic */
46+
export declare const isArrayOfBasic: {
47+
/** @type `~lib/array/Array<i32>` */
48+
get value(): Array<number>
49+
};
50+
/**
51+
* bindings/noExportRuntime/returnsArrayOfBasic
52+
* @returns `~lib/array/Array<i32>`
53+
*/
54+
export declare function returnsArrayOfBasic(): Array<number>;
55+
/** bindings/noExportRuntime/isArrayOfArray */
56+
export declare const isArrayOfArray: {
57+
/** @type `~lib/array/Array<~lib/array/Array<i32>>` */
58+
get value(): Array<Array<number>>
59+
};
60+
/**
61+
* bindings/noExportRuntime/returnsArrayOfArray
62+
* @returns `~lib/array/Array<~lib/array/Array<i32>>`
63+
*/
64+
export declare function returnsArrayOfArray(): Array<Array<number>>;
65+
/**
66+
* bindings/noExportRuntime/takesNonPlainObject
67+
* @param obj `bindings/noExportRuntime/NonPlainObject`
68+
*/
69+
export declare function takesNonPlainObject(obj: __Internref6): void;
70+
/**
71+
* bindings/noExportRuntime/takesFunction
72+
* @param fn `() => void`
73+
*/
74+
export declare function takesFunction(fn: __Internref7): void;
75+
/** bindings/noExportRuntime/NonPlainObject */
76+
declare class __Internref6 extends Number {
77+
private __nominal6: symbol;
78+
}
79+
/** ~lib/function/Function<%28%29=>void> */
80+
declare class __Internref7 extends Number {
81+
private __nominal7: symbol;
82+
}

0 commit comments

Comments
 (0)