From 478895f9ba0400537a24ae4b4746b029fc4f8a60 Mon Sep 17 00:00:00 2001 From: CountBleck Date: Tue, 22 Nov 2022 10:42:09 -0800 Subject: [PATCH 1/2] Add --uncheckedBehavior to customize the use of unchecked() contexts This new option allows users to use unchecked() as usual, ignore it entirely, and force unchecked contexts everywhere. Ignoring unchecked is useful for debugging, and, if you're certain you want to accept the risk, forcing unchecked could be beneficial for performance. --- cli/index.js | 9 ++++++++- cli/options.json | 16 ++++++++++++++++ src/builtins.ts | 8 +++++--- src/compiler.ts | 12 ++++++++++++ src/flow.ts | 10 ++++++++++ src/index-wasm.ts | 8 +++++++- 6 files changed, 58 insertions(+), 5 deletions(-) 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..e162cfc4f9 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,9 @@ function builtin_unchecked(ctx: BuiltinContext): ExpressionRef { checkArgsRequired(ctx, 1) ) return module.unreachable(); let flow = compiler.currentFlow; - let alreadyUnchecked = flow.is(FlowFlags.UncheckedContext); - flow.set(FlowFlags.UncheckedContext); + let ignoreUnchecked = compiler.options.uncheckedBehavior === UncheckedBehavior.Never; + let alreadyUnchecked = !ignoreUnchecked && flow.is(FlowFlags.UncheckedContext); + if (!ignoreUnchecked) 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; From 89664fa2f76ca3d8b8600cb7bf1bceab45814e81 Mon Sep 17 00:00:00 2001 From: CountBleck Date: Thu, 22 Dec 2022 15:21:04 -0800 Subject: [PATCH 2/2] Assert the context is checked in "--uncheckedBehavior never" If the context is unchecked, something very wrong must have happened. Co-authored-by: dcode --- src/builtins.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/builtins.ts b/src/builtins.ts index e162cfc4f9..8846825427 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -3589,8 +3589,9 @@ function builtin_unchecked(ctx: BuiltinContext): ExpressionRef { ) return module.unreachable(); let flow = compiler.currentFlow; let ignoreUnchecked = compiler.options.uncheckedBehavior === UncheckedBehavior.Never; - let alreadyUnchecked = !ignoreUnchecked && flow.is(FlowFlags.UncheckedContext); - if (!ignoreUnchecked) flow.set(FlowFlags.UncheckedContext); + let alreadyUnchecked = flow.is(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);