Skip to content

Commit 79f849a

Browse files
authored
Omit full collection support if the entire program is acyclic (#1071)
1 parent 395df77 commit 79f849a

File tree

62 files changed

+33077
-77610
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+33077
-77610
lines changed

cli/asc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ exports.main = function main(argv, options, callback) {
224224
assemblyscript.setMemoryBase(compilerOptions, args.memoryBase >>> 0);
225225
assemblyscript.setSourceMap(compilerOptions, args.sourceMap != null);
226226
assemblyscript.setNoUnsafe(compilerOptions, args.noUnsafe);
227+
assemblyscript.setPedantic(compilerOptions, args.pedantic);
227228

228229
// Initialize default aliases
229230
assemblyscript.setGlobalAlias(compilerOptions, "Math", "NativeMath");

cli/asc.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,11 @@
224224
"description": "Specifies the path to a custom transform to 'require'.",
225225
"type": "S"
226226
},
227+
"pedantic": {
228+
"description": "Make yourself sad for no good reason.",
229+
"type": "b",
230+
"default": false
231+
},
227232
"traceResolution": {
228233
"description": "Enables tracing of package resolution.",
229234
"type": "b",

src/builtins.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ import {
6363
Field,
6464
Global,
6565
DecoratorFlags,
66-
Element
66+
Element,
67+
Class
6768
} from "./program";
6869

6970
import {
@@ -4733,7 +4734,8 @@ export function compileVisitGlobals(compiler: Compiler): void {
47334734
var nativeSizeType = compiler.options.nativeSizeType;
47344735
var visitInstance = assert(compiler.program.visitInstance);
47354736

4736-
compiler.compileFunction(visitInstance);
4737+
// this function is @lazy: make sure it exists
4738+
compiler.compileFunction(visitInstance, true);
47374739

47384740
for (let element of compiler.program.elementsByName.values()) {
47394741
if (element.kind != ElementKind.GLOBAL) continue;
@@ -4794,6 +4796,9 @@ export function compileVisitMembers(compiler: Compiler): void {
47944796
var blocks = new Array<RelooperBlockRef>();
47954797
var relooper = Relooper.create(module);
47964798

4799+
// this function is @lazy: make sure it exists
4800+
compiler.compileFunction(visitInstance, true);
4801+
47974802
var outer = relooper.addBlockWithSwitch(
47984803
module.nop(),
47994804
module.load(nativeSizeSize, false,

src/compiler.ts

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,8 @@ export class Options {
210210
features: Feature = Feature.MUTABLE_GLOBALS;
211211
/** If true, disallows unsafe features in user code. */
212212
noUnsafe: bool = false;
213+
/** If true, enables pedantic diagnostics. */
214+
pedantic: bool = false;
213215

214216
/** Hinted optimize level. Not applied by the compiler itself. */
215217
optimizeLevelHint: i32 = 0;
@@ -236,6 +238,11 @@ export class Options {
236238
return this.target == Target.WASM64 ? NativeType.I64 : NativeType.I32;
237239
}
238240

241+
/** Gets if any optimizations will be performed. */
242+
get willOptimize(): bool {
243+
return this.optimizeLevelHint > 0 || this.shrinkLevelHint > 0;
244+
}
245+
239246
/** Tests if a specific feature is activated. */
240247
hasFeature(feature: Feature): bool {
241248
return (this.features & feature) != 0;
@@ -322,6 +329,8 @@ export class Compiler extends DiagnosticEmitter {
322329
skippedAutoreleases: Set<ExpressionRef> = new Set();
323330
/** Current inline functions stack. */
324331
inlineStack: Function[] = [];
332+
/** Lazily compiled library functions. */
333+
lazyLibraryFunctions: Set<Function> = new Set();
325334

326335
/** Compiles a {@link Program} to a {@link Module} using the specified options. */
327336
static compile(program: Program): Module {
@@ -387,7 +396,7 @@ export class Compiler extends DiagnosticEmitter {
387396
}
388397
}
389398

390-
// compile the start function if not empty or explicitly requested
399+
// compile the start function if not empty or if explicitly requested
391400
var startIsEmpty = !startFunctionBody.length;
392401
var explicitStart = options.explicitStart;
393402
if (!startIsEmpty || explicitStart) {
@@ -414,11 +423,39 @@ export class Compiler extends DiagnosticEmitter {
414423
else module.addFunctionExport(startFunctionInstance.internalName, ExportNames.start);
415424
}
416425

417-
// compile runtime features
418-
if (this.runtimeFeatures & RuntimeFeatures.visitGlobals) compileVisitGlobals(this);
419-
if (this.runtimeFeatures & RuntimeFeatures.visitMembers) compileVisitMembers(this);
426+
// check if the entire program is acyclic
427+
var cyclicClasses = program.findCyclicClasses();
428+
if (cyclicClasses.size) {
429+
if (options.pedantic) {
430+
for (let classInstance of cyclicClasses) {
431+
this.info(
432+
DiagnosticCode.Type_0_is_cyclic_Module_will_include_deferred_garbage_collection,
433+
classInstance.identifierNode.range, classInstance.internalName
434+
);
435+
}
436+
}
437+
} else {
438+
program.registerConstantInteger("__GC_ALL_ACYCLIC", Type.bool, i64_new(1, 0));
439+
}
440+
441+
// compile lazy library functions
442+
var lazyLibraryFunctions = this.lazyLibraryFunctions;
443+
do {
444+
let functionsToCompile = new Array<Function>();
445+
for (let instance of lazyLibraryFunctions) {
446+
functionsToCompile.push(instance);
447+
}
448+
lazyLibraryFunctions.clear();
449+
for (let i = 0, k = functionsToCompile.length; i < k; ++i) {
450+
this.compileFunction(unchecked(functionsToCompile[i]), true);
451+
}
452+
} while (lazyLibraryFunctions.size);
453+
454+
// finalize runtime features
420455
module.removeGlobal(BuiltinNames.rtti_base);
421456
if (this.runtimeFeatures & RuntimeFeatures.RTTI) compileRTTI(this);
457+
if (this.runtimeFeatures & RuntimeFeatures.visitGlobals) compileVisitGlobals(this);
458+
if (this.runtimeFeatures & RuntimeFeatures.visitMembers) compileVisitMembers(this);
422459

423460
// update the heap base pointer
424461
var memoryOffset = this.memoryOffset;
@@ -464,8 +501,24 @@ export class Compiler extends DiagnosticEmitter {
464501
module.setFunctionTable(1 + functionTable.length, Module.UNLIMITED_TABLE, functionTable, module.i32(1));
465502

466503
// import and/or export table if requested (default table is named '0' by Binaryen)
467-
if (options.importTable) module.addTableImport("0", "env", "table");
468-
if (options.exportTable) module.addTableExport("0", ExportNames.table);
504+
if (options.importTable) {
505+
module.addTableImport("0", "env", "table");
506+
if (options.pedantic && options.willOptimize) {
507+
this.warning(
508+
DiagnosticCode.Importing_the_table_disables_some_indirect_call_optimizations,
509+
null
510+
);
511+
}
512+
}
513+
if (options.exportTable) {
514+
module.addTableExport("0", ExportNames.table);
515+
if (options.pedantic && options.willOptimize) {
516+
this.warning(
517+
DiagnosticCode.Exporting_the_table_disables_some_indirect_call_optimizations,
518+
null
519+
);
520+
}
521+
}
469522

470523
// set up module exports
471524
for (let file of this.program.filesByName.values()) {
@@ -1101,11 +1154,15 @@ export class Compiler extends DiagnosticEmitter {
11011154
forceStdAlternative: bool = false
11021155
): bool {
11031156
if (instance.is(CommonFlags.COMPILED)) return true;
1104-
if (instance.hasDecorator(DecoratorFlags.BUILTIN)) {
1105-
if (!forceStdAlternative) return true;
1157+
if (!forceStdAlternative) {
1158+
if (instance.hasDecorator(DecoratorFlags.BUILTIN)) return true;
1159+
if (instance.hasDecorator(DecoratorFlags.LAZY)) {
1160+
this.lazyLibraryFunctions.add(instance);
1161+
return true;
1162+
}
11061163
}
11071164

1108-
var previousType = this.currentType; // remember to retain it if compiling a function lazily
1165+
var previousType = this.currentType;
11091166
instance.set(CommonFlags.COMPILED);
11101167

11111168
var module = this.module;

src/diagnosticMessages.generated.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ export enum DiagnosticCode {
3737
_0_must_be_a_power_of_two = 223,
3838
_0_is_not_a_valid_operator = 224,
3939
Expression_cannot_be_represented_by_a_type = 225,
40+
Type_0_is_cyclic_Module_will_include_deferred_garbage_collection = 900,
41+
Importing_the_table_disables_some_indirect_call_optimizations = 901,
42+
Exporting_the_table_disables_some_indirect_call_optimizations = 902,
4043
Unterminated_string_literal = 1002,
4144
Identifier_expected = 1003,
4245
_0_expected = 1005,
@@ -181,6 +184,9 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
181184
case 223: return "'{0}' must be a power of two.";
182185
case 224: return "'{0}' is not a valid operator.";
183186
case 225: return "Expression cannot be represented by a type.";
187+
case 900: return "Type '{0}' is cyclic. Module will include deferred garbage collection.";
188+
case 901: return "Importing the table disables some indirect call optimizations.";
189+
case 902: return "Exporting the table disables some indirect call optimizations.";
184190
case 1002: return "Unterminated string literal.";
185191
case 1003: return "Identifier expected.";
186192
case 1005: return "'{0}' expected.";

src/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
"'{0}' is not a valid operator.": 224,
3232
"Expression cannot be represented by a type.": 225,
3333

34+
"Type '{0}' is cyclic. Module will include deferred garbage collection.": 900,
35+
"Importing the table disables some indirect call optimizations.": 901,
36+
"Exporting the table disables some indirect call optimizations.": 902,
37+
3438
"Unterminated string literal.": 1002,
3539
"Identifier expected.": 1003,
3640
"'{0}' expected.": 1005,

src/diagnostics.ts

Lines changed: 17 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -103,33 +103,6 @@ export class DiagnosticMessage {
103103
return new DiagnosticMessage(code, category, message);
104104
}
105105

106-
/** Creates a new informatory diagnostic message. */
107-
static createInfo(
108-
code: DiagnosticCode,
109-
arg0: string | null = null,
110-
arg1: string | null = null
111-
): DiagnosticMessage {
112-
return DiagnosticMessage.create(code, DiagnosticCategory.INFO, arg0, arg1);
113-
}
114-
115-
/** Creates a new warning diagnostic message. */
116-
static createWarning(
117-
code: DiagnosticCode,
118-
arg0: string | null = null,
119-
arg1: string | null = null
120-
): DiagnosticMessage {
121-
return DiagnosticMessage.create(code, DiagnosticCategory.WARNING, arg0, arg1);
122-
}
123-
124-
/** Creates a new error diagnostic message. */
125-
static createError(
126-
code: DiagnosticCode,
127-
arg0: string | null = null,
128-
arg1: string | null = null
129-
): DiagnosticMessage {
130-
return DiagnosticMessage.create(code, DiagnosticCategory.ERROR, arg0, arg1);
131-
}
132-
133106
/** Adds a source range to this message. */
134107
withRange(range: Range): this {
135108
this.range = range;
@@ -273,7 +246,7 @@ export abstract class DiagnosticEmitter {
273246
emitDiagnostic(
274247
code: DiagnosticCode,
275248
category: DiagnosticCategory,
276-
range: Range,
249+
range: Range | null,
277250
relatedRange: Range | null,
278251
arg0: string | null = null,
279252
arg1: string | null = null,
@@ -282,17 +255,20 @@ export abstract class DiagnosticEmitter {
282255
// It is possible that the same diagnostic is emitted twice, for example
283256
// when compiling generics with different types or when recompiling a loop
284257
// because our initial assumptions didn't hold. Deduplicate these.
285-
var seen = this.seen;
286-
if (seen.has(range)) {
287-
let codes = seen.get(range)!;
288-
if (codes.has(code)) return;
289-
codes.add(code);
290-
} else {
291-
let codes = new Set<DiagnosticCode>();
292-
codes.add(code);
293-
seen.set(range, codes);
258+
if (range) {
259+
let seen = this.seen;
260+
if (seen.has(range)) {
261+
let codes = seen.get(range)!;
262+
if (codes.has(code)) return;
263+
codes.add(code);
264+
} else {
265+
let codes = new Set<DiagnosticCode>();
266+
codes.add(code);
267+
seen.set(range, codes);
268+
}
294269
}
295-
var message = DiagnosticMessage.create(code, category, arg0, arg1, arg2).withRange(range);
270+
var message = DiagnosticMessage.create(code, category, arg0, arg1, arg2);
271+
if (range) message = message.withRange(range);
296272
if (relatedRange) message.relatedRange = relatedRange;
297273
this.diagnostics.push(message);
298274
// console.log(formatDiagnosticMessage(message, true, true) + "\n"); // temporary
@@ -302,7 +278,7 @@ export abstract class DiagnosticEmitter {
302278
/** Emits an informatory diagnostic message. */
303279
info(
304280
code: DiagnosticCode,
305-
range: Range,
281+
range: Range | null,
306282
arg0: string | null = null,
307283
arg1: string | null = null,
308284
arg2: string | null = null
@@ -325,7 +301,7 @@ export abstract class DiagnosticEmitter {
325301
/** Emits a warning diagnostic message. */
326302
warning(
327303
code: DiagnosticCode,
328-
range: Range,
304+
range: Range | null,
329305
arg0: string | null = null,
330306
arg1: string | null = null,
331307
arg2: string | null = null
@@ -348,7 +324,7 @@ export abstract class DiagnosticEmitter {
348324
/** Emits an error diagnostic message. */
349325
error(
350326
code: DiagnosticCode,
351-
range: Range,
327+
range: Range | null,
352328
arg0: string | null = null,
353329
arg1: string | null = null,
354330
arg2: string | null = null

src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ export function setOptimizeLevelHints(options: Options, optimizeLevel: i32, shri
109109
options.shrinkLevelHint = shrinkLevel;
110110
}
111111

112+
/** Sets the `pedantic` option. */
113+
export function setPedantic(options: Options, pedantic: bool): void {
114+
options.pedantic = pedantic;
115+
}
116+
112117
// Program
113118

114119
/** Creates a new Program. */

src/program.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,7 +1068,7 @@ export class Program extends DiagnosticEmitter {
10681068
}
10691069

10701070
/** Registers a constant integer value within the global scope. */
1071-
private registerConstantInteger(name: string, type: Type, value: I64): void {
1071+
registerConstantInteger(name: string, type: Type, value: I64): void {
10721072
assert(type.is(TypeFlags.INTEGER)); // must be an integer type
10731073
var global = new Global(
10741074
name,
@@ -1797,6 +1797,9 @@ export class Program extends DiagnosticEmitter {
17971797
validDecorators |= DecoratorFlags.EXTERNAL;
17981798
} else {
17991799
validDecorators |= DecoratorFlags.INLINE;
1800+
if (declaration.range.source.isLibrary) {
1801+
validDecorators |= DecoratorFlags.LAZY;
1802+
}
18001803
}
18011804
if (!declaration.is(CommonFlags.INSTANCE)) {
18021805
if (parent.kind != ElementKind.CLASS_PROTOTYPE) {
@@ -1982,6 +1985,16 @@ export class Program extends DiagnosticEmitter {
19821985
// } while (current = current.base);
19831986
// return null;
19841987
// }
1988+
1989+
/** Finds all cyclic classes. */
1990+
findCyclicClasses(): Set<Class> {
1991+
var managedClasses = this.managedClasses;
1992+
var cyclics = new Set<Class>();
1993+
for (let instance of managedClasses.values()) {
1994+
if (!instance.isAcyclic) cyclics.add(instance);
1995+
}
1996+
return cyclics;
1997+
}
19851998
}
19861999

19872000
/** Indicates the specific kind of an {@link Element}. */
@@ -3220,8 +3233,13 @@ export class ClassPrototype extends DeclaredElement {
32203233
/** Tests if this prototype extends the specified. */
32213234
extends(basePtototype: ClassPrototype | null): bool {
32223235
var current: ClassPrototype | null = this;
3223-
do if (current === basePtototype) return true;
3224-
while (current = current.basePrototype);
3236+
var seen = new Set<ClassPrototype>();
3237+
do {
3238+
// cannot directly or indirectly extend itself
3239+
if (seen.has(current)) break;
3240+
seen.add(current);
3241+
if (current === basePtototype) return true;
3242+
} while (current = current.basePrototype);
32253243
return false;
32263244
}
32273245

std/assembly/rt/index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ declare function __visit(ref: usize, cookie: i32): void;
1111
declare function __visit_globals(cookie: u32): void;
1212
declare function __visit_members(ref: usize, cookie: u32): void;
1313
declare function __allocArray(length: i32, alignLog2: usize, id: u32, data?: usize): usize;
14-
declare const ASC_RTRACE: boolean;
14+
declare const ASC_RTRACE: bool;
15+
declare const __GC_ALL_ACYCLIC: bool;

0 commit comments

Comments
 (0)