Repository: tetratelabs/wazero
Pull request #735 (2022-08-04 12:04:12)

Optimize memory bulk operations: fill on x86
Nobody answered me in #512 🥲, so I decided on my own what implementation to use.

Instead of a copy loop:
```asm
.loop:
MOVB [dst], value
DEC size
CMP size, 0
INC dst
JNE .loop
```

Its now a copy loop for the first %15 bytes, and then `REP STOSQ` instructions for efficient byte copies.
See https://github.com/tetratelabs/wazero/issues/512#issuecomment-1198659163 for benchmarks.

```asm
.loop:
TEST size, 15
JZ .end
MOVB [dst], value
DEC size
INC dst
JMP .loop
.end:

; divide by 8
SHR size, 8

; replicate byte on full register
IMUL value, 0x01010101010101

; exchange registers rdi, rax, rcx
REP STOSQ
; exhcange registers back
```



dranikpg: The performance improvements are great. We have an about 5% performance decrease for small sizes up to 16 bytes, **but are up to 30 times faster for multi kilobyte ranges** and multiple times faster for several hundred bytes 🔥 

| Test  | Old | New | Ratio |
| :---        |  :---   |  :--- | :---: |
|5|6.823|7.219|0.95|
|17|11.7|10.52|1.11|
|128|67.8|13.6|4.99|
|10000|5066.0|159.9|31.68|
|64000|29764.0|1021.0|29.15|


codefromthecrypt: howdy, and apologies about neglect on your question earlier. Both of us had looked at it, started to think and became distracted by personal things and international travel. I promised to reply and I didn't so I apologize.

