github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/integration_test/bench/hostfunc_bench_test.go (about)

     1  package bench
     2  
     3  import (
     4  	"context"
     5  	_ "embed"
     6  	"encoding/binary"
     7  	"math"
     8  	"testing"
     9  
    10  	"github.com/tetratelabs/wazero"
    11  	"github.com/tetratelabs/wazero/api"
    12  	"github.com/tetratelabs/wazero/internal/platform"
    13  	"github.com/tetratelabs/wazero/internal/testing/binaryencoding"
    14  	"github.com/tetratelabs/wazero/internal/testing/require"
    15  	"github.com/tetratelabs/wazero/internal/wasm"
    16  )
    17  
    18  const (
    19  	// callGoHostName is the name of exported function which calls the
    20  	// Go-implemented host function.
    21  	callGoHostName = "call_go_host"
    22  	// callGoReflectHostName is the name of exported function which calls the
    23  	// Go-implemented host function defined in reflection.
    24  	callGoReflectHostName = "call_go_reflect_host"
    25  )
    26  
    27  // BenchmarkHostFunctionCall measures the cost of host function calls whose target functions are either
    28  // Go-implemented or Wasm-implemented, and compare the results between them.
    29  func BenchmarkHostFunctionCall(b *testing.B) {
    30  	if !platform.CompilerSupported() {
    31  		b.Skip()
    32  	}
    33  
    34  	m := setupHostCallBench(func(err error) {
    35  		if err != nil {
    36  			b.Fatal(err)
    37  		}
    38  	})
    39  
    40  	const offset = uint64(100)
    41  	const val = float32(1.1234)
    42  
    43  	binary.LittleEndian.PutUint32(m.MemoryInstance.Buffer[offset:], math.Float32bits(val))
    44  
    45  	for _, fn := range []string{callGoReflectHostName, callGoHostName} {
    46  		fn := fn
    47  
    48  		b.Run(fn, func(b *testing.B) {
    49  			ce := getCallEngine(m, fn)
    50  
    51  			b.ResetTimer()
    52  			for i := 0; i < b.N; i++ {
    53  				res, err := ce.Call(testCtx, offset)
    54  				if err != nil {
    55  					b.Fatal(err)
    56  				}
    57  				if uint32(res[0]) != math.Float32bits(val) {
    58  					b.Fail()
    59  				}
    60  			}
    61  		})
    62  
    63  		b.Run(fn+"_with_stack", func(b *testing.B) {
    64  			ce := getCallEngine(m, fn)
    65  
    66  			b.ResetTimer()
    67  			stack := make([]uint64, 1)
    68  			for i := 0; i < b.N; i++ {
    69  				stack[0] = offset
    70  				err := ce.CallWithStack(testCtx, stack)
    71  				if err != nil {
    72  					b.Fatal(err)
    73  				}
    74  				if uint32(stack[0]) != math.Float32bits(val) {
    75  					b.Fail()
    76  				}
    77  			}
    78  		})
    79  	}
    80  }
    81  
    82  func TestBenchmarkFunctionCall(t *testing.T) {
    83  	if !platform.CompilerSupported() {
    84  		t.Skip()
    85  	}
    86  
    87  	m := setupHostCallBench(func(err error) {
    88  		require.NoError(t, err)
    89  	})
    90  
    91  	callGoHost := getCallEngine(m, callGoHostName)
    92  	callGoReflectHost := getCallEngine(m, callGoReflectHostName)
    93  
    94  	require.NotNil(t, callGoHost)
    95  	require.NotNil(t, callGoReflectHost)
    96  
    97  	tests := []struct {
    98  		offset uint32
    99  		val    float32
   100  	}{
   101  		{offset: 0, val: math.Float32frombits(0xffffffff)},
   102  		{offset: 100, val: 1.12314},
   103  		{offset: wasm.MemoryPageSize - 4, val: 1.12314},
   104  	}
   105  
   106  	mem := m.MemoryInstance.Buffer
   107  
   108  	for _, f := range []struct {
   109  		name string
   110  		ce   api.Function
   111  	}{
   112  		{name: "go", ce: callGoHost},
   113  		{name: "go-reflect", ce: callGoReflectHost},
   114  	} {
   115  		f := f
   116  		t.Run(f.name, func(t *testing.T) {
   117  			for _, tc := range tests {
   118  				binary.LittleEndian.PutUint32(mem[tc.offset:], math.Float32bits(tc.val))
   119  				res, err := f.ce.Call(context.Background(), uint64(tc.offset))
   120  				require.NoError(t, err)
   121  				require.Equal(t, math.Float32bits(tc.val), uint32(res[0]))
   122  			}
   123  		})
   124  	}
   125  }
   126  
   127  func getCallEngine(m *wasm.ModuleInstance, name string) (ce api.Function) {
   128  	exp := m.Exports[name]
   129  	ce = m.Engine.NewFunction(exp.Index)
   130  	return
   131  }
   132  
   133  func setupHostCallBench(requireNoError func(error)) *wasm.ModuleInstance {
   134  	ctx := context.Background()
   135  	r := wazero.NewRuntime(ctx)
   136  
   137  	const i32, f32 = api.ValueTypeI32, api.ValueTypeF32
   138  	_, err := r.NewHostModuleBuilder("host").
   139  		NewFunctionBuilder().WithGoModuleFunction(api.GoModuleFunc(func(ctx context.Context, mod api.Module, stack []uint64) {
   140  		ret, ok := mod.Memory().ReadUint32Le(uint32(stack[0]))
   141  		if !ok {
   142  			panic("couldn't read memory")
   143  		}
   144  		stack[0] = uint64(ret)
   145  	}), []api.ValueType{i32}, []api.ValueType{f32}).Export("go").
   146  		NewFunctionBuilder().WithFunc(func(ctx context.Context, m api.Module, pos uint32) float32 {
   147  		ret, ok := m.Memory().ReadUint32Le(pos)
   148  		if !ok {
   149  			panic("couldn't read memory")
   150  		}
   151  		return math.Float32frombits(ret)
   152  	}).Export("go-reflect").Instantiate(ctx)
   153  	requireNoError(err)
   154  
   155  	// Build the importing module.
   156  	importingModuleBin := binaryencoding.EncodeModule(&wasm.Module{
   157  		TypeSection: []wasm.FunctionType{{
   158  			Params:  []wasm.ValueType{i32},
   159  			Results: []wasm.ValueType{f32},
   160  		}},
   161  		ImportSection: []wasm.Import{
   162  			// Placeholders for imports from hostModule.
   163  			{Type: wasm.ExternTypeFunc, Module: "host", Name: "go"},
   164  			{Type: wasm.ExternTypeFunc, Module: "host", Name: "go-reflect"},
   165  		},
   166  		FunctionSection: []wasm.Index{0, 0},
   167  		ExportSection: []wasm.Export{
   168  			{Name: callGoHostName, Type: wasm.ExternTypeFunc, Index: 2},
   169  			{Name: callGoReflectHostName, Type: wasm.ExternTypeFunc, Index: 3},
   170  		},
   171  		Exports: map[string]*wasm.Export{
   172  			callGoHostName:        {Name: callGoHostName, Type: wasm.ExternTypeFunc, Index: 2},
   173  			callGoReflectHostName: {Name: callGoReflectHostName, Type: wasm.ExternTypeFunc, Index: 3},
   174  		},
   175  		CodeSection: []wasm.Code{
   176  			{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Calling the index 0 = host.go.
   177  			{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 1, wasm.OpcodeEnd}}, // Calling the index 1 = host.go-reflect.
   178  		},
   179  		MemorySection: &wasm.Memory{Min: 1},
   180  	})
   181  
   182  	importing, err := r.Instantiate(ctx, importingModuleBin)
   183  	requireNoError(err)
   184  	return importing.(*wasm.ModuleInstance)
   185  }