diff --git a/cli/index.js b/cli/index.js index 36eeb4715e..5d92e648e8 100644 --- a/cli/index.js +++ b/cli/index.js @@ -295,7 +295,7 @@ export async function main(argv, options) { } // Set up options - let program, runtime; + let program, runtime, uncheckedBehavior; const compilerOptions = assemblyscript.newOptions(); switch (opts.runtime) { case "stub": runtime = 0; break; @@ -303,6 +303,12 @@ export async function main(argv, options) { /* incremental */ default: runtime = 2; break; } + switch (opts.uncheckedBehavior) { + /* default */ + default: uncheckedBehavior = 0; break; + case "never": uncheckedBehavior = 1; break; + case "always": uncheckedBehavior = 2; break; + } assemblyscript.setTarget(compilerOptions, 0); assemblyscript.setDebugInfo(compilerOptions, !!opts.debug); assemblyscript.setRuntime(compilerOptions, runtime); @@ -320,6 +326,7 @@ export async function main(argv, options) { assemblyscript.setMemoryBase(compilerOptions, opts.memoryBase >>> 0); assemblyscript.setTableBase(compilerOptions, opts.tableBase >>> 0); assemblyscript.setSourceMap(compilerOptions, opts.sourceMap != null); + assemblyscript.setUncheckedBehavior(compilerOptions, uncheckedBehavior); assemblyscript.setNoUnsafe(compilerOptions, opts.noUnsafe); assemblyscript.setPedantic(compilerOptions, opts.pedantic); assemblyscript.setLowMemoryLimit(compilerOptions, opts.lowMemoryLimit >>> 0); diff --git a/cli/options.json b/cli/options.json index 71b0dce24d..6590a92138 100644 --- a/cli/options.json +++ b/cli/options.json @@ -98,6 +98,22 @@ ], "type": "s" }, + "uncheckedBehavior": { + "category": "Debugging", + "description": [ + "Changes the behavior of unchecked() expressions.", + "Using this option can potentially cause breakage.", + "", + " default The default behavior: unchecked operations are", + " only used inside of unchecked().", + " never Unchecked operations are never used, even when", + " inside of unchecked().", + " always Unchecked operations are always used if possible,", + " whether or not unchecked() is used." + ], + "type": "s", + "default": "default" + }, "debug": { "category": "Debugging", "description": "Enables debug information in emitted binaries.", diff --git a/src/builtins.ts b/src/builtins.ts index 559dd6d8b4..8846825427 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -26,7 +26,8 @@ import { Compiler, Constraints, - RuntimeFeatures + RuntimeFeatures, + UncheckedBehavior } from "./compiler"; import { @@ -3587,8 +3588,10 @@ function builtin_unchecked(ctx: BuiltinContext): ExpressionRef { checkArgsRequired(ctx, 1) ) return module.unreachable(); let flow = compiler.currentFlow; + let ignoreUnchecked = compiler.options.uncheckedBehavior === UncheckedBehavior.Never; let alreadyUnchecked = flow.is(FlowFlags.UncheckedContext); - flow.set(FlowFlags.UncheckedContext); + if (ignoreUnchecked) assert(!alreadyUnchecked); + else flow.set(FlowFlags.UncheckedContext); // eliminate unnecessary tees by preferring contextualType(=void) let expr = compiler.compileExpression(ctx.operands[0], ctx.contextualType); if (!alreadyUnchecked) flow.unset(FlowFlags.UncheckedContext); diff --git a/src/compiler.ts b/src/compiler.ts index 2d55c91e12..b637192733 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -245,6 +245,8 @@ export class Options { exportTable: bool = false; /** If true, generates information necessary for source maps. */ sourceMap: bool = false; + /** Unchecked behavior. Defaults to only using unchecked operations inside unchecked(). */ + uncheckedBehavior: UncheckedBehavior = UncheckedBehavior.Default; /** If given, exports the start function instead of calling it implicitly. */ exportStart: string | null = null; /** Static memory start offset. */ @@ -315,6 +317,16 @@ export class Options { } } +/** Behaviors regarding unchecked operations. */ +export const enum UncheckedBehavior { + /** Only use unchecked operations inside unchecked(). */ + Default = 0, + /** Never use unchecked operations. */ + Never = 1, + /** Always use unchecked operations if possible. */ + Always = 2 +} + /** Various constraints in expression compilation. */ export const enum Constraints { None = 0, diff --git a/src/flow.ts b/src/flow.ts index 31909c258a..3f4838ab3d 100644 --- a/src/flow.ts +++ b/src/flow.ts @@ -78,6 +78,10 @@ import { CommonFlags } from "./common"; +import { + UncheckedBehavior +} from "./compiler"; + import { DiagnosticCode } from "./diagnostics"; @@ -203,6 +207,9 @@ export class Flow { if (targetFunction.is(CommonFlags.Constructor)) { flow.initThisFieldFlags(); } + if (targetFunction.program.options.uncheckedBehavior === UncheckedBehavior.Always) { + flow.set(FlowFlags.UncheckedContext); + } return flow; } @@ -215,6 +222,9 @@ export class Flow { if (inlineFunction.is(CommonFlags.Constructor)) { flow.initThisFieldFlags(); } + if (targetFunction.program.options.uncheckedBehavior === UncheckedBehavior.Always) { + flow.set(FlowFlags.UncheckedContext); + } return flow; } diff --git a/src/index-wasm.ts b/src/index-wasm.ts index 101070d5d9..a43202657d 100644 --- a/src/index-wasm.ts +++ b/src/index-wasm.ts @@ -22,7 +22,8 @@ import { import { Compiler, - Options + Options, + UncheckedBehavior } from "./compiler"; import { @@ -102,6 +103,11 @@ export function setSourceMap(options: Options, sourceMap: bool): void { options.sourceMap = sourceMap; } +/** Sets the `uncheckedBehavior` option. */ +export function setUncheckedBehavior(options: Options, uncheckedBehavior: UncheckedBehavior): void { + options.uncheckedBehavior = uncheckedBehavior; +} + /** Sets the `memoryBase` option. */ export function setMemoryBase(options: Options, memoryBase: u32): void { options.memoryBase = memoryBase;