Good news is we're together even physically next week and can review this when you're ready for review (assuming you'll do both arm64 also in this PR)
mathetake: get back to you next monday or tuesday !

Patch:
diff --git a/config.go b/config.go
index 99bef862..cee38053 100644
--- a/config.go
+++ b/config.go
@@ -565,12 +565,15 @@ type ModuleConfig interface {
 	// See WithNanosleep
 	WithSysNanosleep() ModuleConfig
 
-	// WithRandSource configures a source of random bytes. Defaults to crypto/rand.Reader.
+	// WithRandSource configures a source of random bytes. Defaults to return a
+	// deterministic source. You might override this with crypto/rand.Reader
 	//
-	// This reader is most commonly used by the functions like "random_get" in "wasi_snapshot_preview1" or "seed" in
-	// AssemblyScript standard "env" although it could be used by functions imported from other modules.
+	// This reader is most commonly used by the functions like "random_get" in
+	// "wasi_snapshot_preview1", "seed" in AssemblyScript standard "env", and
+	// "getRandomData" when runtime.GOOS is "js".
 	//
-	// Note: The caller is responsible to close any io.Reader they supply: It is not closed on api.Module Close.
+	// Note: The caller is responsible to close any io.Reader they supply: It
+	// is not closed on api.Module Close.
 	WithRandSource(io.Reader) ModuleConfig
 }
 
diff --git a/internal/asm/amd64/consts.go b/internal/asm/amd64/consts.go
index 896d8662..7418cc90 100644
--- a/internal/asm/amd64/consts.go
+++ b/internal/asm/amd64/consts.go
@@ -191,6 +191,8 @@ const (
 	MULL
 	// MULQ is the MUL instruction in 64-bit mode. https://www.felixcloutier.com/x86/mul
 	MULQ
+	// IMULQ is the IMUL instruction in 64-bit mode. https://www.felixcloutier.com/x86/imul
+	IMULQ
 	// MULSD is the MULSD instruction. https://www.felixcloutier.com/x86/mulsd
 	MULSD
 	// MULSS is the MULSS instruction. https://www.felixcloutier.com/x86/mulss
@@ -219,6 +221,8 @@ const (
 	PSRLQ
 	// REPMOVSQ is the REP MOVSQ instruction in 64-bit mode. https://www.felixcloutier.com/x86/movs:movsb:movsw:movsd:movsq https://www.felixcloutier.com/x86/rep:repe:repz:repne:repnz
 	REPMOVSQ
+	// REPSTOSQ is the REP STOSQ instruction in 64-bit mode. https://www.felixcloutier.com/x86/stos:stosb:stosw:stosd:stosq https://www.felixcloutier.com/x86/rep:repe:repz:repne:repnz
+	REPSTOSQ
 	// ROLL is the ROL instruction in 32-bit mode. https://www.felixcloutier.com/x86/rcl:rcr:rol:ror
 	ROLL
 	// ROLQ is the ROL instruction in 64-bit mode. https://www.felixcloutier.com/x86/rcl:rcr:rol:ror
@@ -717,6 +721,8 @@ func InstructionName(instruction asm.Instruction) string {
 		return "MULL"
 	case MULQ:
 		return "MULQ"
+	case IMULQ:
+		return "IMULQ"
 	case MULSD:
 		return "MULSD"
 	case MULSS:
@@ -743,6 +749,8 @@ func InstructionName(instruction asm.Instruction) string {
 		return "PSRLQ"
 	case REPMOVSQ:
 		return "REP MOVSQ"
+	case REPSTOSQ:
+		return "REP STOSQ"
 	case ROLL:
 		return "ROLL"
 	case ROLQ:
diff --git a/internal/asm/amd64/impl.go b/internal/asm/amd64/impl.go
index 76b27871..ffe8621b 100644
--- a/internal/asm/amd64/impl.go
+++ b/internal/asm/amd64/impl.go
@@ -788,6 +788,8 @@ func (a *AssemblerImpl) encodeNoneToNone(n *nodeImpl) (err error) {
 		_, err = a.buf.Write([]byte{0x0f, 0x0b})
 	case REPMOVSQ:
 		_, err = a.buf.Write([]byte{0xf3, RexPrefixW, 0xa5})
+	case REPSTOSQ:
+		_, err = a.buf.Write([]byte{0xf3, RexPrefixW, 0xab})
 	case STD:
 		_, err = a.buf.Write([]byte{0xfd})
 	case CLD:
@@ -1164,6 +1166,8 @@ var registerToRegisterOpcode = map[asm.Instruction]struct {
 	MOVWQSX: {opcode: []byte{0x0f, 0xbf}, rPrefix: RexPrefixW},
 	// https://www.felixcloutier.com/x86/movsx:movsxd
 	MOVWLSX: {opcode: []byte{0x0f, 0xbf}},
+	// https://www.felixcloutier.com/x86/imul
+	IMULQ: {opcode: []byte{0x0f, 0xaf}, rPrefix: RexPrefixW},
 	// https://www.felixcloutier.com/x86/mulss
 	MULSS: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0x59}, requireSrcFloat: true, requireDstFloat: true},
 	// https://www.felixcloutier.com/x86/mulsd
diff --git a/internal/engine/compiler/impl_amd64.go b/internal/engine/compiler/impl_amd64.go
index 6b26c6d1..82ee330c 100644
--- a/internal/engine/compiler/impl_amd64.go
+++ b/internal/engine/compiler/impl_amd64.go
@@ -3801,8 +3801,45 @@ func (c *amd64Compiler) compileMemoryCopy() error {
 	return nil
 }
 
+// compileFillLoopImpl implements a REP STOSQ fill loop.
+func (c *amd64Compiler) compileFillLoopImpl(destinationOffset, value, fillSize *runtimeValueLocation, tmp asm.Register, replicateByte bool) {
+	// Skip if nothing to fill.
+	c.assembler.CompileRegisterToConst(amd64.CMPQ, fillSize.register, 0)
+	emptyEightGroupsJump := c.assembler.CompileJump(amd64.JEQ)
+
+	if replicateByte {
+		// Replicate single byte onto full 8-byte register.
+		c.assembler.CompileConstToRegister(amd64.MOVQ, 0x0101010101010101, tmp)
+		c.assembler.CompileRegisterToRegister(amd64.IMULQ, tmp, value.register)
+	}
+
+	// Prepare registers for swaps. There will never be more than 3 XCHGs in total.
+	restoreCrossing := c.compilePreventCrossedTargetRegisters(
+		[]*runtimeValueLocation{destinationOffset, value, fillSize},
+		[]asm.Register{amd64.RegDI, amd64.RegAX, amd64.RegCX})
+
+	// Prepare registers for REP STOSQ: fill at [rdi] with rax, rcx times.
+	c.compileMaybeSwapRegisters(destinationOffset.register, amd64.RegDI)
+	c.compileMaybeSwapRegisters(value.register, amd64.RegAX)
+	c.compileMaybeSwapRegisters(fillSize.register, amd64.RegCX)
+
+	c.assembler.CompileStandAlone(amd64.REPSTOSQ)
+
+	// Restore registers.
+	c.compileMaybeSwapRegisters(destinationOffset.register, amd64.RegDI)
+	c.compileMaybeSwapRegisters(value.register, amd64.RegAX)
+	c.compileMaybeSwapRegisters(fillSize.register, amd64.RegCX)
+	restoreCrossing()
+
+	c.assembler.SetJumpTargetOnNext(emptyEightGroupsJump)
+}
+
 // compileMemoryFill implements compiler.compileMemoryFill for the amd64 architecture.
 //
+// This function uses efficient `REP STOSQ` instructions to copy in quadword (8 bytes) batches
+// if the size if above 15 bytes. For smaller sizes, a simple MOVB copy loop is the best
+// option.
+//
 // TODO: the compiled code in this function should be reused and compile at once as
 // the code is independent of any module.
 func (c *amd64Compiler) compileFillImpl(isTable bool, tableIndex uint32) error {
@@ -3864,35 +3901,36 @@ func (c *amd64Compiler) compileFillImpl(isTable bool, tableIndex uint32) error {
 	c.assembler.CompileRegisterToConst(amd64.CMPQ, copySize.register, 0)
 	skipJump := c.assembler.CompileJump(amd64.JEQ)
 
-	var scale int16
-	var movInst asm.Instruction
+	// destinationOffset -= size.
+	c.assembler.CompileRegisterToRegister(amd64.SUBQ, copySize.register, destinationOffset.register)
+
 	if isTable {
 		// Each element is of type uintptr; 2^3 = 1 << pointerSizeLog2.
 		c.assembler.CompileConstToRegister(amd64.SHLQ, pointerSizeLog2, destinationOffset.register)
 		// destinationOffset += table buffer's absolute address.
 		c.assembler.CompileMemoryToRegister(amd64.ADDQ, tmp, tableInstanceTableOffset, destinationOffset.register)
-		// For tables, we move 8 bytes at once.
-		scale, movInst = 8, amd64.MOVQ
+
 	} else {
 		// destinationOffset += memory buffer's absolute address.
 		c.assembler.CompileRegisterToRegister(amd64.ADDQ, amd64ReservedRegisterForMemory, destinationOffset.register)
-		scale, movInst = 1, amd64.MOVB
-	}
 
-	// Negate the counter.
-	c.assembler.CompileNoneToRegister(amd64.NEGQ, copySize.register)
+		// Copy first %15 bytes with simple MOVB instruction.
+		beginCopyLoop := c.assembler.CompileStandAlone(amd64.NOP)
+		c.assembler.CompileConstToRegister(amd64.TESTQ, 15, copySize.register)
+		breakLoop := c.assembler.CompileJump(amd64.JEQ)
 
-	beginCopyLoop := c.assembler.CompileStandAlone(amd64.NOP)
+		c.assembler.CompileRegisterToMemory(amd64.MOVB, value.register, destinationOffset.register, 0)
 
-	// [destinationOffset + (size.register)] = tmp.
-	c.assembler.CompileRegisterToMemoryWithIndex(movInst,
-		value.register,
-		destinationOffset.register, 0, copySize.register, scale,
-	)
+		c.assembler.CompileNoneToRegister(amd64.INCQ, destinationOffset.register)
+		c.assembler.CompileNoneToRegister(amd64.DECQ, copySize.register)
+		c.assembler.CompileJump(amd64.JMP).AssignJumpTarget(beginCopyLoop)
 
-	// size += 1
-	c.assembler.CompileNoneToRegister(amd64.INCQ, copySize.register)
-	c.assembler.CompileJump(amd64.JMI).AssignJumpTarget(beginCopyLoop)
+		c.assembler.SetJumpTargetOnNext(breakLoop)
+		// compileFillLoopImpl counts in groups of 8 bytes, so we have to divide the copySize by 8.
+		c.assembler.CompileConstToRegister(amd64.SHRQ, 3, copySize.register)
+	}
+
+	c.compileFillLoopImpl(destinationOffset, value, copySize, tmp, !isTable)
 
 	c.locationStack.markRegisterUnused(copySize.register, value.register,
 		destinationOffset.register, tmp)
@@ -4509,7 +4547,7 @@ func (c *amd64Compiler) compileCallFunctionImpl(index wasm.Index, functionAddres
 	// Since call frame stack pointer is the index for callEngine.callFrameStack slice,
 	// here we get the actual offset in bytes via shifting callFrameStackPointerRegister by callFrameDataSizeMostSignificantSetBit.
 	// That is valid because the size of callFrame struct is a power of 2 (see TestVerifyOffsetValue), which means
-	// multiplying withe the size of struct equals shifting by its most significant bit.
+	// multiplying with the size of struct equals shifting by its most significant bit.
 	c.assembler.CompileConstToRegister(amd64.SHLQ, int64(callFrameDataSizeMostSignificantSetBit), callFrameStackPointerRegister)
 
 	// At this point, callFrameStackPointerRegister holds the offset in call frame slice in bytes,
@@ -4637,6 +4675,7 @@ func (c *amd64Compiler) compileCallFunctionImpl(index wasm.Index, functionAddres
 	if err := c.compileModuleContextInitialization(); err != nil {
 		return err
 	}
+
 	// Due to the change to callEngine.valueStackContext.stackBasePointer.
 	c.compileReservedStackBasePointerInitialization()
 
@@ -4926,7 +4965,7 @@ func (c *amd64Compiler) compileReservedStackBasePointerInitialization() {
 }
 
 func (c *amd64Compiler) compileReservedMemoryPointerInitialization() {
-	if c.ir.HasMemory {
+	if c.ir.HasMemory || c.ir.UsesMemory {
 		c.assembler.CompileMemoryToRegister(amd64.MOVQ,
 			amd64ReservedRegisterForCallEngine, callEngineModuleContextMemoryElement0AddressOffset,
 			amd64ReservedRegisterForMemory,
@@ -4965,13 +5004,6 @@ func (c *amd64Compiler) compileMaybeGrowValueStack() error {
 // callEngine.ModuleContext.ModuleInstanceAddress.
 // This is called in two cases: in function preamble, and on the return from (non-Go) function calls.
 func (c *amd64Compiler) compileModuleContextInitialization() error {
-	if c.ir.IsHostFunction {
-		// If this is the host function, our semantic is that host functions use the caller's context.
-		// Therefore, we can skip all the initialization steps here as at this point, the caller's memory, etc
-		// are already set on the callEngine.
-		return nil
-	}
-
 	// amd64CallingConventionModuleInstanceAddressRegister holds the module instance's address
 	// so mark it used so that it won't be used as a free register until the module context initialization finishes.
 	c.locationStack.markRegisterUsed(amd64CallingConventionModuleInstanceAddressRegister)
@@ -5061,7 +5093,7 @@ func (c *amd64Compiler) compileModuleContextInitialization() error {
 		c.assembler.CompileRegisterToMemory(amd64.MOVQ, tmpRegister2,
 			amd64ReservedRegisterForCallEngine, callEngineModuleContextMemorySliceLenOffset)
 
-		// Set elemnt zero address.
+		// Set element zero address.
 		c.assembler.CompileMemoryToRegister(amd64.MOVQ, tmpRegister, memoryInstanceBufferOffset, tmpRegister2)
 		c.assembler.CompileRegisterToMemory(amd64.MOVQ, tmpRegister2,
 			amd64ReservedRegisterForCallEngine, callEngineModuleContextMemoryElement0AddressOffset)
diff --git a/internal/engine/compiler/impl_arm64.go b/internal/engine/compiler/impl_arm64.go
index f370174a..928f360a 100644
--- a/internal/engine/compiler/impl_arm64.go
+++ b/internal/engine/compiler/impl_arm64.go
@@ -4117,7 +4117,7 @@ func (c *arm64Compiler) compileReservedStackBasePointerRegisterInitialization()
 }
 
 func (c *arm64Compiler) compileReservedMemoryRegisterInitialization() {
-	if c.ir.HasMemory {
+	if c.ir.HasMemory || c.ir.UsesMemory {
 		// "arm64ReservedRegisterForMemory = ce.MemoryElement0Address"
 		c.assembler.CompileMemoryToRegister(
 			arm64.LDRD,
@@ -4131,12 +4131,6 @@ func (c *arm64Compiler) compileReservedMemoryRegisterInitialization() {
 // ce.moduleContext.ModuleInstanceAddress.
 // This is called in two cases: in function preamble, and on the return from (non-Go) function calls.
 func (c *arm64Compiler) compileModuleContextInitialization() error {
-	if c.ir.IsHostFunction {
-		// If this is the host function, our semantic is that host functions use the caller's context.
-		// Therefore, we can skip all the initialization steps here as at this point, the caller's memory, etc
-		// are already set on the callEngine.
-		return nil
-	}
 
 	c.markRegisterUsed(arm64CallingConventionModuleInstanceAddressRegister)
 	defer c.markRegisterUnused(arm64CallingConventionModuleInstanceAddressRegister)
diff --git a/internal/platform/crypto.go b/internal/platform/crypto.go
new file mode 100644
index 00000000..c141f00f
--- /dev/null
+++ b/internal/platform/crypto.go
@@ -0,0 +1,17 @@
+package platform
+
+import (
+	"io"
+	"math/rand"
+)
+
+// seed is a fixed seed value for NewFakeRandSource.
+//
+// Trivia: While arbitrary, 42 was chosen as it is the "Ultimate Answer" in
+// the Douglas Adams novel "The Hitchhiker's Guide to the Galaxy."
+const seed = int64(42)
+
+// NewFakeRandSource returns a deterministic source of random values.
+func NewFakeRandSource() io.Reader {
+	return rand.New(rand.NewSource(seed))
+}
diff --git a/internal/sys/sys.go b/internal/sys/sys.go
index 663773e0..7b1e405c 100644
--- a/internal/sys/sys.go
+++ b/internal/sys/sys.go
@@ -2,7 +2,6 @@ package sys
 
 import (
 	"context"
-	"crypto/rand"
 	"errors"
 	"fmt"
 	"io"
@@ -118,7 +117,7 @@ func (c *Context) FS(ctx context.Context) *FSContext {
 	return c.fsc
 }
 
-// RandSource is a source of random bytes and defaults to crypto/rand.Reader.
+// RandSource is a source of random bytes and defaults to a deterministic source.
 // see wazero.ModuleConfig WithRandSource
 func (c *Context) RandSource() io.Reader {
 	return c.randSource
@@ -153,8 +152,10 @@ func NewContext(
 	stdin io.Reader,
 	stdout, stderr io.Writer,
 	randSource io.Reader,
-	walltime *sys.Walltime, walltimeResolution sys.ClockResolution,
-	nanotime *sys.Nanotime, nanotimeResolution sys.ClockResolution,
+	walltime *sys.Walltime,
+	walltimeResolution sys.ClockResolution,
+	nanotime *sys.Nanotime,
+	nanotimeResolution sys.ClockResolution,
 	nanosleep *sys.Nanosleep,
 	fs fs.FS,
 ) (sysCtx *Context, err error) {
@@ -187,7 +188,7 @@ func NewContext(
 	}
 
 	if randSource == nil {
-		sysCtx.randSource = rand.Reader
+		sysCtx.randSource = platform.NewFakeRandSource()
 	} else {
 		sysCtx.randSource = randSource
 	}
diff --git a/wasi_snapshot_preview1/fs.go b/wasi_snapshot_preview1/fs.go
index 4b4cfecf..5a1866df 100644
--- a/wasi_snapshot_preview1/fs.go
+++ b/wasi_snapshot_preview1/fs.go
@@ -500,7 +500,7 @@ var fdSeek = wasm.NewGoFunc(
 			return ErrnoIo
 		}
 
-		if !mod.Memory().WriteUint32Le(ctx, resultNewoffset, uint32(newOffset)) {
+		if !mod.Memory().WriteUint64Le(ctx, resultNewoffset, uint64(newOffset)) {
 			return ErrnoFault
 		}
 


Test patch:
diff --git a/assemblyscript/assemblyscript_test.go b/assemblyscript/assemblyscript_test.go
index a23895fc..7f10dcfe 100644
--- a/assemblyscript/assemblyscript_test.go
+++ b/assemblyscript/assemblyscript_test.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"context"
 	_ "embed"
+	"encoding/hex"
 	"errors"
 	"io"
 	"strings"
@@ -123,8 +124,7 @@ func TestAbort_Error(t *testing.T) {
 }
 
 func TestSeed(t *testing.T) {
-	b := []byte{0, 1, 2, 3, 4, 5, 6, 7}
-	mod, r, log := requireProxyModule(t, NewFunctionExporter(), wazero.NewModuleConfig().WithRandSource(bytes.NewReader(b)))
+	mod, r, log := requireProxyModule(t, NewFunctionExporter(), wazero.NewModuleConfig())
 	defer r.Close(testCtx)
 
 	ret, err := mod.ExportedFunction(functionSeed).Call(testCtx)
@@ -132,11 +132,11 @@ func TestSeed(t *testing.T) {
 	require.Equal(t, `
 --> proxy.seed()
 	==> env.~lib/builtins/seed()
-	<== (7.949928895127363e-275)
-<-- (7.949928895127363e-275)
+	<== (4.958153677776298e-175)
+<-- (4.958153677776298e-175)
 `, "\n"+log.String())
 
-	require.Equal(t, b, u64.LeBytes(ret[0]))
+	require.Equal(t, "538c7f96b164bf1b", hex.EncodeToString(u64.LeBytes(ret[0])))
 }
 
 func TestSeed_error(t *testing.T) {
diff --git a/config_test.go b/config_test.go
index a17b2711..729450fe 100644
--- a/config_test.go
+++ b/config_test.go
@@ -2,6 +2,7 @@ package wazero
 
 import (
 	"context"
+	"crypto/rand"
 	"io"
 	"io/fs"
 	"math"
@@ -479,6 +480,23 @@ func TestModuleConfig_toSysContext(t *testing.T) {
 				testFS2, // fs
 			),
 		},
+		{
+			name:  "WithRandSource",
+			input: base.WithRandSource(rand.Reader),
+			expected: requireSysContext(t,
+				math.MaxUint32, // max
+				nil,            // args
+				nil,            // environ
+				nil,            // stdin
+				nil,            // stdout
+				nil,            // stderr
+				rand.Reader,    // randSource
+				&wt, 1,         // walltime, walltimeResolution
+				&nt, 1, // nanotime, nanotimeResolution
+				nil, // nanosleep
+				nil, // fs
+			),
+		},
 	}
 
 	for _, tt := range tests {
diff --git a/internal/asm/amd64/impl_1_test.go b/internal/asm/amd64/impl_1_test.go
index bf6b03b5..02c26927 100644
--- a/internal/asm/amd64/impl_1_test.go
+++ b/internal/asm/amd64/impl_1_test.go
@@ -445,6 +445,7 @@ func TestAssemblerImpl_encodeNoneToNone(t *testing.T) {
 		{inst: NOP, exp: nil},
 		{inst: RET, exp: []byte{0xc3}},
 		{inst: REPMOVSQ, exp: []byte{0xf3, RexPrefixW, 0xa5}},
+		{inst: REPSTOSQ, exp: []byte{0xf3, RexPrefixW, 0xab}},
 		{inst: STD, exp: []byte{0xfd}},
 		{inst: CLD, exp: []byte{0xfc}},
 	}
diff --git a/internal/asm/amd64/impl_2_test.go b/internal/asm/amd64/impl_2_test.go
index 034b43b3..3b0e9652 100644
--- a/internal/asm/amd64/impl_2_test.go
+++ b/internal/asm/amd64/impl_2_test.go
@@ -1013,6 +1013,10 @@ func TestAssemblerImpl_EncodeRegisterToRegister(t *testing.T) {
 		{name: "MULSS/src=X0/dst=X8/arg=0", n: &nodeImpl{instruction: MULSS, srcReg: RegX0, dstReg: RegX8, arg: 0x0}, exp: []byte{0xf3, 0x44, 0xf, 0x59, 0xc0}},
 		{name: "MULSS/src=X8/dst=X0/arg=0", n: &nodeImpl{instruction: MULSS, srcReg: RegX8, dstReg: RegX0, arg: 0x0}, exp: []byte{0xf3, 0x41, 0xf, 0x59, 0xc0}},
 		{name: "MULSS/src=X8/dst=X8/arg=0", n: &nodeImpl{instruction: MULSS, srcReg: RegX8, dstReg: RegX8, arg: 0x0}, exp: []byte{0xf3, 0x45, 0xf, 0x59, 0xc0}},
+		{name: "IMULQ/src=R8/dst=R8/arg=0", n: &nodeImpl{instruction: IMULQ, srcReg: RegR8, dstReg: RegR8, arg: 0x0}, exp: []byte{0x4d, 0xf, 0xaf, 0xc0}},
+		{name: "IMULQ/src=DX/dst=AX/arg=0", n: &nodeImpl{instruction: IMULQ, srcReg: RegDX, dstReg: RegAX, arg: 0x0}, exp: []byte{0x48, 0xf, 0xaf, 0xc2}},
+		{name: "IMULQ/src=R10/dst=CX/arg=0", n: &nodeImpl{instruction: IMULQ, srcReg: RegR10, dstReg: RegCX, arg: 0x0}, exp: []byte{0x49, 0xf, 0xaf, 0xca}},
+		{name: "IMULQ/src=CX/dst=DI/arg=0", n: &nodeImpl{instruction: IMULQ, srcReg: RegCX, dstReg: RegDI, arg: 0x0}, exp: []byte{0x48, 0xf, 0xaf, 0xf9}},
 		{name: "ORL/src=AX/dst=AX/arg=0", n: &nodeImpl{instruction: ORL, srcReg: RegAX, dstReg: RegAX, arg: 0x0}, exp: []byte{0x9, 0xc0}},
 		{name: "ORL/src=AX/dst=R8/arg=0", n: &nodeImpl{instruction: ORL, srcReg: RegAX, dstReg: RegR8, arg: 0x0}, exp: []byte{0x41, 0x9, 0xc0}},
 		{name: "ORL/src=R8/dst=AX/arg=0", n: &nodeImpl{instruction: ORL, srcReg: RegR8, dstReg: RegAX, arg: 0x0}, exp: []byte{0x44, 0x9, 0xc0}},
diff --git a/internal/engine/compiler/compiler_bench_test.go b/internal/engine/compiler/compiler_bench_test.go
index 894e497f..d9c623a0 100644
--- a/internal/engine/compiler/compiler_bench_test.go
+++ b/internal/engine/compiler/compiler_bench_test.go
@@ -63,6 +63,52 @@ func BenchmarkCompiler_compileMemoryCopy(b *testing.B) {
 	}
 }
 
+func BenchmarkCompiler_compileMemoryFill(b *testing.B) {
+	sizes := []uint32{5, 17, 128, 10000, 64000}
+
+	for _, size := range sizes {
+		b.Run(fmt.Sprintf("%v", size), func(b *testing.B) {
+			env := newCompilerEnvironment()
+
+			mem := env.memory()
+			testMem := make([]byte, len(mem))
+			for i := 0; i < len(mem); i++ {
+				mem[i] = byte(i)
+				testMem[i] = byte(i)
+			}
+
+			compiler, _ := newCompiler(&wazeroir.CompilationResult{HasMemory: true, Signature: &wasm.FunctionType{}})
+			err := compiler.compilePreamble()
+			requireNoError(b, err)
+
+			var startOffset uint32 = 100
+			var value uint8 = 5
+
+			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: startOffset})
+			requireNoError(b, err)
+			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(value)})
+			requireNoError(b, err)
+			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: size})
+			requireNoError(b, err)
+			err = compiler.compileMemoryFill()
+			requireNoError(b, err)
+			err = compiler.(compilerImpl).compileReturnFunction()
+			requireNoError(b, err)
+			code, _, err := compiler.compile()
+			requireNoError(b, err)
+
+			env.execBench(b, code)
+
+			for i := startOffset; i < startOffset+size; i++ {
+				testMem[i] = value
+			}
+			if !bytes.Equal(mem, testMem) {
+				b.FailNow()
+			}
+		})
+	}
+}
+
 func (j *compilerEnv) execBench(b *testing.B, codeSegment []byte) {
 	f := j.newFunctionFrame(codeSegment)
 
diff --git a/internal/engine/compiler/compiler_post1_0_test.go b/internal/engine/compiler/compiler_post1_0_test.go
index 8e546d3f..baedbb81 100644
--- a/internal/engine/compiler/compiler_post1_0_test.go
+++ b/internal/engine/compiler/compiler_post1_0_test.go
@@ -252,10 +252,15 @@ func TestCompiler_compileMemoryFill(t *testing.T) {
 		size                    uint32
 		requireOutOfBoundsError bool
 	}{
-		{v: 0, destOffset: 10, size: 10},
+		{v: 0, destOffset: 0, size: 25},
+		{v: 0, destOffset: 10, size: 17},
+		{v: 0, destOffset: 10, size: 15},
 		{v: 0, destOffset: 10, size: 5},
 		{v: 0, destOffset: 10, size: 1},
 		{v: 0, destOffset: 10, size: 0},
+		{v: 5, destOffset: 10, size: 27},
+		{v: 5, destOffset: 10, size: 25},
+		{v: 5, destOffset: 10, size: 21},
 		{v: 5, destOffset: 10, size: 10},
 		{v: 5, destOffset: 10, size: 5},
 		{v: 5, destOffset: 10, size: 1},
diff --git a/internal/sys/sys_test.go b/internal/sys/sys_test.go
index b4fc8b5c..1779dd36 100644
--- a/internal/sys/sys_test.go
+++ b/internal/sys/sys_test.go
@@ -3,7 +3,6 @@ package sys
 import (
 	"bytes"
 	"context"
-	"crypto/rand"
 	"io"
 	"testing"
 	"time"
@@ -55,7 +54,7 @@ func TestDefaultSysContext(t *testing.T) {
 	require.Zero(t, sysCtx.Nanotime(testCtx)) // See above on functions.
 	require.Equal(t, sys.ClockResolution(1), sysCtx.NanotimeResolution())
 	require.Equal(t, &ns, sysCtx.nanosleep)
-	require.Equal(t, rand.Reader, sysCtx.RandSource())
+	require.Equal(t, platform.NewFakeRandSource(), sysCtx.RandSource())
 	require.Equal(t, NewFSContext(testfs.FS{}), sysCtx.FS(testCtx))
 }
 
diff --git a/internal/testing/enginetest/enginetest.go b/internal/testing/enginetest/enginetest.go
index 08283031..f5777983 100644
--- a/internal/testing/enginetest/enginetest.go
+++ b/internal/testing/enginetest/enginetest.go
@@ -790,15 +790,22 @@ func setupCallMemTests(t *testing.T, e wasm.Engine, readMem *wasm.Code, fnlf exp
 
 	callReadMem := &wasm.Code{ // shows indirect calls still use the same memory
 		IsHostFunction: true,
-		Body:           []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd},
+		Body: []byte{
+			wasm.OpcodeCall, 1,
+			// On the return from the another host function,
+			// we should still be able to access the memory.
+			wasm.OpcodeI32Const, 0,
+			wasm.OpcodeI32Load, 0x2, 0x0,
+			wasm.OpcodeEnd,
+		},
 	}
 	hostModule := &wasm.Module{
 		TypeSection:     []*wasm.FunctionType{ft},
 		FunctionSection: []wasm.Index{0, 0},
-		CodeSection:     []*wasm.Code{readMem, callReadMem},
+		CodeSection:     []*wasm.Code{callReadMem, readMem},
 		ExportSection: []*wasm.Export{
-			{Name: readMemName, Type: wasm.ExternTypeFunc, Index: 0},
-			{Name: callReadMemName, Type: wasm.ExternTypeFunc, Index: 1},
+			{Name: callReadMemName, Type: wasm.ExternTypeFunc, Index: 0},
+			{Name: readMemName, Type: wasm.ExternTypeFunc, Index: 1},
 		},
 		NameSection: &wasm.NameSection{
 			ModuleName:    "host",
@@ -832,8 +839,8 @@ func setupCallMemTests(t *testing.T, e wasm.Engine, readMem *wasm.Code, fnlf exp
 			{Name: callImportCallReadMemName, Type: wasm.ExternTypeFunc, Index: 3},
 		},
 		CodeSection: []*wasm.Code{
-			{Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Calling the index 0 = readMemFn.
-			{Body: []byte{wasm.OpcodeCall, 1, wasm.OpcodeEnd}}, // Calling the index 1 = callReadMemFn.
+			{Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Calling the index 0 = callReadMemFn.
+			{Body: []byte{wasm.OpcodeCall, 1, wasm.OpcodeEnd}}, // Calling the index 1 = readMemFn.
 		},
 		NameSection: &wasm.NameSection{
 			ModuleName: "importing",
@@ -854,7 +861,7 @@ func setupCallMemTests(t *testing.T, e wasm.Engine, readMem *wasm.Code, fnlf exp
 	importing := &wasm.ModuleInstance{Name: importingModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}}
 	importingFunctions := importing.BuildFunctions(importingModule, buildListeners(fnlf, importingModule))
 	// Note: adds imported functions readMemFn and callReadMemFn at index 0 and 1.
-	importing.Functions = append([]*wasm.FunctionInstance{readMemFn, callReadMemFn}, importingFunctions...)
+	importing.Functions = append([]*wasm.FunctionInstance{callReadMemFn, readMemFn}, importingFunctions...)
 	importing.BuildExports(importingModule.ExportSection)
 
 	// Compile the importing module
diff --git a/wasi_snapshot_preview1/fs_test.go b/wasi_snapshot_preview1/fs_test.go
index 852c1447..fa15b9a4 100644
--- a/wasi_snapshot_preview1/fs_test.go
+++ b/wasi_snapshot_preview1/fs_test.go
@@ -657,8 +657,8 @@ func Test_fdSeek(t *testing.T) {
 			whence:         io.SeekStart,
 			expectedOffset: 4, // = offset
 			expectedMemory: []byte{
-				'?',        // resultNewoffset is after this
-				4, 0, 0, 0, // = expectedOffset
+				'?',                    // resultNewoffset is after this
+				4, 0, 0, 0, 0, 0, 0, 0, // = expectedOffset
 				'?',
 			},
 			expectedLog: `
@@ -674,8 +674,8 @@ func Test_fdSeek(t *testing.T) {
 			whence:         io.SeekCurrent,
 			expectedOffset: 2, // = 1 (the initial offset of the test file) + 1 (offset)
 			expectedMemory: []byte{
-				'?',        // resultNewoffset is after this
-				2, 0, 0, 0, // = expectedOffset
+				'?',                    // resultNewoffset is after this
+				2, 0, 0, 0, 0, 0, 0, 0, // = expectedOffset
 				'?',
 			},
 			expectedLog: `
@@ -691,8 +691,8 @@ func Test_fdSeek(t *testing.T) {
 			whence:         io.SeekEnd,
 			expectedOffset: 5, // = 6 (the size of the test file with content "wazero") + -1 (offset)
 			expectedMemory: []byte{
-				'?',        // resultNewoffset is after this
-				5, 0, 0, 0, // = expectedOffset
+				'?',                    // resultNewoffset is after this
+				5, 0, 0, 0, 0, 0, 0, 0, // = expectedOffset
 				'?',
 			},
 			expectedLog: `
diff --git a/wasi_snapshot_preview1/random_test.go b/wasi_snapshot_preview1/random_test.go
index ee778461..53b42147 100644
--- a/wasi_snapshot_preview1/random_test.go
+++ b/wasi_snapshot_preview1/random_test.go
@@ -12,8 +12,7 @@ import (
 )
 
 func Test_randomGet(t *testing.T) {
-	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().
-		WithRandSource(deterministicRandomSource()))
+	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig())
 	defer r.Close(testCtx)
 
 	expectedMemory := []byte{
@@ -42,8 +41,7 @@ func Test_randomGet(t *testing.T) {
 }
 
 func Test_randomGet_Errors(t *testing.T) {
-	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().
-		WithRandSource(deterministicRandomSource()))
+	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig())
 	defer r.Close(testCtx)
 
 	memorySize := mod.Memory().Size(testCtx)
diff --git a/wasi_snapshot_preview1/wasi_test.go b/wasi_snapshot_preview1/wasi_test.go
index 7f990edb..02d1843c 100644
--- a/wasi_snapshot_preview1/wasi_test.go
+++ b/wasi_snapshot_preview1/wasi_test.go
@@ -4,8 +4,6 @@ import (
 	"bytes"
 	"context"
 	_ "embed"
-	"io"
-	"math/rand"
 	"testing"
 
 	"github.com/tetratelabs/wazero"
@@ -16,12 +14,6 @@ import (
 	"github.com/tetratelabs/wazero/internal/testing/require"
 )
 
-const seed = int64(42) // fixed seed value
-
-var deterministicRandomSource = func() io.Reader {
-	return rand.New(rand.NewSource(seed))
-}
-
 // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
 var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")