Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/asc.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ exports.main = function main(argv, options, callback) {
assemblyscript.setMemoryBase(compilerOptions, args.memoryBase >>> 0);
assemblyscript.setSourceMap(compilerOptions, args.sourceMap != null);
assemblyscript.setNoUnsafe(compilerOptions, args.noUnsafe);
assemblyscript.setPedantic(compilerOptions, args.pedantic);

// Initialize default aliases
assemblyscript.setGlobalAlias(compilerOptions, "Math", "NativeMath");
Expand Down
5 changes: 5 additions & 0 deletions cli/asc.json
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@
"description": "Specifies the path to a custom transform to 'require'.",
"type": "S"
},
"pedantic": {
"description": "Make yourself sad for no good reason.",
"type": "b",
"default": false
},
"traceResolution": {
"description": "Enables tracing of package resolution.",
"type": "b",
Expand Down
9 changes: 7 additions & 2 deletions src/builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ import {
Field,
Global,
DecoratorFlags,
Element
Element,
Class
} from "./program";

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

compiler.compileFunction(visitInstance);
// this function is @lazy: make sure it exists
compiler.compileFunction(visitInstance, true);

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

// this function is @lazy: make sure it exists
compiler.compileFunction(visitInstance, true);

var outer = relooper.addBlockWithSwitch(
module.nop(),
module.load(nativeSizeSize, false,
Expand Down
75 changes: 66 additions & 9 deletions src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ export class Options {
features: Feature = Feature.MUTABLE_GLOBALS;
/** If true, disallows unsafe features in user code. */
noUnsafe: bool = false;
/** If true, enables pedantic diagnostics. */
pedantic: bool = false;

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

/** Gets if any optimizations will be performed. */
get willOptimize(): bool {
return this.optimizeLevelHint > 0 || this.shrinkLevelHint > 0;
}

/** Tests if a specific feature is activated. */
hasFeature(feature: Feature): bool {
return (this.features & feature) != 0;
Expand Down Expand Up @@ -322,6 +329,8 @@ export class Compiler extends DiagnosticEmitter {
skippedAutoreleases: Set<ExpressionRef> = new Set();
/** Current inline functions stack. */
inlineStack: Function[] = [];
/** Lazily compiled library functions. */
lazyLibraryFunctions: Set<Function> = new Set();

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

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

// compile runtime features
if (this.runtimeFeatures & RuntimeFeatures.visitGlobals) compileVisitGlobals(this);
if (this.runtimeFeatures & RuntimeFeatures.visitMembers) compileVisitMembers(this);
// check if the entire program is acyclic
var cyclicClasses = program.findCyclicClasses();
if (cyclicClasses.size) {
if (options.pedantic) {
for (let classInstance of cyclicClasses) {
this.info(
DiagnosticCode.Type_0_is_cyclic_Module_will_include_deferred_garbage_collection,
classInstance.identifierNode.range, classInstance.internalName
);
}
}
} else {
program.registerConstantInteger("__GC_ALL_ACYCLIC", Type.bool, i64_new(1, 0));
}

// compile lazy library functions
var lazyLibraryFunctions = this.lazyLibraryFunctions;
do {
let functionsToCompile = new Array<Function>();
for (let instance of lazyLibraryFunctions) {
functionsToCompile.push(instance);
}
lazyLibraryFunctions.clear();
for (let i = 0, k = functionsToCompile.length; i < k; ++i) {
this.compileFunction(unchecked(functionsToCompile[i]), true);
}
} while (lazyLibraryFunctions.size);

// finalize runtime features
module.removeGlobal(BuiltinNames.rtti_base);
if (this.runtimeFeatures & RuntimeFeatures.RTTI) compileRTTI(this);
if (this.runtimeFeatures & RuntimeFeatures.visitGlobals) compileVisitGlobals(this);
if (this.runtimeFeatures & RuntimeFeatures.visitMembers) compileVisitMembers(this);

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

// import and/or export table if requested (default table is named '0' by Binaryen)
if (options.importTable) module.addTableImport("0", "env", "table");
if (options.exportTable) module.addTableExport("0", ExportNames.table);
if (options.importTable) {
module.addTableImport("0", "env", "table");
if (options.pedantic && options.willOptimize) {
this.warning(
DiagnosticCode.Importing_the_table_disables_some_indirect_call_optimizations,
null
);
}
}
if (options.exportTable) {
module.addTableExport("0", ExportNames.table);
if (options.pedantic && options.willOptimize) {
this.warning(
DiagnosticCode.Exporting_the_table_disables_some_indirect_call_optimizations,
null
);
}
}

// set up module exports
for (let file of this.program.filesByName.values()) {
Expand Down Expand Up @@ -1101,11 +1154,15 @@ export class Compiler extends DiagnosticEmitter {
forceStdAlternative: bool = false
): bool {
if (instance.is(CommonFlags.COMPILED)) return true;
if (instance.hasDecorator(DecoratorFlags.BUILTIN)) {
if (!forceStdAlternative) return true;
if (!forceStdAlternative) {
if (instance.hasDecorator(DecoratorFlags.BUILTIN)) return true;
if (instance.hasDecorator(DecoratorFlags.LAZY)) {
this.lazyLibraryFunctions.add(instance);
return true;
}
}

var previousType = this.currentType; // remember to retain it if compiling a function lazily
var previousType = this.currentType;
instance.set(CommonFlags.COMPILED);

var module = this.module;
Expand Down
6 changes: 6 additions & 0 deletions src/diagnosticMessages.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export enum DiagnosticCode {
_0_must_be_a_power_of_two = 223,
_0_is_not_a_valid_operator = 224,
Expression_cannot_be_represented_by_a_type = 225,
Type_0_is_cyclic_Module_will_include_deferred_garbage_collection = 900,
Importing_the_table_disables_some_indirect_call_optimizations = 901,
Exporting_the_table_disables_some_indirect_call_optimizations = 902,
Unterminated_string_literal = 1002,
Identifier_expected = 1003,
_0_expected = 1005,
Expand Down Expand Up @@ -181,6 +184,9 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
case 223: return "'{0}' must be a power of two.";
case 224: return "'{0}' is not a valid operator.";
case 225: return "Expression cannot be represented by a type.";
case 900: return "Type '{0}' is cyclic. Module will include deferred garbage collection.";
case 901: return "Importing the table disables some indirect call optimizations.";
case 902: return "Exporting the table disables some indirect call optimizations.";
case 1002: return "Unterminated string literal.";
case 1003: return "Identifier expected.";
case 1005: return "'{0}' expected.";
Expand Down
4 changes: 4 additions & 0 deletions src/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
"'{0}' is not a valid operator.": 224,
"Expression cannot be represented by a type.": 225,

"Type '{0}' is cyclic. Module will include deferred garbage collection.": 900,
"Importing the table disables some indirect call optimizations.": 901,
"Exporting the table disables some indirect call optimizations.": 902,

"Unterminated string literal.": 1002,
"Identifier expected.": 1003,
"'{0}' expected.": 1005,
Expand Down
58 changes: 17 additions & 41 deletions src/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,33 +103,6 @@ export class DiagnosticMessage {
return new DiagnosticMessage(code, category, message);
}

/** Creates a new informatory diagnostic message. */
static createInfo(
code: DiagnosticCode,
arg0: string | null = null,
arg1: string | null = null
): DiagnosticMessage {
return DiagnosticMessage.create(code, DiagnosticCategory.INFO, arg0, arg1);
}

/** Creates a new warning diagnostic message. */
static createWarning(
code: DiagnosticCode,
arg0: string | null = null,
arg1: string | null = null
): DiagnosticMessage {
return DiagnosticMessage.create(code, DiagnosticCategory.WARNING, arg0, arg1);
}

/** Creates a new error diagnostic message. */
static createError(
code: DiagnosticCode,
arg0: string | null = null,
arg1: string | null = null
): DiagnosticMessage {
return DiagnosticMessage.create(code, DiagnosticCategory.ERROR, arg0, arg1);
}

/** Adds a source range to this message. */
withRange(range: Range): this {
this.range = range;
Expand Down Expand Up @@ -273,7 +246,7 @@ export abstract class DiagnosticEmitter {
emitDiagnostic(
code: DiagnosticCode,
category: DiagnosticCategory,
range: Range,
range: Range | null,
relatedRange: Range | null,
arg0: string | null = null,
arg1: string | null = null,
Expand All @@ -282,17 +255,20 @@ export abstract class DiagnosticEmitter {
// It is possible that the same diagnostic is emitted twice, for example
// when compiling generics with different types or when recompiling a loop
// because our initial assumptions didn't hold. Deduplicate these.
var seen = this.seen;
if (seen.has(range)) {
let codes = seen.get(range)!;
if (codes.has(code)) return;
codes.add(code);
} else {
let codes = new Set<DiagnosticCode>();
codes.add(code);
seen.set(range, codes);
if (range) {
let seen = this.seen;
if (seen.has(range)) {
let codes = seen.get(range)!;
if (codes.has(code)) return;
codes.add(code);
} else {
let codes = new Set<DiagnosticCode>();
codes.add(code);
seen.set(range, codes);
}
}
var message = DiagnosticMessage.create(code, category, arg0, arg1, arg2).withRange(range);
var message = DiagnosticMessage.create(code, category, arg0, arg1, arg2);
if (range) message = message.withRange(range);
if (relatedRange) message.relatedRange = relatedRange;
this.diagnostics.push(message);
// console.log(formatDiagnosticMessage(message, true, true) + "\n"); // temporary
Expand All @@ -302,7 +278,7 @@ export abstract class DiagnosticEmitter {
/** Emits an informatory diagnostic message. */
info(
code: DiagnosticCode,
range: Range,
range: Range | null,
arg0: string | null = null,
arg1: string | null = null,
arg2: string | null = null
Expand All @@ -325,7 +301,7 @@ export abstract class DiagnosticEmitter {
/** Emits a warning diagnostic message. */
warning(
code: DiagnosticCode,
range: Range,
range: Range | null,
arg0: string | null = null,
arg1: string | null = null,
arg2: string | null = null
Expand All @@ -348,7 +324,7 @@ export abstract class DiagnosticEmitter {
/** Emits an error diagnostic message. */
error(
code: DiagnosticCode,
range: Range,
range: Range | null,
arg0: string | null = null,
arg1: string | null = null,
arg2: string | null = null
Expand Down
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ export function setOptimizeLevelHints(options: Options, optimizeLevel: i32, shri
options.shrinkLevelHint = shrinkLevel;
}

/** Sets the `pedantic` option. */
export function setPedantic(options: Options, pedantic: bool): void {
options.pedantic = pedantic;
}

// Program

/** Creates a new Program. */
Expand Down
24 changes: 21 additions & 3 deletions src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1068,7 +1068,7 @@ export class Program extends DiagnosticEmitter {
}

/** Registers a constant integer value within the global scope. */
private registerConstantInteger(name: string, type: Type, value: I64): void {
registerConstantInteger(name: string, type: Type, value: I64): void {
assert(type.is(TypeFlags.INTEGER)); // must be an integer type
var global = new Global(
name,
Expand Down Expand Up @@ -1797,6 +1797,9 @@ export class Program extends DiagnosticEmitter {
validDecorators |= DecoratorFlags.EXTERNAL;
} else {
validDecorators |= DecoratorFlags.INLINE;
if (declaration.range.source.isLibrary) {
validDecorators |= DecoratorFlags.LAZY;
}
}
if (!declaration.is(CommonFlags.INSTANCE)) {
if (parent.kind != ElementKind.CLASS_PROTOTYPE) {
Expand Down Expand Up @@ -1982,6 +1985,16 @@ export class Program extends DiagnosticEmitter {
// } while (current = current.base);
// return null;
// }

/** Finds all cyclic classes. */
findCyclicClasses(): Set<Class> {
var managedClasses = this.managedClasses;
var cyclics = new Set<Class>();
for (let instance of managedClasses.values()) {
if (!instance.isAcyclic) cyclics.add(instance);
}
return cyclics;
}
}

/** Indicates the specific kind of an {@link Element}. */
Expand Down Expand Up @@ -3220,8 +3233,13 @@ export class ClassPrototype extends DeclaredElement {
/** Tests if this prototype extends the specified. */
extends(basePtototype: ClassPrototype | null): bool {
var current: ClassPrototype | null = this;
do if (current === basePtototype) return true;
while (current = current.basePrototype);
var seen = new Set<ClassPrototype>();
do {
// cannot directly or indirectly extend itself
if (seen.has(current)) break;
seen.add(current);
if (current === basePtototype) return true;
} while (current = current.basePrototype);
return false;
}

Expand Down
3 changes: 2 additions & 1 deletion std/assembly/rt/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ declare function __visit(ref: usize, cookie: i32): void;
declare function __visit_globals(cookie: u32): void;
declare function __visit_members(ref: usize, cookie: u32): void;
declare function __allocArray(length: i32, alignLog2: usize, id: u32, data?: usize): usize;
declare const ASC_RTRACE: boolean;
declare const ASC_RTRACE: bool;
declare const __GC_ALL_ACYCLIC: bool;
Loading