diff --git a/src/builtins.ts b/src/builtins.ts index 956ab08a2f..ed14495482 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -191,6 +191,7 @@ export namespace BuiltinNames { export const assert = "~lib/builtins/assert"; export const call_indirect = "~lib/builtins/call_indirect"; export const unchecked = "~lib/builtins/unchecked"; + export const inlined = "~lib/builtins/inlined"; export const instantiate = "~lib/builtins/instantiate"; export const idof = "~lib/builtins/idof"; @@ -3611,6 +3612,24 @@ function builtin_unchecked(ctx: BuiltinFunctionContext): ExpressionRef { } builtinFunctions.set(BuiltinNames.unchecked, builtin_unchecked); +// inlined(expr: *) -> * +function builtin_inlined(ctx: BuiltinFunctionContext): ExpressionRef { + let compiler = ctx.compiler; + let module = compiler.module; + if ( + checkTypeAbsent(ctx) | + checkArgsRequired(ctx, 1) + ) return module.unreachable(); + let flow = compiler.currentFlow; + let alreadyInline = flow.is(FlowFlags.InlineContext); + if (!alreadyInline) flow.set(FlowFlags.InlineContext); + // eliminate unnecessary tees by preferring contextualType(=void) + let expr = compiler.compileExpression(ctx.operands[0], ctx.contextualType); + if (!alreadyInline) flow.unset(FlowFlags.InlineContext); + return expr; +} +builtinFunctions.set(BuiltinNames.inlined, builtin_inlined); + // call_indirect(index: u32, ...args: *[]) -> T function builtin_call_indirect(ctx: BuiltinFunctionContext): ExpressionRef { let compiler = ctx.compiler; diff --git a/src/compiler.ts b/src/compiler.ts index bf4e482b94..753972d768 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -6255,7 +6255,8 @@ export class Compiler extends DiagnosticEmitter { } // Inline if explicitly requested - if (instance.hasDecorator(DecoratorFlags.Inline) && (!instance.is(CommonFlags.Overridden) || reportNode.isAccessOnSuper)) { + let inlineRequested = instance.hasDecorator(DecoratorFlags.Inline) || this.currentFlow.is(FlowFlags.InlineContext); + if (inlineRequested && (!instance.is(CommonFlags.Overridden) || reportNode.isAccessOnSuper)) { assert(!instance.is(CommonFlags.Stub)); // doesn't make sense let inlineStack = this.inlineStack; if (inlineStack.includes(instance)) { diff --git a/src/flow.ts b/src/flow.ts index d481a61fc9..ddc7941916 100644 --- a/src/flow.ts +++ b/src/flow.ts @@ -146,6 +146,8 @@ export const enum FlowFlags { UncheckedContext = 1 << 15, /** This is a flow compiling a constructor parameter. */ CtorParamContext = 1 << 16, + /** This is a flow where all function calls are inlined if possible. */ + InlineContext = 1 << 17, // masks diff --git a/std/assembly/builtins.ts b/std/assembly/builtins.ts index 0f910a39e4..1bf6bee7b6 100644 --- a/std/assembly/builtins.ts +++ b/std/assembly/builtins.ts @@ -202,6 +202,10 @@ export declare function assert(isTrueish: T, message?: string): T; @unsafe @builtin export declare function unchecked(expr: T): T; +// @ts-ignore: decorator +@unsafe @builtin +export declare function inlined(expr: T): T; + // @ts-ignore: decorator @unsafe @builtin export declare function call_indirect(index: u32, ...args: auto[]): T; diff --git a/std/assembly/index.d.ts b/std/assembly/index.d.ts index eb8a177b31..a88d82b892 100644 --- a/std/assembly/index.d.ts +++ b/std/assembly/index.d.ts @@ -222,6 +222,8 @@ declare function idof(): u32; declare function changetype(value: any): T; /** Explicitly requests no bounds checks on the provided expression. Useful for array accesses. */ declare function unchecked(value: T): T; +/** Explicitly requests inlined function calls on the provided expression wherever possible. */ +declare function inlined(value: T): T; /** Emits a `call_indirect` instruction, calling the specified function in the function table by index with the specified arguments. Does result in a runtime error if the arguments do not match the called function. */ declare function call_indirect(index: u32, ...args: unknown[]): T; /** Instantiates a new instance of `T` using the specified constructor arguments. */ diff --git a/tests/compiler/inlining.debug.wat b/tests/compiler/inlining.debug.wat index 68a7fd21cd..4613c1a7a4 100644 --- a/tests/compiler/inlining.debug.wat +++ b/tests/compiler/inlining.debug.wat @@ -6,8 +6,10 @@ (type $4 (func (param i32 i32) (result i32))) (type $5 (func (result i32))) (type $6 (func (param i32 i32 i32))) - (type $7 (func (param i32 i32 i32 i32))) - (type $8 (func (param i32 i32 i64) (result i32))) + (type $7 (func (param i32 i32 i32) (result f64))) + (type $8 (func (param i32 i32 i32 i32))) + (type $9 (func (param i32 i32 i64) (result i32))) + (type $10 (func (param f64) (result f64))) (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) (global $inlining/constantGlobal i32 (i32.const 1)) (global $~argumentsLength (mut i32) (i32.const 0)) @@ -44,6 +46,8 @@ (table $0 2 2 funcref) (elem $0 (i32.const 1) $inlining/func_fe~anonymous|0) (export "test" (func $inlining/test)) + (export "foo" (func $inlining/foo)) + (export "bar" (func $inlining/bar)) (export "memory" (memory $0)) (start $~start) (func $inlining/test (result i32) @@ -2573,6 +2577,192 @@ global.set $~lib/rt/itcms/fromSpace call $inlining/test_ctor ) + (func $~lib/math/NativeMath.cbrt (param $x f64) (result f64) + (local $u i64) + (local $hx i32) + (local $t f64) + (local $r f64) + (local $s f64) + local.get $x + i64.reinterpret_f64 + local.set $u + local.get $u + i64.const 32 + i64.shr_u + i32.wrap_i64 + i32.const 2147483647 + i32.and + local.set $hx + local.get $hx + i32.const 2146435072 + i32.ge_u + if + local.get $x + local.get $x + f64.add + return + end + local.get $hx + i32.const 1048576 + i32.lt_u + if + local.get $x + f64.const 18014398509481984 + f64.mul + i64.reinterpret_f64 + local.set $u + local.get $u + i64.const 32 + i64.shr_u + i32.wrap_i64 + i32.const 2147483647 + i32.and + local.set $hx + local.get $hx + i32.const 0 + i32.eq + if + local.get $x + return + end + local.get $hx + i32.const 3 + i32.div_u + i32.const 696219795 + i32.add + local.set $hx + else + local.get $hx + i32.const 3 + i32.div_u + i32.const 715094163 + i32.add + local.set $hx + end + local.get $u + i64.const 1 + i64.const 63 + i64.shl + i64.and + local.set $u + local.get $u + local.get $hx + i64.extend_i32_u + i64.const 32 + i64.shl + i64.or + local.set $u + local.get $u + f64.reinterpret_i64 + local.set $t + local.get $t + local.get $t + f64.mul + local.get $t + local.get $x + f64.div + f64.mul + local.set $r + local.get $t + f64.const 1.87595182427177 + local.get $r + f64.const -1.8849797954337717 + local.get $r + f64.const 1.6214297201053545 + f64.mul + f64.add + f64.mul + f64.add + local.get $r + local.get $r + f64.mul + local.get $r + f64.mul + f64.const -0.758397934778766 + local.get $r + f64.const 0.14599619288661245 + f64.mul + f64.add + f64.mul + f64.add + f64.mul + local.set $t + local.get $t + i64.reinterpret_f64 + i64.const 2147483648 + i64.add + i64.const -1073741824 + i64.and + f64.reinterpret_i64 + local.set $t + local.get $t + local.get $t + f64.mul + local.set $s + local.get $x + local.get $s + f64.div + local.set $r + local.get $r + local.get $t + f64.sub + f64.const 2 + local.get $t + f64.mul + local.get $r + f64.add + f64.div + local.set $r + local.get $t + local.get $t + local.get $r + f64.mul + f64.add + local.set $t + local.get $t + return + ) + (func $inlining/foo (param $a i32) (param $b i32) (param $c i32) (result f64) + local.get $a + f64.convert_i32_s + local.get $b + f64.convert_i32_s + call $~lib/math/NativeMath.cbrt + f64.mul + local.get $c + f64.convert_i32_s + f64.add + return + ) + (func $inlining/bar (param $a i32) (param $b i32) (param $c i32) (result f64) + (local $a|3 i32) + (local $b|4 i32) + (local $c|5 i32) + block $inlining/foo|inlined.0 (result f64) + local.get $a + local.set $a|3 + local.get $b + local.set $b|4 + local.get $c + local.set $c|5 + local.get $a|3 + f64.convert_i32_s + local.get $b|4 + f64.convert_i32_s + call $~lib/math/NativeMath.cbrt + f64.mul + local.get $c|5 + f64.convert_i32_s + f64.add + br $inlining/foo|inlined.0 + end + local.get $b + local.get $a + local.get $c + call $inlining/foo + f64.div + return + ) (func $~lib/rt/__visit_globals (param $0 i32) (local $1 i32) i32.const 304 diff --git a/tests/compiler/inlining.release.wat b/tests/compiler/inlining.release.wat index d8ff242546..67e09d9b8e 100644 --- a/tests/compiler/inlining.release.wat +++ b/tests/compiler/inlining.release.wat @@ -5,8 +5,10 @@ (type $3 (func (param i32))) (type $4 (func (param i32 i32))) (type $5 (func (param i32 i32) (result i32))) - (type $6 (func (param i32 i32 i32 i32))) - (type $7 (func (param i32 i32 i64))) + (type $6 (func (param i32 i32 i32) (result f64))) + (type $7 (func (param i32 i32 i32 i32))) + (type $8 (func (param i32 i32 i64))) + (type $9 (func (param f64) (result f64))) (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) (global $~lib/rt/itcms/total (mut i32) (i32.const 0)) (global $~lib/rt/itcms/threshold (mut i32) (i32.const 0)) @@ -39,6 +41,8 @@ (table $0 2 2 funcref) (elem $0 (i32.const 1) $inlining/func_fe~anonymous|0) (export "test" (func $inlining/test)) + (export "foo" (func $inlining/foo)) + (export "bar" (func $inlining/bar)) (export "memory" (memory $0)) (start $~start) (func $inlining/test (result i32) @@ -130,7 +134,7 @@ local.get $0 global.set $~lib/rt/itcms/iter end - block $__inlined_func$~lib/rt/itcms/Object#unlink$124 + block $__inlined_func$~lib/rt/itcms/Object#unlink$125 local.get $1 i32.load offset=4 i32.const -4 @@ -154,7 +158,7 @@ call $~lib/builtins/abort unreachable end - br $__inlined_func$~lib/rt/itcms/Object#unlink$124 + br $__inlined_func$~lib/rt/itcms/Object#unlink$125 end local.get $1 i32.load offset=8 @@ -1752,6 +1756,162 @@ call $~lib/builtins/abort unreachable ) + (func $~lib/math/NativeMath.cbrt (param $0 f64) (result f64) + (local $1 i32) + (local $2 i64) + (local $3 f64) + (local $4 f64) + local.get $0 + i64.reinterpret_f64 + local.tee $2 + i64.const 32 + i64.shr_u + i32.wrap_i64 + i32.const 2147483647 + i32.and + local.tee $1 + i32.const 2146435072 + i32.ge_u + if + local.get $0 + local.get $0 + f64.add + return + end + local.get $1 + i32.const 1048576 + i32.lt_u + if (result i32) + local.get $0 + f64.const 18014398509481984 + f64.mul + i64.reinterpret_f64 + local.tee $2 + i64.const 32 + i64.shr_u + i32.wrap_i64 + i32.const 2147483647 + i32.and + local.tee $1 + i32.eqz + if + local.get $0 + return + end + local.get $1 + i32.const 3 + i32.div_u + i32.const 696219795 + i32.add + else + local.get $1 + i32.const 3 + i32.div_u + i32.const 715094163 + i32.add + end + local.set $1 + local.get $2 + i64.const -9223372036854775808 + i64.and + local.get $1 + i64.extend_i32_u + i64.const 32 + i64.shl + i64.or + f64.reinterpret_i64 + local.tee $4 + local.get $4 + f64.mul + local.get $4 + local.get $0 + f64.div + f64.mul + local.set $3 + local.get $0 + local.get $4 + local.get $3 + local.get $3 + f64.const 1.6214297201053545 + f64.mul + f64.const -1.8849797954337717 + f64.add + f64.mul + f64.const 1.87595182427177 + f64.add + local.get $3 + local.get $3 + f64.mul + local.get $3 + f64.mul + local.get $3 + f64.const 0.14599619288661245 + f64.mul + f64.const -0.758397934778766 + f64.add + f64.mul + f64.add + f64.mul + i64.reinterpret_f64 + i64.const 2147483648 + i64.add + i64.const -1073741824 + i64.and + f64.reinterpret_i64 + local.tee $0 + local.get $0 + f64.mul + f64.div + local.set $3 + local.get $0 + local.get $0 + local.get $3 + local.get $0 + f64.sub + local.get $0 + local.get $0 + f64.add + local.get $3 + f64.add + f64.div + f64.mul + f64.add + ) + (func $inlining/foo (param $0 i32) (param $1 i32) (param $2 i32) (result f64) + local.get $0 + f64.convert_i32_s + local.get $1 + f64.convert_i32_s + call $~lib/math/NativeMath.cbrt + f64.mul + local.get $2 + f64.convert_i32_s + f64.add + ) + (func $inlining/bar (param $0 i32) (param $1 i32) (param $2 i32) (result f64) + (local $3 f64) + (local $4 f64) + (local $5 f64) + local.get $0 + f64.convert_i32_s + local.tee $3 + local.get $1 + f64.convert_i32_s + local.tee $4 + call $~lib/math/NativeMath.cbrt + f64.mul + local.get $2 + f64.convert_i32_s + local.tee $5 + f64.add + local.get $4 + local.get $3 + call $~lib/math/NativeMath.cbrt + f64.mul + local.get $5 + f64.add + f64.div + ) (func $~lib/rt/__visit_members (param $0 i32) block $invalid block $inlining/Baz diff --git a/tests/compiler/inlining.ts b/tests/compiler/inlining.ts index 16515596d2..737fb72886 100644 --- a/tests/compiler/inlining.ts +++ b/tests/compiler/inlining.ts @@ -100,3 +100,11 @@ function test_ctor(): void { } test_ctor(); + +export function foo(a: i32, b: i32, c: i32): f64 { + return a * Math.cbrt(b) + c; +} + +export function bar(a: i32, b: i32, c: i32): f64 { + return inlined(foo(a, b, c)) / foo(b, a, c); +}