Files
wazero/internal/engine/compiler/compiler_conditional_save_test.go
Takeshi Yoneda 9ad8af121a compiler: simplify calling convention (#782)
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>
2022-09-06 13:29:56 +09:00

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())
}