1 package wazero
2
3 import (
4 "context"
5 _ "embed"
6 "fmt"
7 "os"
8 "path"
9 goruntime "runtime"
10 "testing"
11
12 "github.com/tetratelabs/wazero/internal/platform"
13 "github.com/tetratelabs/wazero/internal/testing/require"
14 "github.com/tetratelabs/wazero/internal/wasm"
15 )
16
17
18 var facWasm []byte
19
20
21 var memGrowWasm []byte
22
23 func TestCompilationCache(t *testing.T) {
24 ctx := context.Background()
25
26 t.Run("non-host module", func(t *testing.T) {
27 foo, bar := getCacheSharedRuntimes(ctx, t)
28 cacheInst := foo.cache
29
30
31 _, err := bar.store.GetFunctionTypeIDs(
32
33 []wasm.FunctionType{{Params: []wasm.ValueType{
34 wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32,
35 wasm.ValueTypeI32, wasm.ValueTypeV128, wasm.ValueTypeI32, wasm.ValueTypeV128, wasm.ValueTypeI32, wasm.ValueTypeI32,
36 wasm.ValueTypeI32, wasm.ValueTypeV128, wasm.ValueTypeI32, wasm.ValueTypeV128, wasm.ValueTypeI32, wasm.ValueTypeI32,
37 wasm.ValueTypeI32, wasm.ValueTypeV128, wasm.ValueTypeI32, wasm.ValueTypeV128, wasm.ValueTypeI32, wasm.ValueTypeI32,
38 wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32,
39 }}})
40 require.NoError(t, err)
41
42
43 eng := foo.cache.engs[engineKindInterpreter]
44 if platform.CompilerSupported() {
45 eng = foo.cache.engs[engineKindCompiler]
46 }
47
48
49 compiled, err := foo.CompileModule(ctx, facWasm)
50 require.NoError(t, err)
51
52 require.Equal(t, uint32(1), eng.CompiledModuleCount())
53 barCompiled, err := bar.CompileModule(ctx, facWasm)
54 require.NoError(t, err)
55
56
57 require.Equal(t, compiled.(*compiledModule).module, barCompiled.(*compiledModule).module)
58 require.Equal(t, compiled.(*compiledModule).closeWithModule, barCompiled.(*compiledModule).closeWithModule)
59 require.Equal(t, compiled.(*compiledModule).compiledEngine, barCompiled.(*compiledModule).compiledEngine)
60
61 require.NotEqual(t, compiled.(*compiledModule).typeIDs, barCompiled.(*compiledModule).typeIDs)
62
63
64
65 fooInst, err := foo.InstantiateModule(ctx, compiled, NewModuleConfig().WithName("same_name"))
66 require.NoError(t, err)
67 barInst, err := bar.InstantiateModule(ctx, compiled, NewModuleConfig().WithName("same_name"))
68 require.NoError(t, err)
69
70 require.NotEqual(t, fooInst, barInst)
71
72
73 err = foo.Close(ctx)
74 require.NoError(t, err)
75 err = bar.Close(ctx)
76 require.NoError(t, err)
77 require.Equal(t, uint32(1), eng.CompiledModuleCount())
78
79
80 err = cacheInst.Close(ctx)
81 require.NoError(t, err)
82 require.Equal(t, uint32(0), eng.CompiledModuleCount())
83 })
84
85
86
87 t.Run("host module", func(t *testing.T) {
88 foo, bar := getCacheSharedRuntimes(ctx, t)
89
90 goFn := func() (dummy uint32) { return }
91 fooCompiled, err := foo.NewHostModuleBuilder("env").
92 NewFunctionBuilder().WithFunc(goFn).Export("go_fn").
93 Compile(testCtx)
94 require.NoError(t, err)
95 barCompiled, err := bar.NewHostModuleBuilder("env").
96 NewFunctionBuilder().WithFunc(goFn).Export("go_fn").
97 Compile(testCtx)
98 require.NoError(t, err)
99
100
101 require.NotEqual(t, fooCompiled, barCompiled)
102 })
103
104 t.Run("memory limit should not affect caches", func(t *testing.T) {
105
106 c := NewCompilationCache()
107 config := NewRuntimeConfig().WithCompilationCache(c)
108
109
110 rt0 := NewRuntimeWithConfig(ctx, config)
111 rt1 := NewRuntimeWithConfig(ctx, config.WithMemoryLimitPages(2))
112 rt2 := NewRuntimeWithConfig(ctx, config.WithMemoryLimitPages(4))
113
114
115 module0, _ := rt0.CompileModule(ctx, memGrowWasm)
116 module1, _ := rt1.CompileModule(ctx, memGrowWasm)
117 module2, _ := rt2.CompileModule(ctx, memGrowWasm)
118
119 max0, _ := module0.ExportedMemories()["memory"].Max()
120 max1, _ := module1.ExportedMemories()["memory"].Max()
121 max2, _ := module2.ExportedMemories()["memory"].Max()
122 require.Equal(t, uint32(5), max0)
123 require.Equal(t, uint32(2), max1)
124 require.Equal(t, uint32(4), max2)
125
126 compiledModule0 := module0.(*compiledModule)
127 compiledModule1 := module1.(*compiledModule)
128 compiledModule2 := module2.(*compiledModule)
129
130
131 require.Equal(t, compiledModule0.compiledEngine, compiledModule1.compiledEngine)
132 require.Equal(t, compiledModule1.compiledEngine, compiledModule2.compiledEngine)
133 })
134 }
135
136 func getCacheSharedRuntimes(ctx context.Context, t *testing.T) (foo, bar *runtime) {
137
138 c := NewCompilationCache()
139 config := NewRuntimeConfig().WithCompilationCache(c)
140
141 _foo := NewRuntimeWithConfig(ctx, config)
142 _bar := NewRuntimeWithConfig(ctx, config)
143
144 var ok bool
145 foo, ok = _foo.(*runtime)
146 require.True(t, ok)
147 bar, ok = _bar.(*runtime)
148 require.True(t, ok)
149
150
151 require.Equal(t, foo.cache, bar.cache)
152 return
153 }
154
155 func TestCache_ensuresFileCache(t *testing.T) {
156 const version = "dev"
157
158 expectedSubdir := fmt.Sprintf("wazero-dev-%s-%s", goruntime.GOARCH, goruntime.GOOS)
159
160 t.Run("ok", func(t *testing.T) {
161 dir := t.TempDir()
162 c := &cache{}
163 err := c.ensuresFileCache(dir, version)
164 require.NoError(t, err)
165 })
166 t.Run("create dir", func(t *testing.T) {
167 tmpDir := path.Join(t.TempDir(), "1", "2", "3")
168 dir := path.Join(tmpDir, "foo")
169
170 c := &cache{}
171 err := c.ensuresFileCache(dir, version)
172 require.NoError(t, err)
173
174 requireContainsDir(t, tmpDir, "foo")
175 })
176 t.Run("create relative dir", func(t *testing.T) {
177 tmpDir, oldwd := requireChdirToTemp(t)
178 defer os.Chdir(oldwd)
179 dir := "foo"
180
181 c := &cache{}
182 err := c.ensuresFileCache(dir, version)
183 require.NoError(t, err)
184
185 requireContainsDir(t, tmpDir, dir)
186 })
187 t.Run("basedir is not a dir", func(t *testing.T) {
188 f, err := os.CreateTemp(t.TempDir(), "nondir")
189 require.NoError(t, err)
190 defer f.Close()
191
192 c := &cache{}
193 err = c.ensuresFileCache(f.Name(), version)
194 require.Contains(t, err.Error(), "is not dir")
195 })
196 t.Run("versiondir is not a dir", func(t *testing.T) {
197 dir := t.TempDir()
198 require.NoError(t, os.WriteFile(path.Join(dir, expectedSubdir), []byte{}, 0o600))
199 c := &cache{}
200 err := c.ensuresFileCache(dir, version)
201 require.Contains(t, err.Error(), "is not dir")
202 })
203 }
204
205
206
207
208 func requireContainsDir(t *testing.T, parent, dir string) {
209 entries, err := os.ReadDir(parent)
210 require.NoError(t, err)
211 require.Equal(t, 1, len(entries))
212 require.Equal(t, dir, entries[0].Name())
213 require.True(t, entries[0].IsDir())
214 }
215
216 func requireChdirToTemp(t *testing.T) (string, string) {
217 tmpDir := t.TempDir()
218 oldwd, err := os.Getwd()
219 require.NoError(t, err)
220 require.NoError(t, os.Chdir(tmpDir))
221 return tmpDir, oldwd
222 }
223
224 func TestCache_Close(t *testing.T) {
225 t.Run("all engines", func(t *testing.T) {
226 c := &cache{engs: [engineKindCount]wasm.Engine{&mockEngine{}, &mockEngine{}}}
227 err := c.Close(testCtx)
228 require.NoError(t, err)
229 for i := engineKind(0); i < engineKindCount; i++ {
230 require.True(t, c.engs[i].(*mockEngine).closed)
231 }
232 })
233 t.Run("only interp", func(t *testing.T) {
234 c := &cache{engs: [engineKindCount]wasm.Engine{nil, &mockEngine{}}}
235 err := c.Close(testCtx)
236 require.NoError(t, err)
237 require.True(t, c.engs[engineKindInterpreter].(*mockEngine).closed)
238 })
239 t.Run("only compiler", func(t *testing.T) {
240 c := &cache{engs: [engineKindCount]wasm.Engine{&mockEngine{}, nil}}
241 err := c.Close(testCtx)
242 require.NoError(t, err)
243 require.True(t, c.engs[engineKindCompiler].(*mockEngine).closed)
244 })
245 }
246
View as plain text