1 package compiler
2
3 import (
4 "encoding/binary"
5 "fmt"
6 "math"
7 "testing"
8
9 "github.com/tetratelabs/wazero/internal/asm"
10 "github.com/tetratelabs/wazero/internal/testing/require"
11 "github.com/tetratelabs/wazero/internal/wasm"
12 "github.com/tetratelabs/wazero/internal/wazeroir"
13 )
14
15 func TestCompiler_compileMemoryGrow(t *testing.T) {
16 env := newCompilerEnvironment()
17 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
18 err := compiler.compilePreamble()
19 require.NoError(t, err)
20
21 err = compiler.compileMemoryGrow()
22 require.NoError(t, err)
23
24
25
26 const expValue uint32 = 100
27 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(expValue)))
28 require.NoError(t, err)
29 err = compiler.compileReturnFunction()
30 require.NoError(t, err)
31
32 code := asm.CodeSegment{}
33 defer func() { require.NoError(t, code.Unmap()) }()
34
35
36 _, err = compiler.compile(code.NextCodeSection())
37 require.NoError(t, err)
38 env.exec(code.Bytes())
39
40
41 require.Equal(t, nativeCallStatusCodeCallBuiltInFunction, env.compilerStatus())
42 require.Equal(t, builtinFunctionIndexMemoryGrow, env.builtinFunctionCallAddress())
43
44
45 nativecall(
46 env.ce.returnAddress,
47 env.callEngine(),
48 env.module(),
49 )
50
51
52 require.Equal(t, expValue, env.stackTopAsUint32())
53 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
54 }
55
56 func TestCompiler_compileMemorySize(t *testing.T) {
57 env := newCompilerEnvironment()
58 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{HasMemory: true})
59
60 err := compiler.compilePreamble()
61 require.NoError(t, err)
62
63
64 err = compiler.compileMemorySize()
65 require.NoError(t, err)
66
67 requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
68
69 err = compiler.compileReturnFunction()
70 require.NoError(t, err)
71
72 code := asm.CodeSegment{}
73 defer func() { require.NoError(t, code.Unmap()) }()
74
75
76 _, err = compiler.compile(code.NextCodeSection())
77 require.NoError(t, err)
78 env.exec(code.Bytes())
79
80 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
81 require.Equal(t, uint32(defaultMemoryPageNumInTest), env.stackTopAsUint32())
82 }
83
84 func TestCompiler_compileLoad(t *testing.T) {
85
86 loadTargetValue := uint64(0x12_34_56_78_9a_bc_ef_fe)
87 baseOffset := uint32(100)
88 arg := wazeroir.MemoryArg{Offset: 361}
89 offset := baseOffset + arg.Offset
90
91 tests := []struct {
92 name string
93 isFloatTarget bool
94 operationSetupFn func(t *testing.T, compiler compilerImpl)
95 loadedValueVerifyFn func(t *testing.T, loadedValueAsUint64 uint64)
96 }{
97 {
98 name: "i32.load",
99 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
100 err := compiler.compileLoad(operationPtr(wazeroir.NewOperationLoad(wazeroir.UnsignedTypeI32, arg)))
101 require.NoError(t, err)
102 },
103 loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
104 require.Equal(t, uint32(loadTargetValue), uint32(loadedValueAsUint64))
105 },
106 },
107 {
108 name: "i64.load",
109 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
110 err := compiler.compileLoad(operationPtr(wazeroir.NewOperationLoad(wazeroir.UnsignedTypeI64, arg)))
111 require.NoError(t, err)
112 },
113 loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
114 require.Equal(t, loadTargetValue, loadedValueAsUint64)
115 },
116 },
117 {
118 name: "f32.load",
119 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
120 err := compiler.compileLoad(operationPtr(wazeroir.NewOperationLoad(wazeroir.UnsignedTypeF32, arg)))
121 require.NoError(t, err)
122 },
123 loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
124 require.Equal(t, uint32(loadTargetValue), uint32(loadedValueAsUint64))
125 },
126 isFloatTarget: true,
127 },
128 {
129 name: "f64.load",
130 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
131 err := compiler.compileLoad(operationPtr(wazeroir.NewOperationLoad(wazeroir.UnsignedTypeF64, arg)))
132 require.NoError(t, err)
133 },
134 loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
135 require.Equal(t, loadTargetValue, loadedValueAsUint64)
136 },
137 isFloatTarget: true,
138 },
139 {
140 name: "i32.load8s",
141 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
142 err := compiler.compileLoad8(operationPtr(wazeroir.NewOperationLoad8(wazeroir.SignedInt32, arg)))
143 require.NoError(t, err)
144 },
145 loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
146 require.Equal(t, int32(int8(loadedValueAsUint64)), int32(uint32(loadedValueAsUint64)))
147 },
148 },
149 {
150 name: "i32.load8u",
151 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
152 err := compiler.compileLoad8(operationPtr(wazeroir.NewOperationLoad8(wazeroir.SignedUint32, arg)))
153 require.NoError(t, err)
154 },
155 loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
156 require.Equal(t, uint32(byte(loadedValueAsUint64)), uint32(loadedValueAsUint64))
157 },
158 },
159 {
160 name: "i64.load8s",
161 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
162 err := compiler.compileLoad8(operationPtr(wazeroir.NewOperationLoad8(wazeroir.SignedInt64, arg)))
163 require.NoError(t, err)
164 },
165 loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
166 require.Equal(t, int64(int8(loadedValueAsUint64)), int64(loadedValueAsUint64))
167 },
168 },
169 {
170 name: "i64.load8u",
171 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
172 err := compiler.compileLoad8(operationPtr(wazeroir.NewOperationLoad8(wazeroir.SignedUint64, arg)))
173 require.NoError(t, err)
174 },
175 loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
176 require.Equal(t, uint64(byte(loadedValueAsUint64)), loadedValueAsUint64)
177 },
178 },
179 {
180 name: "i32.load16s",
181 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
182 err := compiler.compileLoad16(operationPtr(wazeroir.NewOperationLoad16(wazeroir.SignedInt32, arg)))
183 require.NoError(t, err)
184 },
185 loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
186 require.Equal(t, int32(int16(loadedValueAsUint64)), int32(uint32(loadedValueAsUint64)))
187 },
188 },
189 {
190 name: "i32.load16u",
191 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
192 err := compiler.compileLoad16(operationPtr(wazeroir.NewOperationLoad16(wazeroir.SignedUint32, arg)))
193 require.NoError(t, err)
194 },
195 loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
196 require.Equal(t, uint32(loadedValueAsUint64), uint32(loadedValueAsUint64))
197 },
198 },
199 {
200 name: "i64.load16s",
201 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
202 err := compiler.compileLoad16(operationPtr(wazeroir.NewOperationLoad16(wazeroir.SignedInt64, arg)))
203 require.NoError(t, err)
204 },
205 loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
206 require.Equal(t, int64(int16(loadedValueAsUint64)), int64(loadedValueAsUint64))
207 },
208 },
209 {
210 name: "i64.load16u",
211 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
212 err := compiler.compileLoad16(operationPtr(wazeroir.NewOperationLoad16(wazeroir.SignedUint64, arg)))
213 require.NoError(t, err)
214 },
215 loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
216 require.Equal(t, uint64(uint16(loadedValueAsUint64)), loadedValueAsUint64)
217 },
218 },
219 {
220 name: "i64.load32s",
221 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
222 err := compiler.compileLoad32(operationPtr(wazeroir.NewOperationLoad32(true, arg)))
223 require.NoError(t, err)
224 },
225 loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
226 require.Equal(t, int64(int32(loadedValueAsUint64)), int64(loadedValueAsUint64))
227 },
228 },
229 {
230 name: "i64.load32u",
231 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
232 err := compiler.compileLoad32(operationPtr(wazeroir.NewOperationLoad32(false, arg)))
233 require.NoError(t, err)
234 },
235 loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
236 require.Equal(t, uint64(uint32(loadedValueAsUint64)), loadedValueAsUint64)
237 },
238 },
239 }
240
241 for _, tt := range tests {
242 tc := tt
243 t.Run(tc.name, func(t *testing.T) {
244 env := newCompilerEnvironment()
245 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{HasMemory: true})
246
247 err := compiler.compilePreamble()
248 require.NoError(t, err)
249
250 binary.LittleEndian.PutUint64(env.memory()[offset:], loadTargetValue)
251
252
253 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(baseOffset)))
254 require.NoError(t, err)
255
256 tc.operationSetupFn(t, compiler)
257
258
259 requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
260 require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
261 loadedLocation := compiler.runtimeValueLocationStack().peek()
262 require.True(t, loadedLocation.onRegister())
263 if tc.isFloatTarget {
264 require.Equal(t, registerTypeVector, loadedLocation.getRegisterType())
265 } else {
266 require.Equal(t, registerTypeGeneralPurpose, loadedLocation.getRegisterType())
267 }
268 err = compiler.compileReturnFunction()
269 require.NoError(t, err)
270
271 code := asm.CodeSegment{}
272 defer func() { require.NoError(t, code.Unmap()) }()
273
274
275 _, err = compiler.compile(code.NextCodeSection())
276 require.NoError(t, err)
277 env.exec(code.Bytes())
278
279
280 require.Equal(t, uint64(1), env.stackPointer())
281 tc.loadedValueVerifyFn(t, env.stackTopAsUint64())
282 })
283 }
284 }
285
286 func TestCompiler_compileStore(t *testing.T) {
287
288 storeTargetValue := uint64(math.MaxUint64)
289 baseOffset := uint32(100)
290 arg := wazeroir.MemoryArg{Offset: 361}
291 offset := arg.Offset + baseOffset
292
293 tests := []struct {
294 name string
295 isFloatTarget bool
296 targetSizeInBytes uint32
297 operationSetupFn func(t *testing.T, compiler compilerImpl)
298 storedValueVerifyFn func(t *testing.T, mem []byte)
299 }{
300 {
301 name: "i32.store",
302 targetSizeInBytes: 32 / 8,
303 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
304 err := compiler.compileStore(operationPtr(wazeroir.NewOperationStore(wazeroir.UnsignedTypeI32, arg)))
305 require.NoError(t, err)
306 },
307 storedValueVerifyFn: func(t *testing.T, mem []byte) {
308 require.Equal(t, uint32(storeTargetValue), binary.LittleEndian.Uint32(mem[offset:]))
309 },
310 },
311 {
312 name: "f32.store",
313 isFloatTarget: true,
314 targetSizeInBytes: 32 / 8,
315 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
316 err := compiler.compileStore(operationPtr(wazeroir.NewOperationStore(wazeroir.UnsignedTypeF32, arg)))
317 require.NoError(t, err)
318 },
319 storedValueVerifyFn: func(t *testing.T, mem []byte) {
320 require.Equal(t, uint32(storeTargetValue), binary.LittleEndian.Uint32(mem[offset:]))
321 },
322 },
323 {
324 name: "i64.store",
325 targetSizeInBytes: 64 / 8,
326 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
327 err := compiler.compileStore(operationPtr(wazeroir.NewOperationStore(wazeroir.UnsignedTypeI64, arg)))
328 require.NoError(t, err)
329 },
330 storedValueVerifyFn: func(t *testing.T, mem []byte) {
331 require.Equal(t, storeTargetValue, binary.LittleEndian.Uint64(mem[offset:]))
332 },
333 },
334 {
335 name: "f64.store",
336 isFloatTarget: true,
337 targetSizeInBytes: 64 / 8,
338 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
339 err := compiler.compileStore(operationPtr(wazeroir.NewOperationStore(wazeroir.UnsignedTypeF64, arg)))
340 require.NoError(t, err)
341 },
342 storedValueVerifyFn: func(t *testing.T, mem []byte) {
343 require.Equal(t, storeTargetValue, binary.LittleEndian.Uint64(mem[offset:]))
344 },
345 },
346 {
347 name: "store8",
348 targetSizeInBytes: 1,
349 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
350 err := compiler.compileStore8(operationPtr(wazeroir.NewOperationStore8(arg)))
351 require.NoError(t, err)
352 },
353 storedValueVerifyFn: func(t *testing.T, mem []byte) {
354 require.Equal(t, byte(storeTargetValue), mem[offset])
355 },
356 },
357 {
358 name: "store16",
359 targetSizeInBytes: 16 / 8,
360 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
361 err := compiler.compileStore16(operationPtr(wazeroir.NewOperationStore16(arg)))
362 require.NoError(t, err)
363 },
364 storedValueVerifyFn: func(t *testing.T, mem []byte) {
365 require.Equal(t, uint16(storeTargetValue), binary.LittleEndian.Uint16(mem[offset:]))
366 },
367 },
368 {
369 name: "store32",
370 targetSizeInBytes: 32 / 8,
371 operationSetupFn: func(t *testing.T, compiler compilerImpl) {
372 err := compiler.compileStore32(operationPtr(wazeroir.NewOperationStore32(arg)))
373 require.NoError(t, err)
374 },
375 storedValueVerifyFn: func(t *testing.T, mem []byte) {
376 require.Equal(t, uint32(storeTargetValue), binary.LittleEndian.Uint32(mem[offset:]))
377 },
378 },
379 }
380
381 for _, tt := range tests {
382 tc := tt
383 t.Run(tc.name, func(t *testing.T) {
384 env := newCompilerEnvironment()
385 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{HasMemory: true})
386
387 err := compiler.compilePreamble()
388 require.NoError(t, err)
389
390
391 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(baseOffset)))
392 require.NoError(t, err)
393 if tc.isFloatTarget {
394 err = compiler.compileConstF64(operationPtr(wazeroir.NewOperationConstF64(math.Float64frombits(storeTargetValue))))
395 } else {
396 err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(storeTargetValue)))
397 }
398 require.NoError(t, err)
399
400 tc.operationSetupFn(t, compiler)
401
402
403 require.Zero(t, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
404 requireRuntimeLocationStackPointerEqual(t, uint64(0), compiler)
405
406 code := asm.CodeSegment{}
407 defer func() { require.NoError(t, code.Unmap()) }()
408
409
410 err = compiler.compileReturnFunction()
411 require.NoError(t, err)
412 _, err = compiler.compile(code.NextCodeSection())
413 require.NoError(t, err)
414
415
416
417 ceil := offset + tc.targetSizeInBytes
418 mem := env.memory()
419 expectedNeighbor8Bytes := uint64(0x12_34_56_78_9a_bc_ef_fe)
420 binary.LittleEndian.PutUint64(mem[offset-8:offset], expectedNeighbor8Bytes)
421 binary.LittleEndian.PutUint64(mem[ceil:ceil+8], expectedNeighbor8Bytes)
422
423
424 env.exec(code.Bytes())
425
426 tc.storedValueVerifyFn(t, mem)
427
428
429 require.Equal(t, expectedNeighbor8Bytes, binary.LittleEndian.Uint64(mem[offset-8:offset]))
430 require.Equal(t, expectedNeighbor8Bytes, binary.LittleEndian.Uint64(mem[ceil:ceil+8]))
431 })
432 }
433 }
434
435 func TestCompiler_MemoryOutOfBounds(t *testing.T) {
436 bases := []uint32{0, 1 << 5, 1 << 9, 1 << 10, 1 << 15, math.MaxUint32 - 1, math.MaxUint32}
437 offsets := []uint32{
438 0,
439 1 << 10, 1 << 31,
440 defaultMemoryPageNumInTest*wasm.MemoryPageSize - 1, defaultMemoryPageNumInTest * wasm.MemoryPageSize,
441 math.MaxInt32 - 1, math.MaxInt32 - 2, math.MaxInt32 - 3, math.MaxInt32 - 4,
442 math.MaxInt32 - 5, math.MaxInt32 - 8, math.MaxInt32 - 9, math.MaxInt32, math.MaxUint32,
443 }
444 targetSizeInBytes := []int64{1, 2, 4, 8}
445 for _, base := range bases {
446 base := base
447 for _, offset := range offsets {
448 offset := offset
449 for _, targetSizeInByte := range targetSizeInBytes {
450 targetSizeInByte := targetSizeInByte
451 t.Run(fmt.Sprintf("base=%d,offset=%d,targetSizeInBytes=%d", base, offset, targetSizeInByte), func(t *testing.T) {
452 env := newCompilerEnvironment()
453 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
454
455 err := compiler.compilePreamble()
456 require.NoError(t, err)
457
458 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(base)))
459 require.NoError(t, err)
460
461 arg := wazeroir.MemoryArg{Offset: offset}
462
463 switch targetSizeInByte {
464 case 1:
465 err = compiler.compileLoad8(operationPtr(wazeroir.NewOperationLoad8(wazeroir.SignedInt32, arg)))
466 case 2:
467 err = compiler.compileLoad16(operationPtr(wazeroir.NewOperationLoad16(wazeroir.SignedInt32, arg)))
468 case 4:
469 err = compiler.compileLoad32(operationPtr(wazeroir.NewOperationLoad32(false, arg)))
470 case 8:
471 err = compiler.compileLoad(operationPtr(wazeroir.NewOperationLoad(wazeroir.UnsignedTypeF64, arg)))
472 default:
473 t.Fail()
474 }
475
476 require.NoError(t, err)
477 require.NoError(t, compiler.compileReturnFunction())
478
479 code := asm.CodeSegment{}
480 defer func() { require.NoError(t, code.Unmap()) }()
481
482
483 _, err = compiler.compile(code.NextCodeSection())
484 require.NoError(t, err)
485 env.exec(code.Bytes())
486
487 mem := env.memory()
488 if ceil := int64(base) + int64(offset) + int64(targetSizeInByte); int64(len(mem)) < ceil {
489
490
491 require.Equal(t, nativeCallStatusCodeMemoryOutOfBounds, env.compilerStatus())
492 }
493 })
494 }
495 }
496 }
497 }
498
View as plain text