This simplifies the calling convention and consolidates the call frame stack and value stack into a single stack. As a result, the cost of function calls decreases because we now don't need to check the boundary twice (value and call frame stacks) at each function call. The following is the result of the benchmark for recursive Fibonacci function in integration_test/bench/testdata/case.go, and it shows that this actually improves the performance of function calls. [amd64] name old time/op new time/op delta Invocation/compiler/fib_for_5-32 109ns ± 3% 81ns ± 1% -25.86% (p=0.008 n=5+5) Invocation/compiler/fib_for_10-32 556ns ± 3% 473ns ± 3% -14.99% (p=0.008 n=5+5) Invocation/compiler/fib_for_20-32 61.4µs ± 2% 55.9µs ± 5% -8.98% (p=0.008 n=5+5) Invocation/compiler/fib_for_30-32 7.41ms ± 3% 6.83ms ± 3% -7.90% (p=0.008 n=5+5) [arm64] name old time/op new time/op delta Invocation/compiler/fib_for_5-10 67.7ns ± 1% 60.2ns ± 1% -11.12% (p=0.000 n=9+9) Invocation/compiler/fib_for_10-10 487ns ± 1% 460ns ± 0% -5.56% (p=0.000 n=10+9) Invocation/compiler/fib_for_20-10 58.0µs ± 1% 54.3µs ± 1% -6.38% (p=0.000 n=10+10) Invocation/compiler/fib_for_30-10 7.12ms ± 1% 6.67ms ± 1% -6.31% (p=0.000 n=10+9) Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
63 lines
2.3 KiB
Go
63 lines
2.3 KiB
Go
package compiler
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
|
"github.com/tetratelabs/wazero/internal/wazeroir"
|
|
)
|
|
|
|
// TestCompiler_conditional_value_saving ensure that saving conditional register works correctly even if there's
|
|
// no free registers available.
|
|
func TestCompiler_conditional_value_saving(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
compiler := env.requireNewCompiler(t, newCompiler, nil)
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
// Place the f32 local.
|
|
err = compiler.compileConstF32(&wazeroir.OperationConstF32{Value: 1.0})
|
|
require.NoError(t, err)
|
|
|
|
// Generate constants to occupy all the unreserved GP registers.
|
|
for i := 0; i < len(unreservedGeneralPurposeRegisters); i++ {
|
|
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: 100})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Pick the f32 floating point local (1.0) twice.
|
|
// Note that the f32 (function local variable in general) is placed above the call frame.
|
|
err = compiler.compilePick(&wazeroir.OperationPick{Depth: int(compiler.runtimeValueLocationStack().sp - 1 - callFrameDataSizeInUint64)})
|
|
|
|
require.NoError(t, err)
|
|
err = compiler.compilePick(&wazeroir.OperationPick{Depth: int(compiler.runtimeValueLocationStack().sp - 1 - callFrameDataSizeInUint64)})
|
|
|
|
require.NoError(t, err)
|
|
// Generate conditional flag via floating point comparisons.
|
|
err = compiler.compileLe(&wazeroir.OperationLe{Type: wazeroir.SignedTypeFloat32})
|
|
require.NoError(t, err)
|
|
|
|
// Ensures that we have conditional value at top of stack.
|
|
l := compiler.runtimeValueLocationStack().peek()
|
|
require.True(t, l.onConditionalRegister())
|
|
|
|
// Ensures that no free registers are available.
|
|
_, ok := compiler.runtimeValueLocationStack().takeFreeRegister(registerTypeGeneralPurpose)
|
|
require.False(t, ok)
|
|
|
|
// We should be able to use the conditional value (an i32 value in Wasm) as an operand for, say, i32.add.
|
|
err = compiler.compileAdd(&wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeI32})
|
|
require.NoError(t, err)
|
|
|
|
err = compiler.compileReturnFunction()
|
|
require.NoError(t, err)
|
|
|
|
// Generate and run the code under test.
|
|
code, _, err := compiler.compile()
|
|
require.NoError(t, err)
|
|
env.exec(code)
|
|
|
|
// expect 101 = 100(== the integer const) + 1 (== flag value == the result of (1.0 <= 1.0))
|
|
require.Equal(t, uint32(101), env.stackTopAsUint32())
|
|
}
|