wazevo: adds support for file cache (#1743)
Signed-off-by: Takeshi Yoneda <t.y.mathetake@gmail.com>
This commit is contained in:
@@ -189,6 +189,7 @@ func TestSpectestV2(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestE2E(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
type callCase struct {
|
||||
funcName string // defaults to testcases.ExportedFunctionName
|
||||
params, expResults []uint64
|
||||
@@ -409,51 +410,63 @@ func TestE2E(t *testing.T) {
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
config := wazero.NewRuntimeConfigCompiler()
|
||||
|
||||
// Configure the new optimizing backend!
|
||||
wazevo.ConfigureWazevo(config)
|
||||
|
||||
ctx := context.Background()
|
||||
r := wazero.NewRuntimeWithConfig(ctx, config)
|
||||
defer func() {
|
||||
require.NoError(t, r.Close(ctx))
|
||||
}()
|
||||
|
||||
if tc.imported != nil {
|
||||
imported, err := r.CompileModule(ctx, binaryencoding.EncodeModule(tc.imported))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = r.InstantiateModule(ctx, imported, wazero.NewModuleConfig())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
compiled, err := r.CompileModule(ctx, binaryencoding.EncodeModule(tc.m))
|
||||
require.NoError(t, err)
|
||||
|
||||
inst, err := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig())
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, cc := range tc.calls {
|
||||
name := cc.funcName
|
||||
if name == "" {
|
||||
name = testcases.ExportedFunctionName
|
||||
for i := 0; i < 2; i++ {
|
||||
var name string
|
||||
if i == 0 {
|
||||
name = "no cache"
|
||||
} else {
|
||||
name = "with cache"
|
||||
}
|
||||
t.Run(fmt.Sprintf("call_%s%v", name, cc.params), func(t *testing.T) {
|
||||
f := inst.ExportedFunction(name)
|
||||
require.NotNil(t, f)
|
||||
result, err := f.Call(ctx, cc.params...)
|
||||
if cc.expErr != "" {
|
||||
require.Contains(t, err.Error(), cc.expErr)
|
||||
} else {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
cache, err := wazero.NewCompilationCacheWithDir(tmp)
|
||||
require.NoError(t, err)
|
||||
config := wazero.NewRuntimeConfigCompiler().WithCompilationCache(cache)
|
||||
|
||||
// Configure the new optimizing backend!
|
||||
wazevo.ConfigureWazevo(config)
|
||||
|
||||
ctx := context.Background()
|
||||
r := wazero.NewRuntimeWithConfig(ctx, config)
|
||||
defer func() {
|
||||
require.NoError(t, r.Close(ctx))
|
||||
}()
|
||||
|
||||
if tc.imported != nil {
|
||||
imported, err := r.CompileModule(ctx, binaryencoding.EncodeModule(tc.imported))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(cc.expResults), len(result))
|
||||
require.Equal(t, cc.expResults, result)
|
||||
for i := range cc.expResults {
|
||||
if cc.expResults[i] != result[i] {
|
||||
t.Errorf("result[%d]: exp %d, got %d", i, cc.expResults[i], result[i])
|
||||
}
|
||||
|
||||
_, err = r.InstantiateModule(ctx, imported, wazero.NewModuleConfig())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
compiled, err := r.CompileModule(ctx, binaryencoding.EncodeModule(tc.m))
|
||||
require.NoError(t, err)
|
||||
|
||||
inst, err := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig())
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, cc := range tc.calls {
|
||||
name := cc.funcName
|
||||
if name == "" {
|
||||
name = testcases.ExportedFunctionName
|
||||
}
|
||||
t.Run(fmt.Sprintf("call_%s%v", name, cc.params), func(t *testing.T) {
|
||||
f := inst.ExportedFunction(name)
|
||||
require.NotNil(t, f)
|
||||
result, err := f.Call(ctx, cc.params...)
|
||||
if cc.expErr != "" {
|
||||
require.Contains(t, err.Error(), cc.expErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(cc.expResults), len(result))
|
||||
require.Equal(t, cc.expResults, result)
|
||||
for i := range cc.expResults {
|
||||
if cc.expResults[i] != result[i] {
|
||||
t.Errorf("result[%d]: exp %d, got %d", i, cc.expResults[i], result[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -18,12 +18,15 @@ import (
|
||||
"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
|
||||
"github.com/tetratelabs/wazero/internal/filecache"
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
"github.com/tetratelabs/wazero/internal/version"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
type (
|
||||
// engine implements wasm.Engine.
|
||||
engine struct {
|
||||
wazeroVersion string
|
||||
fileCache filecache.Cache
|
||||
compiledModules map[wasm.ModuleID]*compiledModule
|
||||
// sortedCompiledModules is a list of compiled modules sorted by the initial address of the executable.
|
||||
sortedCompiledModules []*compiledModule
|
||||
@@ -89,21 +92,29 @@ type sourceMap struct {
|
||||
var _ wasm.Engine = (*engine)(nil)
|
||||
|
||||
// NewEngine returns the implementation of wasm.Engine.
|
||||
func NewEngine(ctx context.Context, _ api.CoreFeatures, _ filecache.Cache) wasm.Engine {
|
||||
func NewEngine(ctx context.Context, _ api.CoreFeatures, fc filecache.Cache) wasm.Engine {
|
||||
machine := newMachine()
|
||||
be := backend.NewCompiler(ctx, machine, ssa.NewBuilder())
|
||||
e := &engine{
|
||||
compiledModules: make(map[wasm.ModuleID]*compiledModule), refToBinaryOffset: make(map[ssa.FuncRef]int),
|
||||
setFinalizer: runtime.SetFinalizer,
|
||||
machine: machine,
|
||||
be: be,
|
||||
setFinalizer: runtime.SetFinalizer,
|
||||
machine: machine,
|
||||
be: be,
|
||||
fileCache: fc,
|
||||
wazeroVersion: version.GetWazeroVersion(),
|
||||
}
|
||||
e.compileSharedFunctions()
|
||||
return e
|
||||
}
|
||||
|
||||
// CompileModule implements wasm.Engine.
|
||||
func (e *engine) CompileModule(ctx context.Context, module *wasm.Module, listeners []experimental.FunctionListener, ensureTermination bool) error {
|
||||
func (e *engine) CompileModule(ctx context.Context, module *wasm.Module, listeners []experimental.FunctionListener, ensureTermination bool) (err error) {
|
||||
if _, ok, err := e.getCompiledModule(module, listeners, ensureTermination); ok { // cache hit!
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if wazevoapi.DeterministicCompilationVerifierEnabled {
|
||||
ctx = wazevoapi.NewDeterministicCompilationVerifierContext(ctx, len(module.CodeSection))
|
||||
}
|
||||
@@ -111,7 +122,9 @@ func (e *engine) CompileModule(ctx context.Context, module *wasm.Module, listene
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.addCompiledModule(module, cm)
|
||||
if err = e.addCompiledModule(module, cm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if wazevoapi.DeterministicCompilationVerifierEnabled {
|
||||
for i := 0; i < wazevoapi.DeterministicCompilationVerifyingIter; i++ {
|
||||
@@ -447,15 +460,6 @@ func (e *engine) DeleteCompiledModule(m *wasm.Module) {
|
||||
}
|
||||
}
|
||||
|
||||
func (e *engine) addCompiledModule(m *wasm.Module, cm *compiledModule) {
|
||||
e.mux.Lock()
|
||||
defer e.mux.Unlock()
|
||||
e.compiledModules[m.ID] = cm
|
||||
if len(cm.executable) > 0 {
|
||||
e.addCompiledModuleToSortedList(cm)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *engine) addCompiledModuleToSortedList(cm *compiledModule) {
|
||||
ptr := uintptr(unsafe.Pointer(&cm.executable[0]))
|
||||
|
||||
|
||||
223
internal/engine/wazevo/engine_cache.go
Normal file
223
internal/engine/wazevo/engine_cache.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package wazevo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
"github.com/tetratelabs/wazero/internal/u32"
|
||||
"github.com/tetratelabs/wazero/internal/u64"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
func (e *engine) addCompiledModule(module *wasm.Module, cm *compiledModule) (err error) {
|
||||
e.addCompiledModuleToMemory(module, cm)
|
||||
if !module.IsHostModule && e.fileCache != nil {
|
||||
err = e.addCompiledModuleToCache(module, cm)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (e *engine) getCompiledModule(module *wasm.Module, listeners []experimental.FunctionListener, ensureTermination bool) (cm *compiledModule, ok bool, err error) {
|
||||
cm, ok = e.getCompiledModuleFromMemory(module)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
cm, ok, err = e.getCompiledModuleFromCache(module)
|
||||
if ok {
|
||||
cm.parent = e
|
||||
cm.module = module
|
||||
cm.sharedFunctions = e.sharedFunctions
|
||||
cm.ensureTermination = ensureTermination
|
||||
cm.offsets = wazevoapi.NewModuleContextOffsetData(module, len(listeners) > 0)
|
||||
if len(listeners) > 0 {
|
||||
cm.listeners = listeners
|
||||
cm.listenerBeforeTrampolines = make([]*byte, len(module.TypeSection))
|
||||
cm.listenerAfterTrampolines = make([]*byte, len(module.TypeSection))
|
||||
for i := range module.TypeSection {
|
||||
typ := &module.TypeSection[i]
|
||||
before, after := e.getListenerTrampolineForType(typ)
|
||||
cm.listenerBeforeTrampolines[i] = before
|
||||
cm.listenerAfterTrampolines[i] = after
|
||||
}
|
||||
}
|
||||
e.addCompiledModuleToMemory(module, cm)
|
||||
cm.entryPreambles = make([]*byte, len(module.TypeSection))
|
||||
for i := range cm.entryPreambles {
|
||||
cm.entryPreambles[i] = e.getEntryPreambleForType(&module.TypeSection[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (e *engine) addCompiledModuleToMemory(m *wasm.Module, cm *compiledModule) {
|
||||
e.mux.Lock()
|
||||
defer e.mux.Unlock()
|
||||
e.compiledModules[m.ID] = cm
|
||||
if len(cm.executable) > 0 {
|
||||
e.addCompiledModuleToSortedList(cm)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *engine) getCompiledModuleFromMemory(module *wasm.Module) (cm *compiledModule, ok bool) {
|
||||
e.mux.RLock()
|
||||
defer e.mux.RUnlock()
|
||||
cm, ok = e.compiledModules[module.ID]
|
||||
return
|
||||
}
|
||||
|
||||
func (e *engine) addCompiledModuleToCache(module *wasm.Module, cm *compiledModule) (err error) {
|
||||
if e.fileCache == nil || module.IsHostModule {
|
||||
return
|
||||
}
|
||||
err = e.fileCache.Add(module.ID, serializeCompiledModule(e.wazeroVersion, cm))
|
||||
return
|
||||
}
|
||||
|
||||
func (e *engine) getCompiledModuleFromCache(module *wasm.Module) (cm *compiledModule, hit bool, err error) {
|
||||
if e.fileCache == nil || module.IsHostModule {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the entries exist in the external cache.
|
||||
var cached io.ReadCloser
|
||||
cached, hit, err = e.fileCache.Get(module.ID)
|
||||
if !hit || err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, we hit the cache on external cache.
|
||||
// We retrieve *code structures from `cached`.
|
||||
var staleCache bool
|
||||
// Note: cached.Close is ensured to be called in deserializeCodes.
|
||||
cm, staleCache, err = deserializeCompiledModule(e.wazeroVersion, cached)
|
||||
if err != nil {
|
||||
hit = false
|
||||
return
|
||||
} else if staleCache {
|
||||
return nil, false, e.fileCache.Delete(module.ID)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const magic = "WAZEVO"
|
||||
|
||||
func serializeCompiledModule(wazeroVersion string, cm *compiledModule) io.Reader {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
// First 6 byte: WAZEVO header.
|
||||
buf.WriteString(magic)
|
||||
// Next 1 byte: length of version:
|
||||
buf.WriteByte(byte(len(wazeroVersion)))
|
||||
// Version of wazero.
|
||||
buf.WriteString(wazeroVersion)
|
||||
// Number of *code (== locally defined functions in the module): 4 bytes.
|
||||
buf.Write(u32.LeBytes(uint32(len(cm.functionOffsets))))
|
||||
for _, offset := range cm.functionOffsets {
|
||||
// The offset of this function in the executable (8 bytes).
|
||||
buf.Write(u64.LeBytes(uint64(offset)))
|
||||
}
|
||||
// The length of code segment (8 bytes).
|
||||
buf.Write(u64.LeBytes(uint64(len(cm.executable))))
|
||||
// Append the native code.
|
||||
buf.Write(cm.executable)
|
||||
return bytes.NewReader(buf.Bytes())
|
||||
}
|
||||
|
||||
func deserializeCompiledModule(wazeroVersion string, reader io.ReadCloser) (cm *compiledModule, staleCache bool, err error) {
|
||||
defer reader.Close()
|
||||
cacheHeaderSize := len(magic) + 1 /* version size */ + len(wazeroVersion) + 4 /* number of functions */
|
||||
|
||||
// Read the header before the native code.
|
||||
header := make([]byte, cacheHeaderSize)
|
||||
n, err := reader.Read(header)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("compilationcache: error reading header: %v", err)
|
||||
}
|
||||
|
||||
if n != cacheHeaderSize {
|
||||
return nil, false, fmt.Errorf("compilationcache: invalid header length: %d", n)
|
||||
}
|
||||
|
||||
for i := 0; i < len(magic); i++ {
|
||||
if magic[i] != header[i] {
|
||||
return nil, false, fmt.Errorf(
|
||||
"compilationcache: invalid magic number: got %s but want %s", magic, header[:len(magic)])
|
||||
}
|
||||
}
|
||||
|
||||
// Check the version compatibility.
|
||||
versionSize := int(header[len(magic)])
|
||||
|
||||
cachedVersionBegin, cachedVersionEnd := len(magic)+1, len(magic)+1+versionSize
|
||||
if cachedVersionEnd >= len(header) {
|
||||
staleCache = true
|
||||
return
|
||||
} else if cachedVersion := string(header[cachedVersionBegin:cachedVersionEnd]); cachedVersion != wazeroVersion {
|
||||
staleCache = true
|
||||
return
|
||||
}
|
||||
|
||||
functionsNum := binary.LittleEndian.Uint32(header[len(header)-4:])
|
||||
cm = &compiledModule{functionOffsets: make([]int, functionsNum)}
|
||||
|
||||
var eightBytes [8]byte
|
||||
for i := uint32(0); i < functionsNum; i++ {
|
||||
// Read the offset of each function in the executable.
|
||||
var offset uint64
|
||||
if offset, err = readUint64(reader, &eightBytes); err != nil {
|
||||
err = fmt.Errorf("compilationcache: error reading func[%d] executable offset: %v", i, err)
|
||||
return
|
||||
}
|
||||
cm.functionOffsets[i] = int(offset)
|
||||
}
|
||||
|
||||
executableLen, err := readUint64(reader, &eightBytes)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("compilationcache: error reading executable size: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if executableLen > 0 {
|
||||
executable, err := platform.MmapCodeSegment(int(executableLen))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("compilationcache: error mmapping executable (len=%d): %v", executableLen, err)
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
_, err = io.ReadFull(reader, executable)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("compilationcache: error reading executable (len=%d): %v", executableLen, err)
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if runtime.GOARCH == "arm64" {
|
||||
// On arm64, we cannot give all of rwx at the same time, so we change it to exec.
|
||||
if err = platform.MprotectRX(executable); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
cm.executable = executable
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// readUint64 strictly reads an uint64 in little-endian byte order, using the
|
||||
// given array as a buffer. This returns io.EOF if less than 8 bytes were read.
|
||||
func readUint64(reader io.Reader, b *[8]byte) (uint64, error) {
|
||||
s := b[0:8]
|
||||
n, err := reader.Read(s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if n < 8 { // more strict than reader.Read
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
// Read the u64 from the underlying buffer.
|
||||
ret := binary.LittleEndian.Uint64(s)
|
||||
return ret, nil
|
||||
}
|
||||
208
internal/engine/wazevo/engine_cache_test.go
Normal file
208
internal/engine/wazevo/engine_cache_test.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package wazevo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
"github.com/tetratelabs/wazero/internal/u32"
|
||||
"github.com/tetratelabs/wazero/internal/u64"
|
||||
)
|
||||
|
||||
var testVersion = "0.0.1"
|
||||
|
||||
func TestSerializeCompiledModule(t *testing.T) {
|
||||
tests := []struct {
|
||||
in *compiledModule
|
||||
exp []byte
|
||||
}{
|
||||
{
|
||||
in: &compiledModule{
|
||||
executable: []byte{1, 2, 3, 4, 5},
|
||||
functionOffsets: []int{0},
|
||||
},
|
||||
exp: concat(
|
||||
[]byte(magic),
|
||||
[]byte{byte(len(testVersion))},
|
||||
[]byte(testVersion),
|
||||
u32.LeBytes(1), // number of functions.
|
||||
u64.LeBytes(0), // offset.
|
||||
u64.LeBytes(5), // length of code.
|
||||
[]byte{1, 2, 3, 4, 5}, // code.
|
||||
),
|
||||
},
|
||||
{
|
||||
in: &compiledModule{
|
||||
executable: []byte{1, 2, 3, 4, 5},
|
||||
functionOffsets: []int{0},
|
||||
},
|
||||
exp: concat(
|
||||
[]byte(magic),
|
||||
[]byte{byte(len(testVersion))},
|
||||
[]byte(testVersion),
|
||||
u32.LeBytes(1), // number of functions.
|
||||
u64.LeBytes(0), // offset.
|
||||
u64.LeBytes(5), // length of code.
|
||||
[]byte{1, 2, 3, 4, 5}, // code.
|
||||
),
|
||||
},
|
||||
{
|
||||
in: &compiledModule{
|
||||
executable: []byte{1, 2, 3, 4, 5, 1, 2, 3},
|
||||
functionOffsets: []int{0, 5},
|
||||
},
|
||||
exp: concat(
|
||||
[]byte(magic),
|
||||
[]byte{byte(len(testVersion))},
|
||||
[]byte(testVersion),
|
||||
u32.LeBytes(2), // number of functions.
|
||||
// Function index = 0.
|
||||
u64.LeBytes(0), // offset.
|
||||
// Function index = 1.
|
||||
u64.LeBytes(5), // offset.
|
||||
// Executable.
|
||||
u64.LeBytes(8), // length of code.
|
||||
[]byte{1, 2, 3, 4, 5, 1, 2, 3}, // code.
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range tests {
|
||||
actual, err := io.ReadAll(serializeCompiledModule(testVersion, tc.in))
|
||||
require.NoError(t, err, i)
|
||||
require.Equal(t, tc.exp, actual, i)
|
||||
}
|
||||
}
|
||||
|
||||
func concat(ins ...[]byte) (ret []byte) {
|
||||
for _, in := range ins {
|
||||
ret = append(ret, in...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func TestDeserializeCompiledModule(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in []byte
|
||||
importedFunctionCount uint32
|
||||
expCompiledModule *compiledModule
|
||||
expStaleCache bool
|
||||
expErr string
|
||||
}{
|
||||
{
|
||||
name: "invalid header",
|
||||
in: []byte{1},
|
||||
expErr: "compilationcache: invalid header length: 1",
|
||||
},
|
||||
{
|
||||
name: "version mismatch",
|
||||
in: concat(
|
||||
[]byte(magic),
|
||||
[]byte{byte(len("1233123.1.1"))},
|
||||
[]byte("1233123.1.1"),
|
||||
u32.LeBytes(1), // number of functions.
|
||||
),
|
||||
expStaleCache: true,
|
||||
},
|
||||
{
|
||||
name: "version mismatch",
|
||||
in: concat(
|
||||
[]byte(magic),
|
||||
[]byte{byte(len("0.0.0"))},
|
||||
[]byte("0.0.0"),
|
||||
u32.LeBytes(1), // number of functions.
|
||||
),
|
||||
expStaleCache: true,
|
||||
},
|
||||
{
|
||||
name: "one function",
|
||||
in: concat(
|
||||
[]byte(magic),
|
||||
[]byte{byte(len(testVersion))},
|
||||
[]byte(testVersion),
|
||||
u32.LeBytes(1), // number of functions.
|
||||
u64.LeBytes(0), // offset.
|
||||
// Executable.
|
||||
u64.LeBytes(5), // size.
|
||||
[]byte{1, 2, 3, 4, 5}, // machine code.
|
||||
),
|
||||
expCompiledModule: &compiledModule{
|
||||
executable: []byte{1, 2, 3, 4, 5},
|
||||
functionOffsets: []int{0},
|
||||
},
|
||||
expStaleCache: false,
|
||||
expErr: "",
|
||||
},
|
||||
{
|
||||
name: "two functions",
|
||||
in: concat(
|
||||
[]byte(magic),
|
||||
[]byte{byte(len(testVersion))},
|
||||
[]byte(testVersion),
|
||||
u32.LeBytes(2), // number of functions.
|
||||
// Function index = 0.
|
||||
u64.LeBytes(0), // offset.
|
||||
// Function index = 1.
|
||||
u64.LeBytes(7), // offset.
|
||||
// Executable.
|
||||
u64.LeBytes(10), // size.
|
||||
[]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, // machine code.
|
||||
),
|
||||
importedFunctionCount: 1,
|
||||
expCompiledModule: &compiledModule{
|
||||
executable: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
|
||||
functionOffsets: []int{0, 7},
|
||||
},
|
||||
expStaleCache: false,
|
||||
expErr: "",
|
||||
},
|
||||
{
|
||||
name: "reading executable offset",
|
||||
in: concat(
|
||||
[]byte(magic),
|
||||
[]byte{byte(len(testVersion))},
|
||||
[]byte(testVersion),
|
||||
u32.LeBytes(2), // number of functions.
|
||||
// Function index = 0.
|
||||
u64.LeBytes(5), // offset.
|
||||
// Function index = 1.
|
||||
),
|
||||
expErr: "compilationcache: error reading func[1] executable offset: EOF",
|
||||
},
|
||||
{
|
||||
name: "mmapping",
|
||||
in: concat(
|
||||
[]byte(magic),
|
||||
[]byte{byte(len(testVersion))},
|
||||
[]byte(testVersion),
|
||||
u32.LeBytes(2), // number of functions.
|
||||
// Function index = 0.
|
||||
u64.LeBytes(0), // offset.
|
||||
// Function index = 1.
|
||||
u64.LeBytes(5), // offset.
|
||||
// Executable.
|
||||
u64.LeBytes(5), // size of the executable.
|
||||
// Lack of machine code here.
|
||||
),
|
||||
expErr: "compilationcache: error reading executable (len=5): EOF",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cm, staleCache, err := deserializeCompiledModule(testVersion, io.NopCloser(bytes.NewReader(tc.in)))
|
||||
|
||||
if tc.expErr != "" {
|
||||
require.EqualError(t, err, tc.expErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expCompiledModule, cm)
|
||||
}
|
||||
|
||||
require.Equal(t, tc.expStaleCache, staleCache)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,8 @@ func TestEngine_DeleteCompiledModule(t *testing.T) {
|
||||
e, ok := NewEngine(ctx, api.CoreFeaturesV1, nil).(*engine)
|
||||
require.True(t, ok)
|
||||
id := wasm.ModuleID{0xaa}
|
||||
e.addCompiledModule(&wasm.Module{ID: id}, &compiledModule{executable: make([]byte, 1)})
|
||||
err := e.addCompiledModule(&wasm.Module{ID: id}, &compiledModule{executable: make([]byte, 1)})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint32(1), e.CompiledModuleCount())
|
||||
e.DeleteCompiledModule(&wasm.Module{ID: id})
|
||||
require.Equal(t, uint32(0), e.CompiledModuleCount())
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/experimental/logging"
|
||||
"github.com/tetratelabs/wazero/internal/engine/wazevo"
|
||||
"github.com/tetratelabs/wazero/internal/integration_test/spectest"
|
||||
v1 "github.com/tetratelabs/wazero/internal/integration_test/spectest/v1"
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
@@ -22,11 +24,35 @@ import (
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
func TestSpecTestCompilerCache(t *testing.T) {
|
||||
func TestFileCacheSpecTest_compiler(t *testing.T) {
|
||||
if !platform.CompilerSupported() {
|
||||
return
|
||||
}
|
||||
runAllFileCacheTests(t, wazero.NewRuntimeConfigCompiler())
|
||||
}
|
||||
|
||||
func TestFileCacheSpecTest_wazevo(t *testing.T) {
|
||||
if runtime.GOARCH != "arm64" {
|
||||
return
|
||||
}
|
||||
config := wazero.NewRuntimeConfigCompiler()
|
||||
wazevo.ConfigureWazevo(config)
|
||||
runAllFileCacheTests(t, config)
|
||||
}
|
||||
|
||||
func runAllFileCacheTests(t *testing.T, config wazero.RuntimeConfig) {
|
||||
t.Run("spectest", func(t *testing.T) {
|
||||
testSpecTestCompilerCache(t, config)
|
||||
})
|
||||
t.Run("listeners", func(t *testing.T) {
|
||||
testListeners(t, config)
|
||||
})
|
||||
t.Run("close on context done", func(t *testing.T) {
|
||||
testWithCloseOnContextDone(t, config)
|
||||
})
|
||||
}
|
||||
|
||||
func testSpecTestCompilerCache(t *testing.T, config wazero.RuntimeConfig) {
|
||||
const cachePathKey = "FILE_CACHE_DIR"
|
||||
cacheDir := os.Getenv(cachePathKey)
|
||||
if len(cacheDir) == 0 {
|
||||
@@ -48,6 +74,7 @@ func TestSpecTestCompilerCache(t *testing.T) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
for i := 0; i < 2; i++ {
|
||||
cmd := exec.Command(testExecutable)
|
||||
cmd.Args = append(cmd.Args, fmt.Sprintf("-test.run=%s", t.Name()))
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", cachePathKey, cacheDir))
|
||||
cmd.Stdout = buf
|
||||
cmd.Stderr = buf
|
||||
@@ -68,12 +95,12 @@ func TestSpecTestCompilerCache(t *testing.T) {
|
||||
cc, err := wazero.NewCompilationCacheWithDir(cacheDir)
|
||||
require.NoError(t, err)
|
||||
spectest.Run(t, v1.Testcases, context.Background(),
|
||||
wazero.NewRuntimeConfigCompiler().WithCompilationCache(cc).WithCoreFeatures(api.CoreFeaturesV1))
|
||||
config.WithCompilationCache(cc).WithCoreFeatures(api.CoreFeaturesV1))
|
||||
}
|
||||
}
|
||||
|
||||
// TestListeners ensures that compilation cache works as expected on and off with respect to listeners.
|
||||
func TestListeners(t *testing.T) {
|
||||
func testListeners(t *testing.T, config wazero.RuntimeConfig) {
|
||||
if !platform.CompilerSupported() {
|
||||
t.Skip()
|
||||
}
|
||||
@@ -107,7 +134,7 @@ func TestListeners(t *testing.T) {
|
||||
{
|
||||
cc, err := wazero.NewCompilationCacheWithDir(dir)
|
||||
require.NoError(t, err)
|
||||
rc := wazero.NewRuntimeConfigCompiler().WithCompilationCache(cc)
|
||||
rc := config.WithCompilationCache(cc)
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(ctxWithListener, rc)
|
||||
_, err = r.CompileModule(ctxWithListener, wasmBin)
|
||||
@@ -118,7 +145,7 @@ func TestListeners(t *testing.T) {
|
||||
|
||||
cc, err := wazero.NewCompilationCacheWithDir(dir)
|
||||
require.NoError(t, err)
|
||||
rc := wazero.NewRuntimeConfigCompiler().WithCompilationCache(cc)
|
||||
rc := config.WithCompilationCache(cc)
|
||||
r := wazero.NewRuntimeWithConfig(ctxWithListener, rc)
|
||||
_, err = r.Instantiate(ctxWithListener, wasmBin)
|
||||
require.NoError(t, err)
|
||||
@@ -144,7 +171,7 @@ func TestListeners(t *testing.T) {
|
||||
{
|
||||
cc, err := wazero.NewCompilationCacheWithDir(dir)
|
||||
require.NoError(t, err)
|
||||
rc := wazero.NewRuntimeConfigCompiler().WithCompilationCache(cc)
|
||||
rc := config.WithCompilationCache(cc)
|
||||
|
||||
out := bytes.NewBuffer(nil)
|
||||
ctxWithListener := context.WithValue(context.Background(),
|
||||
@@ -159,7 +186,7 @@ func TestListeners(t *testing.T) {
|
||||
// Then compile without listeners -> run it.
|
||||
cc, err := wazero.NewCompilationCacheWithDir(dir)
|
||||
require.NoError(t, err)
|
||||
rc := wazero.NewRuntimeConfigCompiler().WithCompilationCache(cc)
|
||||
rc := config.WithCompilationCache(cc)
|
||||
r := wazero.NewRuntimeWithConfig(context.Background(), rc)
|
||||
_, err = r.Instantiate(context.Background(), wasmBin)
|
||||
require.NoError(t, err)
|
||||
@@ -174,7 +201,7 @@ func TestListeners(t *testing.T) {
|
||||
{
|
||||
cc, err := wazero.NewCompilationCacheWithDir(dir)
|
||||
require.NoError(t, err)
|
||||
rc := wazero.NewRuntimeConfigCompiler().WithCompilationCache(cc)
|
||||
rc := config.WithCompilationCache(cc)
|
||||
r := wazero.NewRuntimeWithConfig(context.Background(), rc)
|
||||
_, err = r.CompileModule(context.Background(), wasmBin)
|
||||
require.NoError(t, err)
|
||||
@@ -189,7 +216,7 @@ func TestListeners(t *testing.T) {
|
||||
|
||||
cc, err := wazero.NewCompilationCacheWithDir(dir)
|
||||
require.NoError(t, err)
|
||||
rc := wazero.NewRuntimeConfigCompiler().WithCompilationCache(cc)
|
||||
rc := config.WithCompilationCache(cc)
|
||||
r := wazero.NewRuntimeWithConfig(ctxWithListener, rc)
|
||||
_, err = r.Instantiate(ctxWithListener, wasmBin)
|
||||
require.NoError(t, err)
|
||||
@@ -210,11 +237,7 @@ func TestListeners(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestWithCloseOnContextDone ensures that compilation cache works as expected on and off with respect to WithCloseOnContextDone config.
|
||||
func TestWithCloseOnContextDone(t *testing.T) {
|
||||
if !platform.CompilerSupported() {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
func testWithCloseOnContextDone(t *testing.T, config wazero.RuntimeConfig) {
|
||||
var (
|
||||
zero uint32 = 0
|
||||
wasmBin = binaryencoding.EncodeModule(&wasm.Module{
|
||||
@@ -238,7 +261,7 @@ func TestWithCloseOnContextDone(t *testing.T) {
|
||||
{
|
||||
cc, err := wazero.NewCompilationCacheWithDir(dir)
|
||||
require.NoError(t, err)
|
||||
rc := wazero.NewRuntimeConfigCompiler().WithCompilationCache(cc).WithCloseOnContextDone(true)
|
||||
rc := config.WithCompilationCache(cc).WithCloseOnContextDone(true)
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(ctx, rc)
|
||||
_, err = r.CompileModule(ctx, wasmBin)
|
||||
@@ -249,7 +272,7 @@ func TestWithCloseOnContextDone(t *testing.T) {
|
||||
|
||||
cc, err := wazero.NewCompilationCacheWithDir(dir)
|
||||
require.NoError(t, err)
|
||||
rc := wazero.NewRuntimeConfigCompiler().WithCompilationCache(cc).WithCloseOnContextDone(true)
|
||||
rc := config.WithCompilationCache(cc).WithCloseOnContextDone(true)
|
||||
r := wazero.NewRuntimeWithConfig(ctx, rc)
|
||||
|
||||
timeoutCtx, done := context.WithTimeout(ctx, time.Second)
|
||||
@@ -266,7 +289,7 @@ func TestWithCloseOnContextDone(t *testing.T) {
|
||||
{
|
||||
cc, err := wazero.NewCompilationCacheWithDir(dir)
|
||||
require.NoError(t, err)
|
||||
rc := wazero.NewRuntimeConfigCompiler().WithCompilationCache(cc).WithCloseOnContextDone(false)
|
||||
rc := config.WithCompilationCache(cc).WithCloseOnContextDone(false)
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(ctx, rc)
|
||||
_, err = r.CompileModule(ctx, wasmBin)
|
||||
@@ -277,7 +300,7 @@ func TestWithCloseOnContextDone(t *testing.T) {
|
||||
|
||||
cc, err := wazero.NewCompilationCacheWithDir(dir)
|
||||
require.NoError(t, err)
|
||||
rc := wazero.NewRuntimeConfigCompiler().WithCompilationCache(cc).WithCloseOnContextDone(true)
|
||||
rc := config.WithCompilationCache(cc).WithCloseOnContextDone(true)
|
||||
r := wazero.NewRuntimeWithConfig(ctx, rc)
|
||||
|
||||
timeoutCtx, done := context.WithTimeout(ctx, time.Second)
|
||||
|
||||
Reference in New Issue
Block a user