Repository: tetratelabs/wazero
Pull request #2277 (2024-07-02 19:51:56)

ci: tests with Go 1.23rc2

mathetake: GOOS=wasip1 os package on Windows is failing 😉 

Patch:
diff --git a/.github/workflows/commit.yaml b/.github/workflows/commit.yaml
index f3b4922c..a31fe231 100644
--- a/.github/workflows/commit.yaml
+++ b/.github/workflows/commit.yaml
@@ -71,6 +71,7 @@ jobs:
         - os: macos-14
           arch: arm64
         go-version:
+          - "1.23.0-rc.2"  # Test the pending next version
           - "1.22"  # Current Go version
           - "1.20"  # Floor Go version of wazero (current - 2)
 
@@ -112,6 +113,7 @@ jobs:
       fail-fast: false  # don't fail fast as sometimes failures are arch/OS specific
       matrix:  # Use versions consistent with wazero's Go support policy.
         go-version:
+          - "1.23.0-rc.2"  # Test the pending next version
           - "1.22"  # Current Go version
           - "1.20"  # Floor Go version of wazero (current - 2)
         arch:
diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml
index 395e2d94..61c5158e 100644
--- a/.github/workflows/integration.yaml
+++ b/.github/workflows/integration.yaml
@@ -278,6 +278,7 @@ jobs:
         go-version:
           - "1.21"
           - "1.22"
+          - "1.23.0-rc.2"  # Test the pending next version
 
     steps:
       - id: setup-go
diff --git a/Makefile b/Makefile
index d2f9a51c..0b1663f4 100644
--- a/Makefile
+++ b/Makefile
@@ -187,7 +187,7 @@ golangci_lint_goarch ?= $(shell go env GOARCH)
 
 .PHONY: lint
 lint: $(golangci_lint_path)
-	@GOARCH=$(golangci_lint_goarch) CGO_ENABLED=0 $(golangci_lint_path) run --timeout 5m
+	@GOARCH=$(golangci_lint_goarch) CGO_ENABLED=0 $(golangci_lint_path) run --timeout 5m -E testableexamples
 
 .PHONY: format
 format:
diff --git a/imports/wasi_snapshot_preview1/fs.go b/imports/wasi_snapshot_preview1/fs.go
index 1ec0d81b..150f75cc 100644
--- a/imports/wasi_snapshot_preview1/fs.go
+++ b/imports/wasi_snapshot_preview1/fs.go
@@ -1962,17 +1962,11 @@ func pathSymlinkFn(_ context.Context, mod api.Module, params []uint64) experimen
 	return dir.FS.Symlink(
 		// Do not join old path since it's only resolved when dereference the link created here.
 		// And the dereference result depends on the opening directory's file descriptor at that point.
-		bufToStr(oldPathBuf),
+		unsafe.String(&oldPathBuf[0], int(oldPathLen)),
 		newPathName,
 	)
 }
 
-// bufToStr converts the given byte slice as string unsafely.
-func bufToStr(buf []byte) string {
-	// TODO: use unsafe.String after flooring Go 1.20.
-	return *(*string)(unsafe.Pointer(&buf))
-}
-
 // pathUnlinkFile is the WASI function named PathUnlinkFileName which unlinks a
 // file.
 //
diff --git a/internal/engine/wazevo/backend/compiler.go b/internal/engine/wazevo/backend/compiler.go
index 59bbfe02..62d36501 100644
--- a/internal/engine/wazevo/backend/compiler.go
+++ b/internal/engine/wazevo/backend/compiler.go
@@ -69,7 +69,7 @@ type Compiler interface {
 	AllocateVReg(typ ssa.Type) regalloc.VReg
 
 	// ValueDefinition returns the definition of the given value.
-	ValueDefinition(ssa.Value) *SSAValueDefinition
+	ValueDefinition(ssa.Value) SSAValueDefinition
 
 	// VRegOf returns the virtual register of the given ssa.Value.
 	VRegOf(value ssa.Value) regalloc.VReg
@@ -79,13 +79,13 @@ type Compiler interface {
 
 	// MatchInstr returns true if the given definition is from an instruction with the given opcode, the current group ID,
 	// and a refcount of 1. That means, the instruction can be merged/swapped within the current instruction group.
-	MatchInstr(def *SSAValueDefinition, opcode ssa.Opcode) bool
+	MatchInstr(def SSAValueDefinition, opcode ssa.Opcode) bool
 
 	// MatchInstrOneOf is the same as MatchInstr but for multiple opcodes. If it matches one of ssa.Opcode,
 	// this returns the opcode. Otherwise, this returns ssa.OpcodeInvalid.
 	//
 	// Note: caller should be careful to avoid excessive allocation on opcodes slice.
-	MatchInstrOneOf(def *SSAValueDefinition, opcodes []ssa.Opcode) ssa.Opcode
+	MatchInstrOneOf(def SSAValueDefinition, opcodes []ssa.Opcode) ssa.Opcode
 
 	// AddRelocationInfo appends the relocation information for the function reference at the current buffer offset.
 	AddRelocationInfo(funcRef ssa.FuncRef)
@@ -126,10 +126,7 @@ type compiler struct {
 	nextVRegID regalloc.VRegID
 	// ssaValueToVRegs maps ssa.ValueID to regalloc.VReg.
 	ssaValueToVRegs [] /* VRegID to */ regalloc.VReg
-	// ssaValueDefinitions maps ssa.ValueID to its definition.
-	ssaValueDefinitions []SSAValueDefinition
-	// ssaValueRefCounts is a cached list obtained by ssa.Builder.ValueRefCounts().
-	ssaValueRefCounts []int
+	ssaValuesInfo   []ssa.ValueInfo
 	// returnVRegs is the list of virtual registers that store the return values.
 	returnVRegs  []regalloc.VReg
 	varEdges     [][2]regalloc.VReg
@@ -206,15 +203,10 @@ func (c *compiler) setCurrentGroupID(gid ssa.InstructionGroupID) {
 // assignVirtualRegisters assigns a virtual register to each ssa.ValueID Valid in the ssa.Builder.
 func (c *compiler) assignVirtualRegisters() {
 	builder := c.ssaBuilder
-	refCounts := builder.ValueRefCounts()
-	c.ssaValueRefCounts = refCounts
+	c.ssaValuesInfo = builder.ValuesInfo()
 
-	need := len(refCounts)
-	if need >= len(c.ssaValueToVRegs) {
-		c.ssaValueToVRegs = append(c.ssaValueToVRegs, make([]regalloc.VReg, need+1)...)
-	}
-	if need >= len(c.ssaValueDefinitions) {
-		c.ssaValueDefinitions = append(c.ssaValueDefinitions, make([]SSAValueDefinition, need+1)...)
+	if diff := len(c.ssaValuesInfo) - len(c.ssaValueToVRegs); diff > 0 {
+		c.ssaValueToVRegs = append(c.ssaValueToVRegs, make([]regalloc.VReg, diff+1)...)
 	}
 
 	for blk := builder.BlockIteratorReversePostOrderBegin(); blk != nil; blk = builder.BlockIteratorReversePostOrderNext() {
@@ -225,40 +217,26 @@ func (c *compiler) assignVirtualRegisters() {
 			typ := p.Type()
 			vreg := c.AllocateVReg(typ)
 			c.ssaValueToVRegs[pid] = vreg
-			c.ssaValueDefinitions[pid] = SSAValueDefinition{BlockParamValue: p, BlkParamVReg: vreg}
 			c.ssaTypeOfVRegID[vreg.ID()] = p.Type()
 		}
 
 		// Assigns each value to a virtual register produced by instructions.
 		for cur := blk.Root(); cur != nil; cur = cur.Next() {
 			r, rs := cur.Returns()
-			var N int
 			if r.Valid() {
 				id := r.ID()
 				ssaTyp := r.Type()
 				typ := r.Type()
 				vReg := c.AllocateVReg(typ)
 				c.ssaValueToVRegs[id] = vReg
-				c.ssaValueDefinitions[id] = SSAValueDefinition{
-					Instr:    cur,
-					N:        0,
-					RefCount: refCounts[id],
-				}
 				c.ssaTypeOfVRegID[vReg.ID()] = ssaTyp
-				N++
 			}
 			for _, r := range rs {
 				id := r.ID()
 				ssaTyp := r.Type()
 				vReg := c.AllocateVReg(ssaTyp)
 				c.ssaValueToVRegs[id] = vReg
-				c.ssaValueDefinitions[id] = SSAValueDefinition{
-					Instr:    cur,
-					N:        N,
-					RefCount: refCounts[id],
-				}
 				c.ssaTypeOfVRegID[vReg.ID()] = ssaTyp
-				N++
 			}
 		}
 	}
@@ -299,8 +277,12 @@ func (c *compiler) Init() {
 }
 
 // ValueDefinition implements Compiler.ValueDefinition.
-func (c *compiler) ValueDefinition(value ssa.Value) *SSAValueDefinition {
-	return &c.ssaValueDefinitions[value.ID()]
+func (c *compiler) ValueDefinition(value ssa.Value) SSAValueDefinition {
+	return SSAValueDefinition{
+		V:        value,
+		Instr:    c.ssaBuilder.InstructionOfValue(value),
+		RefCount: c.ssaValuesInfo[value.ID()].RefCount,
+	}
 }
 
 // VRegOf implements Compiler.VRegOf.
@@ -319,7 +301,7 @@ func (c *compiler) TypeOf(v regalloc.VReg) ssa.Type {
 }
 
 // MatchInstr implements Compiler.MatchInstr.
-func (c *compiler) MatchInstr(def *SSAValueDefinition, opcode ssa.Opcode) bool {
+func (c *compiler) MatchInstr(def SSAValueDefinition, opcode ssa.Opcode) bool {
 	instr := def.Instr
 	return def.IsFromInstr() &&
 		instr.Opcode() == opcode &&
@@ -328,7 +310,7 @@ func (c *compiler) MatchInstr(def *SSAValueDefinition, opcode ssa.Opcode) bool {
 }
 
 // MatchInstrOneOf implements Compiler.MatchInstrOneOf.
-func (c *compiler) MatchInstrOneOf(def *SSAValueDefinition, opcodes []ssa.Opcode) ssa.Opcode {
+func (c *compiler) MatchInstrOneOf(def SSAValueDefinition, opcodes []ssa.Opcode) ssa.Opcode {
 	instr := def.Instr
 	if !def.IsFromInstr() {
 		return ssa.OpcodeInvalid
diff --git a/internal/engine/wazevo/backend/compiler_lower.go b/internal/engine/wazevo/backend/compiler_lower.go
index 9a9414ae..735cfa3d 100644
--- a/internal/engine/wazevo/backend/compiler_lower.go
+++ b/internal/engine/wazevo/backend/compiler_lower.go
@@ -124,9 +124,10 @@ func (c *compiler) lowerFunctionArguments(entry ssa.BasicBlock) {
 	mach := c.mach
 
 	c.tmpVals = c.tmpVals[:0]
+	data := c.ssaBuilder.ValuesInfo()
 	for i := 0; i < entry.Params(); i++ {
 		p := entry.Param(i)
-		if c.ssaValueRefCounts[p.ID()] > 0 {
+		if data[p.ID()].RefCount > 0 {
 			c.tmpVals = append(c.tmpVals, p)
 		} else {
 			// If the argument is not used, we can just pass an invalid value.
@@ -151,12 +152,12 @@ func (c *compiler) lowerBlockArguments(args []ssa.Value, succ ssa.BasicBlock) {
 		src := args[i]
 
 		dstReg := c.VRegOf(dst)
-		srcDef := c.ssaValueDefinitions[src.ID()]
-		if srcDef.IsFromInstr() && srcDef.Instr.Constant() {
+		srcInstr := c.ssaBuilder.InstructionOfValue(src)
+		if srcInstr != nil && srcInstr.Constant() {
 			c.constEdges = append(c.constEdges, struct {
 				cInst *ssa.Instruction
 				dst   regalloc.VReg
-			}{cInst: srcDef.Instr, dst: dstReg})
+			}{cInst: srcInstr, dst: dstReg})
 		} else {
 			srcReg := c.VRegOf(src)
 			// Even when the src=dst, insert the move so that we can keep such registers keep-alive.
diff --git a/internal/engine/wazevo/backend/isa/amd64/lower_mem.go b/internal/engine/wazevo/backend/isa/amd64/lower_mem.go
index bee673d2..befe8c64 100644
--- a/internal/engine/wazevo/backend/isa/amd64/lower_mem.go
+++ b/internal/engine/wazevo/backend/isa/amd64/lower_mem.go
@@ -130,9 +130,9 @@ func (m *machine) lowerAddendsToAmode(x, y addend, offBase uint32) *amode {
 	}
 }
 
-func (m *machine) lowerAddend(x *backend.SSAValueDefinition) addend {
-	if x.IsFromBlockParam() {
-		return addend{x.BlkParamVReg, 0, 0}
+func (m *machine) lowerAddend(x backend.SSAValueDefinition) addend {
+	if !x.IsFromInstr() {
+		return addend{m.c.VRegOf(x.V), 0, 0}
 	}
 	// Ensure the addend is not referenced in multiple places; we will discard nested Iadds.
 	op := m.c.MatchInstrOneOf(x, addendsMatchOpcodes[:])
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine.go b/internal/engine/wazevo/backend/isa/amd64/machine.go
index 476b4f16..aeeb6b64 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine.go
@@ -1601,21 +1601,24 @@ func (m *machine) lowerExitIfTrueWithCode(execCtx regalloc.VReg, cond ssa.Value,
 	jmpIf.asJmpIf(condFromSSAIntCmpCond(c).invert(), newOperandLabel(l))
 }
 
-func (m *machine) tryLowerBandToFlag(x, y *backend.SSAValueDefinition) (ok bool) {
-	var target *backend.SSAValueDefinition
+func (m *machine) tryLowerBandToFlag(x, y backend.SSAValueDefinition) (ok bool) {
+	var target backend.SSAValueDefinition
+	var got bool
 	if x.IsFromInstr() && x.Instr.Constant() && x.Instr.ConstantVal() == 0 {
 		if m.c.MatchInstr(y, ssa.OpcodeBand) {
 			target = y
+			got = true
 		}
 	}
 
 	if y.IsFromInstr() && y.Instr.Constant() && y.Instr.ConstantVal() == 0 {
 		if m.c.MatchInstr(x, ssa.OpcodeBand) {
 			target = x
+			got = true
 		}
 	}
 
-	if target == nil {
+	if !got {
 		return false
 	}
 
@@ -1947,9 +1950,9 @@ func (m *machine) lowerCall(si *ssa.Instruction) {
 
 // callerGenVRegToFunctionArg is the opposite of GenFunctionArgToVReg, which is used to generate the
 // caller side of the function call.
-func (m *machine) callerGenVRegToFunctionArg(a *backend.FunctionABI, argIndex int, reg regalloc.VReg, def *backend.SSAValueDefinition, stackSlotSize int64) {
+func (m *machine) callerGenVRegToFunctionArg(a *backend.FunctionABI, argIndex int, reg regalloc.VReg, def backend.SSAValueDefinition, stackSlotSize int64) {
 	arg := &a.Args[argIndex]
-	if def != nil && def.IsFromInstr() {
+	if def.IsFromInstr() {
 		// Constant instructions are inlined.
 		if inst := def.Instr; inst.Constant() {
 			m.insertLoadConstant(inst, reg)
@@ -2210,7 +2213,7 @@ func (m *machine) ResolveRelocations(refToBinaryOffset []int, binary []byte, rel
 // CallTrampolineIslandInfo implements backend.Machine CallTrampolineIslandInfo.
 func (m *machine) CallTrampolineIslandInfo(_ int) (_, _ int, _ error) { return }
 
-func (m *machine) lowerIcmpToFlag(xd, yd *backend.SSAValueDefinition, _64 bool) {
+func (m *machine) lowerIcmpToFlag(xd, yd backend.SSAValueDefinition, _64 bool) {
 	x := m.getOperand_Reg(xd)
 	y := m.getOperand_Mem_Imm32_Reg(yd)
 	cmp := m.allocateInstr().asCmpRmiR(true, y, x.reg(), _64)
diff --git a/internal/engine/wazevo/backend/isa/amd64/operands.go b/internal/engine/wazevo/backend/isa/amd64/operands.go
index 10b727e7..78797568 100644
--- a/internal/engine/wazevo/backend/isa/amd64/operands.go
+++ b/internal/engine/wazevo/backend/isa/amd64/operands.go
@@ -252,12 +252,12 @@ func (a *amode) String() string {
 	}
 }
 
-func (m *machine) getOperand_Mem_Reg(def *backend.SSAValueDefinition) (op operand) {
-	if def.IsFromBlockParam() {
-		return newOperandReg(def.BlkParamVReg)
+func (m *machine) getOperand_Mem_Reg(def backend.SSAValueDefinition) (op operand) {
+	if !def.IsFromInstr() {
+		return newOperandReg(m.c.VRegOf(def.V))
 	}
 
-	if def.SSAValue().Type() == ssa.TypeV128 {
+	if def.V.Type() == ssa.TypeV128 {
 		// SIMD instructions require strict memory alignment, so we don't support the memory operand for V128 at the moment.
 		return m.getOperand_Reg(def)
 	}
@@ -272,9 +272,9 @@ func (m *machine) getOperand_Mem_Reg(def *backend.SSAValueDefinition) (op operan
 	return m.getOperand_Reg(def)
 }
 
-func (m *machine) getOperand_Mem_Imm32_Reg(def *backend.SSAValueDefinition) (op operand) {
-	if def.IsFromBlockParam() {
-		return newOperandReg(def.BlkParamVReg)
+func (m *machine) getOperand_Mem_Imm32_Reg(def backend.SSAValueDefinition) (op operand) {
+	if !def.IsFromInstr() {
+		return newOperandReg(m.c.VRegOf(def.V))
 	}
 
 	if m.c.MatchInstr(def, ssa.OpcodeLoad) {
@@ -287,9 +287,9 @@ func (m *machine) getOperand_Mem_Imm32_Reg(def *backend.SSAValueDefinition) (op
 	return m.getOperand_Imm32_Reg(def)
 }
 
-func (m *machine) getOperand_Imm32_Reg(def *backend.SSAValueDefinition) (op operand) {
-	if def.IsFromBlockParam() {
-		return newOperandReg(def.BlkParamVReg)
+func (m *machine) getOperand_Imm32_Reg(def backend.SSAValueDefinition) (op operand) {
+	if !def.IsFromInstr() {
+		return newOperandReg(m.c.VRegOf(def.V))
 	}
 
 	instr := def.Instr
@@ -323,24 +323,14 @@ func asImm32(val uint64, allowSignExt bool) (uint32, bool) {
 	return u32val, true
 }
 
-func (m *machine) getOperand_Reg(def *backend.SSAValueDefinition) (op operand) {
+func (m *machine) getOperand_Reg(def backend.SSAValueDefinition) (op operand) {
 	var v regalloc.VReg
-	if def.IsFromBlockParam() {
-		v = def.BlkParamVReg
+	if instr := def.Instr; instr != nil && instr.Constant() {
+		// We inline all the constant instructions so that we could reduce the register usage.
+		v = m.lowerConstant(instr)
+		instr.MarkLowered()
 	} else {
-		instr := def.Instr
-		if instr.Constant() {
-			// We inline all the constant instructions so that we could reduce the register usage.
-			v = m.lowerConstant(instr)
-			instr.MarkLowered()
-		} else {
-			if n := def.N; n == 0 {
-				v = m.c.VRegOf(instr.Return())
-			} else {
-				_, rs := instr.Returns()
-				v = m.c.VRegOf(rs[n-1])
-			}
-		}
+		v = m.c.VRegOf(def.V)
 	}
 	return newOperandReg(v)
 }
diff --git a/internal/engine/wazevo/backend/isa/arm64/abi.go b/internal/engine/wazevo/backend/isa/arm64/abi.go
index c5e24a57..d1eaa7cd 100644
--- a/internal/engine/wazevo/backend/isa/arm64/abi.go
+++ b/internal/engine/wazevo/backend/isa/arm64/abi.go
@@ -182,9 +182,9 @@ func (m *machine) LowerReturns(rets []ssa.Value) {
 
 // callerGenVRegToFunctionArg is the opposite of GenFunctionArgToVReg, which is used to generate the
 // caller side of the function call.
-func (m *machine) callerGenVRegToFunctionArg(a *backend.FunctionABI, argIndex int, reg regalloc.VReg, def *backend.SSAValueDefinition, slotBegin int64) {
+func (m *machine) callerGenVRegToFunctionArg(a *backend.FunctionABI, argIndex int, reg regalloc.VReg, def backend.SSAValueDefinition, slotBegin int64) {
 	arg := &a.Args[argIndex]
-	if def != nil && def.IsFromInstr() {
+	if def.IsFromInstr() {
 		// Constant instructions are inlined.
 		if inst := def.Instr; inst.Constant() {
 			val := inst.Return()
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr_operands.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr_operands.go
index d9fbf178..7a398c3d 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr_operands.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr_operands.go
@@ -162,9 +162,9 @@ func (o operand) assignReg(v regalloc.VReg) operand {
 //
 // `mode` is used to extend the operand if the bit length is smaller than mode.bits().
 // If the operand can be expressed as operandKindImm12, `mode` is ignored.
-func (m *machine) getOperand_Imm12_ER_SR_NR(def *backend.SSAValueDefinition, mode extMode) (op operand) {
-	if def.IsFromBlockParam() {
-		return operandNR(def.BlkParamVReg)
+func (m *machine) getOperand_Imm12_ER_SR_NR(def backend.SSAValueDefinition, mode extMode) (op operand) {
+	if !def.IsFromInstr() {
+		return operandNR(m.compiler.VRegOf(def.V))
 	}
 
 	instr := def.Instr
@@ -179,9 +179,9 @@ func (m *machine) getOperand_Imm12_ER_SR_NR(def *backend.SSAValueDefinition, mod
 
 // getOperand_MaybeNegatedImm12_ER_SR_NR is almost the same as getOperand_Imm12_ER_SR_NR, but this might negate the immediate value.
 // If the immediate value is negated, the second return value is true, otherwise always false.
-func (m *machine) getOperand_MaybeNegatedImm12_ER_SR_NR(def *backend.SSAValueDefinition, mode extMode) (op operand, negatedImm12 bool) {
-	if def.IsFromBlockParam() {
-		return operandNR(def.BlkParamVReg), false
+func (m *machine) getOperand_MaybeNegatedImm12_ER_SR_NR(def backend.SSAValueDefinition, mode extMode) (op operand, negatedImm12 bool) {
+	if !def.IsFromInstr() {
+		return operandNR(m.compiler.VRegOf(def.V)), false
 	}
 
 	instr := def.Instr
@@ -193,7 +193,7 @@ func (m *machine) getOperand_MaybeNegatedImm12_ER_SR_NR(def *backend.SSAValueDef
 		}
 
 		signExtended := int64(c)
-		if def.SSAValue().Type().Bits() == 32 {
+		if def.V.Type().Bits() == 32 {
 			signExtended = (signExtended << 32) >> 32
 		}
 		negatedWithoutSign := -signExtended
@@ -208,9 +208,9 @@ func (m *machine) getOperand_MaybeNegatedImm12_ER_SR_NR(def *backend.SSAValueDef
 // ensureValueNR returns an operand of either operandKindER, operandKindSR, or operandKindNR from the given value (defined by `def).
 //
 // `mode` is used to extend the operand if the bit length is smaller than mode.bits().
-func (m *machine) getOperand_ER_SR_NR(def *backend.SSAValueDefinition, mode extMode) (op operand) {
-	if def.IsFromBlockParam() {
-		return operandNR(def.BlkParamVReg)
+func (m *machine) getOperand_ER_SR_NR(def backend.SSAValueDefinition, mode extMode) (op operand) {
+	if !def.IsFromInstr() {
+		return operandNR(m.compiler.VRegOf(def.V))
 	}
 
 	if m.compiler.MatchInstr(def, ssa.OpcodeSExtend) || m.compiler.MatchInstr(def, ssa.OpcodeUExtend) {
@@ -251,9 +251,9 @@ func (m *machine) getOperand_ER_SR_NR(def *backend.SSAValueDefinition, mode extM
 // ensureValueNR returns an operand of either operandKindSR or operandKindNR from the given value (defined by `def).
 //
 // `mode` is used to extend the operand if the bit length is smaller than mode.bits().
-func (m *machine) getOperand_SR_NR(def *backend.SSAValueDefinition, mode extMode) (op operand) {
-	if def.IsFromBlockParam() {
-		return operandNR(def.BlkParamVReg)
+func (m *machine) getOperand_SR_NR(def backend.SSAValueDefinition, mode extMode) (op operand) {
+	if !def.IsFromInstr() {
+		return operandNR(m.compiler.VRegOf(def.V))
 	}
 
 	if m.compiler.MatchInstr(def, ssa.OpcodeIshl) {
@@ -273,9 +273,9 @@ func (m *machine) getOperand_SR_NR(def *backend.SSAValueDefinition, mode extMode
 }
 
 // getOperand_ShiftImm_NR returns an operand of either operandKindShiftImm or operandKindNR from the given value (defined by `def).
-func (m *machine) getOperand_ShiftImm_NR(def *backend.SSAValueDefinition, mode extMode, shiftBitWidth byte) (op operand) {
-	if def.IsFromBlockParam() {
-		return operandNR(def.BlkParamVReg)
+func (m *machine) getOperand_ShiftImm_NR(def backend.SSAValueDefinition, mode extMode, shiftBitWidth byte) (op operand) {
+	if !def.IsFromInstr() {
+		return operandNR(m.compiler.VRegOf(def.V))
 	}
 
 	instr := def.Instr
@@ -289,28 +289,18 @@ func (m *machine) getOperand_ShiftImm_NR(def *backend.SSAValueDefinition, mode e
 // ensureValueNR returns an operand of operandKindNR from the given value (defined by `def).
 //
 // `mode` is used to extend the operand if the bit length is smaller than mode.bits().
-func (m *machine) getOperand_NR(def *backend.SSAValueDefinition, mode extMode) (op operand) {
+func (m *machine) getOperand_NR(def backend.SSAValueDefinition, mode extMode) (op operand) {
 	var v regalloc.VReg
-	if def.IsFromBlockParam() {
-		v = def.BlkParamVReg
+	if def.IsFromInstr() && def.Instr.Constant() {
+		// We inline all the constant instructions so that we could reduce the register usage.
+		v = m.lowerConstant(def.Instr)
+		def.Instr.MarkLowered()
 	} else {
-		instr := def.Instr
-		if instr.Constant() {
-			// We inline all the constant instructions so that we could reduce the register usage.
-			v = m.lowerConstant(instr)
-			instr.MarkLowered()
-		} else {
-			if n := def.N; n == 0 {
-				v = m.compiler.VRegOf(instr.Return())
-			} else {
-				_, rs := instr.Returns()
-				v = m.compiler.VRegOf(rs[n-1])
-			}
-		}
+		v = m.compiler.VRegOf(def.V)
 	}
 
 	r := v
-	switch inBits := def.SSAValue().Type().Bits(); {
+	switch inBits := def.V.Type().Bits(); {
 	case mode == extModeNone:
 	case inBits == 32 && (mode == extModeZeroExtend32 || mode == extModeSignExtend32):
 	case inBits == 32 && mode == extModeZeroExtend64:
diff --git a/internal/engine/wazevo/backend/vdef.go b/internal/engine/wazevo/backend/vdef.go
index edfa962b..47a275a3 100644
--- a/internal/engine/wazevo/backend/vdef.go
+++ b/internal/engine/wazevo/backend/vdef.go
@@ -1,43 +1,19 @@
 package backend
 
 import (
-	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
 )
 
 // SSAValueDefinition represents a definition of an SSA value.
 type SSAValueDefinition struct {
-	// BlockParamValue is valid if Instr == nil
-	BlockParamValue ssa.Value
-
-	// BlkParamVReg is valid if Instr == nil
-	BlkParamVReg regalloc.VReg
-
+	V ssa.Value
 	// Instr is not nil if this is a definition from an instruction.
 	Instr *ssa.Instruction
-	// N is the index of the return value in the instr's return values list.
-	N int
 	// RefCount is the number of references to the result.
-	RefCount int
+	RefCount uint32
 }
 
+// IsFromInstr returns true if this definition is from an instruction.
 func (d *SSAValueDefinition) IsFromInstr() bool {
 	return d.Instr != nil
 }
-
-func (d *SSAValueDefinition) IsFromBlockParam() bool {
-	return d.Instr == nil
-}
-
-func (d *SSAValueDefinition) SSAValue() ssa.Value {
-	if d.IsFromBlockParam() {
-		return d.BlockParamValue
-	} else {
-		r, rs := d.Instr.Returns()
-		if d.N == 0 {
-			return r
-		} else {
-			return rs[d.N-1]
-		}
-	}
-}
diff --git a/internal/engine/wazevo/ssa/builder.go b/internal/engine/wazevo/ssa/builder.go
index 227c46ab..43dd7d29 100644
--- a/internal/engine/wazevo/ssa/builder.go
+++ b/internal/engine/wazevo/ssa/builder.go
@@ -94,9 +94,9 @@ type Builder interface {
 	// Returns nil if there's no unseen BasicBlock.
 	BlockIteratorNext() BasicBlock
 
-	// ValueRefCounts returns the map of ValueID to its reference count.
-	// The returned slice must not be modified.
-	ValueRefCounts() []int
+	// ValuesInfo returns the data per Value used to lower the SSA in backend.
+	// This is indexed by ValueID.
+	ValuesInfo() []ValueInfo
 
 	// BlockIteratorReversePostOrderBegin is almost the same as BlockIteratorBegin except it returns the BasicBlock in the reverse post-order.
 	// This is available after RunPasses is run.
@@ -132,6 +132,9 @@ type Builder interface {
 
 	// BasicBlock returns the BasicBlock of the given ID.
 	BasicBlock(id BasicBlockID) BasicBlock
+
+	// InstructionOfValue returns the Instruction that produces the given Value or nil if the Value is not produced by any Instruction.
+	InstructionOfValue(v Value) *Instruction
 }
 
 // NewBuilder returns a new Builder implementation.
@@ -143,7 +146,6 @@ func NewBuilder() Builder {
 		varLengthPool:           wazevoapi.NewVarLengthPool[Value](),
 		valueAnnotations:        make(map[ValueID]string),
 		signatures:              make(map[SignatureID]*Signature),
-		valueIDAliases:          make(map[ValueID]Value),
 		returnBlk:               &basicBlock{id: basicBlockIDReturnBlock},
 	}
 }
@@ -161,19 +163,16 @@ type builder struct {
 	currentBB                     *basicBlock
 	returnBlk                     *basicBlock
 
-	// variables track the types for Variable with the index regarded Variable.
-	variables []Type
 	// nextValueID is used by builder.AllocateValue.
 	nextValueID ValueID
 	// nextVariable is used by builder.AllocateVariable.
 	nextVariable Variable
 
-	valueIDAliases   map[ValueID]Value
+	// valueAnnotations contains the annotations for each Value, only used for debugging.
 	valueAnnotations map[ValueID]string
 
-	// valueRefCounts is used to lower the SSA in backend, and will be calculated
-	// by the last SSA-level optimization pass.
-	valueRefCounts []int
+	// valuesInfo contains the data per Value used to lower the SSA in backend. This is indexed by ValueID.
+	valuesInfo []ValueInfo
 
 	// dominators stores the immediate dominator of each BasicBlock.
 	// The index is blockID of the BasicBlock.
@@ -186,11 +185,10 @@ type builder struct {
 	loopNestingForestRoots []BasicBlock
 
 	// The followings are used for optimization passes/deterministic compilation.
-	instStack            []*Instruction
-	valueIDToInstruction []*Instruction
-	blkStack             []*basicBlock
-	blkStack2            []*basicBlock
-	redundantParams      []redundantParam
+	instStack       []*Instruction
+	blkStack        []*basicBlock
+	blkStack2       []*basicBlock
+	redundantParams []redundantParam
 
 	// blockIterCur is used to implement blockIteratorBegin and blockIteratorNext.
 	blockIterCur int
@@ -208,6 +206,13 @@ type builder struct {
 	zeros [typeEnd]Value
 }
 
+// ValueInfo contains the data per Value used to lower the SSA in backend.
+type ValueInfo struct {
+	// RefCount is the reference count of the Value.
+	RefCount uint32
+	alias    Value
+}
+
 // redundantParam is a pair of the index of the redundant parameter and the Value.
 // This is used to eliminate the redundant parameters in the optimization pass.
 type redundantParam struct {
@@ -287,9 +292,7 @@ func (b *builder) Init(s *Signature) {
 
 	for v := ValueID(0); v < b.nextValueID; v++ {
 		delete(b.valueAnnotations, v)
-		delete(b.valueIDAliases, v)
-		b.valueRefCounts[v] = 0
-		b.valueIDToInstruction[v] = nil
+		b.valuesInfo[v] = ValueInfo{alias: ValueInvalid}
 	}
 	b.nextValueID = 0
 	b.reversePostOrderedBasicBlocks = b.reversePostOrderedBasicBlocks[:0]
@@ -395,7 +398,7 @@ func (b *builder) InsertInstruction(instr *Instruction) {
 	}
 
 	r1 := b.allocateValue(t1)
-	instr.rValue = r1
+	instr.rValue = r1.setInstructionID(instr.id)
 
 	tsl := len(ts)
 	if tsl == 0 {
@@ -404,20 +407,14 @@ func (b *builder) InsertInstruction(instr *Instruction) {
 
 	rValues := b.varLengthPool.Allocate(tsl)
 	for i := 0; i < tsl; i++ {
-		rValues = rValues.Append(&b.varLengthPool, b.allocateValue(ts[i]))
+		rn := b.allocateValue(ts[i])
+		rValues = rValues.Append(&b.varLengthPool, rn.setInstructionID(instr.id))
 	}
 	instr.rValues = rValues
 }
 
 // DefineVariable implements Builder.DefineVariable.
 func (b *builder) DefineVariable(variable Variable, value Value, block BasicBlock) {
-	if b.variables[variable].invalid() {
-		panic("BUG: trying to define variable " + variable.String() + " but is not declared yet")
-	}
-
-	if b.variables[variable] != value.Type() {
-		panic(fmt.Sprintf("BUG: inconsistent type for variable %d: expected %s but got %s", variable, b.variables[variable], value.Type()))
-	}
 	bb := block.(*basicBlock)
 	bb.lastDefinitions[variable] = value
 }
@@ -444,20 +441,9 @@ func (b *builder) EntryBlock() BasicBlock {
 
 // DeclareVariable implements Builder.DeclareVariable.
 func (b *builder) DeclareVariable(typ Type) Variable {
-	v := b.allocateVariable()
-	iv := int(v)
-	if l := len(b.variables); l <= iv {
-		b.variables = append(b.variables, make([]Type, 2*(l+1))...)
-	}
-	b.variables[v] = typ
-	return v
-}
-
-// allocateVariable allocates a new variable.
-func (b *builder) allocateVariable() (ret Variable) {
-	ret = b.nextVariable
+	v := b.nextVariable
 	b.nextVariable++
-	return
+	return v.setType(typ)
 }
 
 // allocateValue implements Builder.AllocateValue.
@@ -493,8 +479,7 @@ func (b *builder) findValueInLinearPath(variable Variable, blk *basicBlock) Valu
 
 // MustFindValue implements Builder.MustFindValue.
 func (b *builder) MustFindValue(variable Variable) Value {
-	typ := b.definedVariableType(variable)
-	return b.findValue(typ, variable, b.currentBB)
+	return b.findValue(variable.getType(), variable, b.currentBB)
 }
 
 // findValue recursively tries to find the latest definition of a `variable`. The algorithm is described in
@@ -522,7 +507,7 @@ func (b *builder) findValue(typ Type, variable Variable, blk *basicBlock) Value
 		return value
 	} else if blk.EntryBlock() {
 		// If this is the entry block, we reach the uninitialized variable which has zero value.
-		return b.zeros[b.definedVariableType(variable)]
+		return b.zeros[variable.getType()]
 	}
 
 	if pred := blk.singlePred; pred != nil {
@@ -554,7 +539,6 @@ func (b *builder) findValue(typ Type, variable Variable, blk *basicBlock) Value
 
 	if uniqueValue != ValueInvalid {
 		// If all the predecessors have the same definition, we can use that value.
-		b.DefineVariable(variable, uniqueValue, blk)
 		b.alias(tmpValue, uniqueValue)
 		return uniqueValue
 	} else {
@@ -584,7 +568,7 @@ func (b *builder) Seal(raw BasicBlock) {
 
 	for _, v := range blk.unknownValues {
 		variable, phiValue := v.variable, v.value
-		typ := b.definedVariableType(variable)
+		typ := variable.getType()
 		blk.addParamOn(b, phiValue)
 		for i := range blk.preds {
 			pred := &blk.preds[i]
@@ -597,15 +581,6 @@ func (b *builder) Seal(raw BasicBlock) {
 	}
 }
 
-// definedVariableType returns the type of the given variable. If the variable is not defined yet, it panics.
-func (b *builder) definedVariableType(variable Variable) Type {
-	typ := b.variables[variable]
-	if typ.invalid() {
-		panic(fmt.Sprintf("%s is not defined yet", variable))
-	}
-	return typ
-}
-
 // Format implements Builder.Format.
 func (b *builder) Format() string {
 	str := strings.Builder{}
@@ -707,15 +682,24 @@ func (b *builder) blockIteratorReversePostOrderNext() *basicBlock {
 	}
 }
 
-// ValueRefCounts implements Builder.ValueRefCounts.
-func (b *builder) ValueRefCounts() []int {
-	return b.valueRefCounts
+// ValuesInfo implements Builder.ValuesInfo.
+func (b *builder) ValuesInfo() []ValueInfo {
+	return b.valuesInfo
 }
 
 // alias records the alias of the given values. The alias(es) will be
 // eliminated in the optimization pass via resolveArgumentAlias.
 func (b *builder) alias(dst, src Value) {
-	b.valueIDAliases[dst.ID()] = src
+	did := int(dst.ID())
+	if did >= len(b.valuesInfo) {
+		l := did + 1 - len(b.valuesInfo)
+		b.valuesInfo = append(b.valuesInfo, make([]ValueInfo, l)...)
+		view := b.valuesInfo[len(b.valuesInfo)-l:]
+		for i := range view {
+			view[i].alias = ValueInvalid
+		}
+	}
+	b.valuesInfo[did].alias = src
 }
 
 // resolveArgumentAlias resolves the alias of the arguments of the given instruction.
@@ -740,10 +724,13 @@ func (b *builder) resolveArgumentAlias(instr *Instruction) {
 
 // resolveAlias resolves the alias of the given value.
 func (b *builder) resolveAlias(v Value) Value {
+	info := b.valuesInfo
+	l := ValueID(len(info))
 	// Some aliases are chained, so we need to resolve them recursively.
 	for {
-		if src, ok := b.valueIDAliases[v.ID()]; ok {
-			v = src
+		vid := v.ID()
+		if vid < l && info[vid].alias.Valid() {
+			v = info[vid].alias
 		} else {
 			break
 		}
@@ -791,3 +778,13 @@ func (b *builder) LoopNestingForestRoots() []BasicBlock {
 func (b *builder) LowestCommonAncestor(blk1, blk2 BasicBlock) BasicBlock {
 	return b.sparseTree.findLCA(blk1.ID(), blk2.ID())
 }
+
+// InstructionOfValue returns the instruction that produces the given Value, or nil
+// if the Value is not produced by any instruction.
+func (b *builder) InstructionOfValue(v Value) *Instruction {
+	instrID := v.instructionID()
+	if instrID <= 0 {
+		return nil
+	}
+	return b.instructionsPool.View(instrID - 1)
+}
diff --git a/internal/engine/wazevo/ssa/pass.go b/internal/engine/wazevo/ssa/pass.go
index 9f0643cc..b9763791 100644
--- a/internal/engine/wazevo/ssa/pass.go
+++ b/internal/engine/wazevo/ssa/pass.go
@@ -235,11 +235,13 @@ func passRedundantPhiEliminationOpt(b *builder) {
 // TODO: the algorithm here might not be efficient. Get back to this later.
 func passDeadCodeEliminationOpt(b *builder) {
 	nvid := int(b.nextValueID)
-	if nvid >= len(b.valueRefCounts) {
-		b.valueRefCounts = append(b.valueRefCounts, make([]int, nvid-len(b.valueRefCounts)+1)...)
-	}
-	if nvid >= len(b.valueIDToInstruction) {
-		b.valueIDToInstruction = append(b.valueIDToInstruction, make([]*Instruction, nvid-len(b.valueIDToInstruction)+1)...)
+	if nvid >= len(b.valuesInfo) {
+		l := nvid - len(b.valuesInfo) + 1
+		b.valuesInfo = append(b.valuesInfo, make([]ValueInfo, l)...)
+		view := b.valuesInfo[len(b.valuesInfo)-l:]
+		for i := range view {
+			view[i].alias = ValueInvalid
+		}
 	}
 
 	// First, we gather all the instructions with side effects.
@@ -259,14 +261,6 @@ func passDeadCodeEliminationOpt(b *builder) {
 				// The strict side effect should create different instruction groups.
 				gid++
 			}
-
-			r1, rs := cur.Returns()
-			if r1.Valid() {
-				b.valueIDToInstruction[r1.ID()] = cur
-			}
-			for _, r := range rs {
-				b.valueIDToInstruction[r.ID()] = cur
-			}
 		}
 	}
 
@@ -287,28 +281,28 @@ func passDeadCodeEliminationOpt(b *builder) {
 
 		v1, v2, v3, vs := live.Args()
 		if v1.Valid() {
-			producingInst := b.valueIDToInstruction[v1.ID()]
+			producingInst := b.InstructionOfValue(v1)
 			if producingInst != nil {
 				liveInstructions = append(liveInstructions, producingInst)
 			}
 		}
 
 		if v2.Valid() {
-			producingInst := b.valueIDToInstruction[v2.ID()]
+			producingInst := b.InstructionOfValue(v2)
 			if producingInst != nil {
 				liveInstructions = append(liveInstructions, producingInst)
 			}
 		}
 
 		if v3.Valid() {
-			producingInst := b.valueIDToInstruction[v3.ID()]
+			producingInst := b.InstructionOfValue(v3)
 			if producingInst != nil {
 				liveInstructions = append(liveInstructions, producingInst)
 			}
 		}
 
 		for _, v := range vs {
-			producingInst := b.valueIDToInstruction[v.ID()]
+			producingInst := b.InstructionOfValue(v)
 			if producingInst != nil {
 				liveInstructions = append(liveInstructions, producingInst)
 			}
@@ -356,34 +350,19 @@ func (b *builder) incRefCount(id ValueID, from *Instruction) {
 	if wazevoapi.SSALoggingEnabled {
 		fmt.Printf("v%d referenced from %v\n", id, from.Format(b))
 	}
-	b.valueRefCounts[id]++
+	info := &b.valuesInfo[id]
+	info.RefCount++
 }
 
 // passNopInstElimination eliminates the instructions which is essentially a no-op.
 func passNopInstElimination(b *builder) {
-	if int(b.nextValueID) >= len(b.valueIDToInstruction) {
-		b.valueIDToInstruction = append(b.valueIDToInstruction, make([]*Instruction, int(b.nextValueID)-len(b.valueIDToInstruction)+1)...)
-	}
-
-	for blk := b.blockIteratorBegin(); blk != nil; blk = b.blockIteratorNext() {
-		for cur := blk.rootInstr; cur != nil; cur = cur.next {
-			r1, rs := cur.Returns()
-			if r1.Valid() {
-				b.valueIDToInstruction[r1.ID()] = cur
-			}
-			for _, r := range rs {
-				b.valueIDToInstruction[r.ID()] = cur
-			}
-		}
-	}
-
 	for blk := b.blockIteratorBegin(); blk != nil; blk = b.blockIteratorNext() {
 		for cur := blk.rootInstr; cur != nil; cur = cur.next {
 			switch cur.Opcode() {
 			// TODO: add more logics here.
 			case OpcodeIshl, OpcodeSshr, OpcodeUshr:
 				x, amount := cur.Arg2()
-				definingInst := b.valueIDToInstruction[amount.ID()]
+				definingInst := b.InstructionOfValue(amount)
 				if definingInst == nil {
 					// If there's no defining instruction, that means the amount is coming from the parameter.
 					continue
diff --git a/internal/engine/wazevo/ssa/vs.go b/internal/engine/wazevo/ssa/vs.go
index bcf83cbf..d906e7e3 100644
--- a/internal/engine/wazevo/ssa/vs.go
+++ b/internal/engine/wazevo/ssa/vs.go
@@ -15,17 +15,31 @@ import (
 //
 // Variable is useful to track the SSA Values of a variable in the source program, and
 // can be used to find the corresponding latest SSA Value via Builder.FindValue.
+//
+// Higher 4-bit is used to store Type for this variable.
 type Variable uint32
 
 // String implements fmt.Stringer.
 func (v Variable) String() string {
-	return fmt.Sprintf("var%d", v)
+	return fmt.Sprintf("var%d", v&0x0fffffff)
+}
+
+func (v Variable) setType(typ Type) Variable {
+	if v >= 1<<28 {
+		panic(fmt.Sprintf("Too large variable: %d", v))
+	}
+	return Variable(typ)<<28 | v
+}
+
+func (v Variable) getType() Type {
+	return Type(v >> 28)
 }
 
 // Value represents an SSA value with a type information. The relationship with Variable is 1: N (including 0),
 // that means there might be multiple Variable(s) for a Value.
 //
-// Higher 32-bit is used to store Type for this value.
+// 32 to 59-bit is used to store the unique identifier of the Instruction that generates this value if any.
+// 60 to 63-bit is used to store Type for this value.
 type Value uint64
 
 // ValueID is the lower 32bit of Value, which is the pure identifier of Value without type info.
@@ -33,7 +47,7 @@ type ValueID uint32
 
 const (
 	valueIDInvalid ValueID = math.MaxUint32
-	ValueInvalid   Value   = Value(valueIDInvalid)
+	ValueInvalid           = Value(valueIDInvalid)
 )
 
 // Format creates a debug string for this Value using the data stored in Builder.
@@ -54,7 +68,7 @@ func (v Value) formatWithType(b Builder) (ret string) {
 	if wazevoapi.SSALoggingEnabled { // This is useful to check live value analysis bugs.
 		if bd := b.(*builder); bd.donePostBlockLayoutPasses {
 			id := v.ID()
-			ret += fmt.Sprintf("(ref=%d)", bd.valueRefCounts[id])
+			ret += fmt.Sprintf("(ref=%d)", bd.valuesInfo[id].RefCount)
 		}
 	}
 	return ret
@@ -67,7 +81,7 @@ func (v Value) Valid() bool {
 
 // Type returns the Type of this value.
 func (v Value) Type() Type {
-	return Type(v >> 32)
+	return Type(v >> 60)
 }
 
 // ID returns the valueID of this value.
@@ -77,7 +91,20 @@ func (v Value) ID() ValueID {
 
 // setType sets a type to this Value and returns the updated Value.
 func (v Value) setType(typ Type) Value {
-	return v | Value(typ)<<32
+	return v | Value(typ)<<60
+}
+
+// setInstructionID sets an Instruction.id to this Value and returns the updated Value.
+func (v Value) setInstructionID(id int) Value {
+	if id < 0 || uint(id) >= 1<<28 {
+		panic(fmt.Sprintf("Too large instruction ID: %d", id))
+	}
+	return v | Value(id)<<32
+}
+
+// instructionID() returns the Instruction.id of this Value.
+func (v Value) instructionID() int {
+	return int(v>>32) & 0x0fffffff
 }
 
 // Values is a slice of Value. Use this instead of []Value to reuse the underlying memory.
diff --git a/internal/wasm/binary/value.go b/internal/wasm/binary/value.go
index 755ee5ea..dfc4417e 100644
--- a/internal/wasm/binary/value.go
+++ b/internal/wasm/binary/value.go
@@ -54,7 +54,6 @@ func decodeUTF8(r *bytes.Reader, contextFormat string, contextArgs ...interface{
 		return "", 0, fmt.Errorf("%s is not valid UTF-8", fmt.Sprintf(contextFormat, contextArgs...))
 	}
 
-	// TODO: use unsafe.String after flooring Go 1.20.
-	ret := *(*string)(unsafe.Pointer(&buf))
+	ret := unsafe.String(&buf[0], int(size))
 	return ret, size + uint32(sizeOfSize), nil
 }
diff --git a/internal/wasm/memory.go b/internal/wasm/memory.go
index 947b1611..1a23dc46 100644
--- a/internal/wasm/memory.go
+++ b/internal/wasm/memory.go
@@ -52,7 +52,8 @@ type MemoryInstance struct {
 	definition api.MemoryDefinition
 
 	// Mux is used in interpreter mode to prevent overlapping calls to atomic instructions,
-	// introduced with WebAssembly threads proposal.
+	// introduced with WebAssembly threads proposal, and in compiler mode to make memory modifications
+	// within Grow non-racy for the Go race detector.
 	Mux sync.Mutex
 
 	// waiters implements atomic wait and notify. It is implemented similarly to golang.org/x/sync/semaphore,
@@ -227,6 +228,11 @@ func MemoryPagesToBytesNum(pages uint32) (bytesNum uint64) {
 
 // Grow implements the same method as documented on api.Memory.
 func (m *MemoryInstance) Grow(delta uint32) (result uint32, ok bool) {
+	if m.Shared {
+		m.Mux.Lock()
+		defer m.Mux.Unlock()
+	}
+
 	currentPages := m.Pages()
 	if delta == 0 {
 		return currentPages, true


Test patch:
diff --git a/experimental/close_example_test.go b/experimental/close_example_test.go
index 59eb08e8..ebb701c2 100644
--- a/experimental/close_example_test.go
+++ b/experimental/close_example_test.go
@@ -12,7 +12,7 @@ var ctx context.Context
 func Example_closeNotifier() {
 	closeCh := make(chan struct{})
 	ctx = experimental.WithCloseNotifier(
-		ctx,
+		context.Background(),
 		experimental.CloseNotifyFunc(func(context.Context, uint32) { close(closeCh) }),
 	)
 
@@ -24,4 +24,6 @@ func Example_closeNotifier() {
 	default:
 		// do some more work with the module
 	}
+
+	// Output:
 }
diff --git a/experimental/sysfs/config_example_test.go b/experimental/sysfs/config_example_test.go
index 6c54a449..021a3c49 100644
--- a/experimental/sysfs/config_example_test.go
+++ b/experimental/sysfs/config_example_test.go
@@ -20,6 +20,8 @@ func ExampleAdaptFS() {
 
 	moduleConfig = wazero.NewModuleConfig().
 		WithFSConfig(wazero.NewFSConfig().(sysfs.FSConfig).WithSysFSMount(root, "/"))
+
+	// Output:
 }
 
 // This example shows how to configure a sysfs.DirFS
@@ -28,6 +30,8 @@ func ExampleDirFS() {
 
 	moduleConfig = wazero.NewModuleConfig().
 		WithFSConfig(wazero.NewFSConfig().(sysfs.FSConfig).WithSysFSMount(root, "/"))
+
+	// Output:
 }
 
 // This example shows how to configure a sysfs.ReadFS
@@ -37,4 +41,6 @@ func ExampleReadFS() {
 
 	moduleConfig = wazero.NewModuleConfig().
 		WithFSConfig(wazero.NewFSConfig().(sysfs.FSConfig).WithSysFSMount(readOnly, "/"))
+
+	// Output:
 }
diff --git a/fsconfig_example_test.go b/fsconfig_example_test.go
index 882e152b..0315c8c1 100644
--- a/fsconfig_example_test.go
+++ b/fsconfig_example_test.go
@@ -24,4 +24,6 @@ func Example_fsConfig() {
 	moduleConfig = wazero.NewModuleConfig().
 		// Make "index.html" accessible to the guest as "/index.html".
 		WithFSConfig(wazero.NewFSConfig().WithFSMount(rooted, "/"))
+
+	// Output:
 }
diff --git a/internal/engine/wazevo/backend/compiler_lower_test.go b/internal/engine/wazevo/backend/compiler_lower_test.go
index 851b4409..0674e264 100644
--- a/internal/engine/wazevo/backend/compiler_lower_test.go
+++ b/internal/engine/wazevo/backend/compiler_lower_test.go
@@ -57,7 +57,6 @@ func TestCompiler_lowerBlockArguments(t *testing.T) {
 				}
 
 				c := newCompiler(context.Background(), m, builder)
-				c.ssaValueDefinitions = []SSAValueDefinition{{Instr: i1}, {Instr: i2}, {Instr: f1}, {Instr: f2}}
 				c.ssaValueToVRegs = []regalloc.VReg{0, 1, 2, 3, 4, 5, 6, 7}
 				return c, []ssa.Value{i1.Return(), i2.Return(), f1.Return(), f2.Return()}, succ, func(t *testing.T) {
 					require.Equal(t, 4, len(insertedConstInstructions))
@@ -85,7 +84,6 @@ func TestCompiler_lowerBlockArguments(t *testing.T) {
 					insertMoves = append(insertMoves, struct{ src, dst regalloc.VReg }{src: src, dst: dst})
 				}}
 				c := newCompiler(context.Background(), m, builder)
-				c.ssaValueDefinitions = []SSAValueDefinition{{}, {}, {}, {}}
 				c.ssaValueToVRegs = []regalloc.VReg{0, 1, 2, 3}
 				c.nextVRegID = 100 // Temporary reg should start with 100.
 				return c, []ssa.Value{v2, v1, v3 /* Swaps v1, v2 and pass v3 as-is. */}, blk, func(t *testing.T) {
@@ -123,7 +121,6 @@ func TestCompiler_lowerBlockArguments(t *testing.T) {
 					insertMoves = append(insertMoves, struct{ src, dst regalloc.VReg }{src: src, dst: dst})
 				}}
 				c := newCompiler(context.Background(), m, builder)
-				c.ssaValueDefinitions = []SSAValueDefinition{{}, {}}
 				c.ssaValueToVRegs = []regalloc.VReg{0, 1}
 				return c, []ssa.Value{add.Return()}, blk, func(t *testing.T) {
 					require.Equal(t, 1, len(insertMoves))
diff --git a/internal/engine/wazevo/backend/isa/amd64/lower_mem_test.go b/internal/engine/wazevo/backend/isa/amd64/lower_mem_test.go
index 7f6d7460..2516ab29 100644
--- a/internal/engine/wazevo/backend/isa/amd64/lower_mem_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/lower_mem_test.go
@@ -34,9 +34,9 @@ func TestMachine_lowerToAddressMode(t *testing.T) {
 				iadd := b.AllocateInstruction().AsIadd(iconst1.Return(), iconst2.Return()).Insert(b)
 				ptr = iadd.Return()
 				offset = 3
-				ctx.definitions[iconst1.Return()] = &backend.SSAValueDefinition{Instr: iconst1}
-				ctx.definitions[iconst2.Return()] = &backend.SSAValueDefinition{Instr: iconst2}
-				ctx.definitions[ptr] = &backend.SSAValueDefinition{Instr: iadd}
+				ctx.definitions[iconst1.Return()] = backend.SSAValueDefinition{Instr: iconst1}
+				ctx.definitions[iconst2.Return()] = backend.SSAValueDefinition{Instr: iconst2}
+				ctx.definitions[ptr] = backend.SSAValueDefinition{Instr: iadd}
 				return
 			},
 			insts: []string{
@@ -52,9 +52,10 @@ func TestMachine_lowerToAddressMode(t *testing.T) {
 				iadd := b.AllocateInstruction().AsIadd(iconst1.Return(), p).Insert(b)
 				ptr = iadd.Return()
 				offset = 3
-				ctx.definitions[iconst1.Return()] = &backend.SSAValueDefinition{Instr: iconst1}
-				ctx.definitions[p] = &backend.SSAValueDefinition{BlockParamValue: p, BlkParamVReg: raxVReg}
-				ctx.definitions[ptr] = &backend.SSAValueDefinition{Instr: iadd}
+				ctx.definitions[iconst1.Return()] = backend.SSAValueDefinition{Instr: iconst1}
+				ctx.vRegMap[p] = raxVReg
+				ctx.definitions[p] = backend.SSAValueDefinition{V: p}
+				ctx.definitions[ptr] = backend.SSAValueDefinition{Instr: iadd}
 				return
 			},
 			am: newAmodeImmReg(1+3, raxVReg),
@@ -67,9 +68,11 @@ func TestMachine_lowerToAddressMode(t *testing.T) {
 				iadd := b.AllocateInstruction().AsIadd(p1, p2).Insert(b)
 				ptr = iadd.Return()
 				offset = 3
-				ctx.definitions[p1] = &backend.SSAValueDefinition{BlockParamValue: p1, BlkParamVReg: raxVReg}
-				ctx.definitions[p2] = &backend.SSAValueDefinition{BlockParamValue: p2, BlkParamVReg: rcxVReg}
-				ctx.definitions[ptr] = &backend.SSAValueDefinition{Instr: iadd}
+				ctx.vRegMap[p1] = raxVReg
+				ctx.definitions[p1] = backend.SSAValueDefinition{V: p1}
+				ctx.vRegMap[p2] = rcxVReg
+				ctx.definitions[p2] = backend.SSAValueDefinition{V: p2}
+				ctx.definitions[ptr] = backend.SSAValueDefinition{Instr: iadd}
 				return
 			},
 			am: newAmodeRegRegShift(3, raxVReg, rcxVReg, 0),
@@ -79,7 +82,8 @@ func TestMachine_lowerToAddressMode(t *testing.T) {
 			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) (ptr ssa.Value, offset uint32) {
 				ptr = b.CurrentBlock().AddParam(b, ssa.TypeI64)
 				offset = 1 << 31
-				ctx.definitions[ptr] = &backend.SSAValueDefinition{BlockParamValue: ptr, BlkParamVReg: raxVReg}
+				ctx.vRegMap[ptr] = raxVReg
+				ctx.definitions[ptr] = backend.SSAValueDefinition{V: ptr}
 				return
 			},
 			insts: []string{
@@ -94,8 +98,8 @@ func TestMachine_lowerToAddressMode(t *testing.T) {
 			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) (ptr ssa.Value, offset uint32) {
 				iconst32 := b.AllocateInstruction().AsIconst32(123).Insert(b)
 				uextend := b.AllocateInstruction().AsUExtend(iconst32.Return(), 32, 64).Insert(b)
-				ctx.definitions[iconst32.Return()] = &backend.SSAValueDefinition{Instr: iconst32}
-				ctx.definitions[uextend.Return()] = &backend.SSAValueDefinition{Instr: uextend}
+				ctx.definitions[iconst32.Return()] = backend.SSAValueDefinition{Instr: iconst32}
+				ctx.definitions[uextend.Return()] = backend.SSAValueDefinition{Instr: uextend}
 				return uextend.Return(), 0
 			},
 			insts: []string{
@@ -109,9 +113,10 @@ func TestMachine_lowerToAddressMode(t *testing.T) {
 				p := b.CurrentBlock().AddParam(b, ssa.TypeI64)
 				iconst64 := b.AllocateInstruction().AsIconst64(2).Insert(b)
 				ishl := b.AllocateInstruction().AsIshl(p, iconst64.Return()).Insert(b)
-				ctx.definitions[p] = &backend.SSAValueDefinition{BlockParamValue: p, BlkParamVReg: raxVReg}
-				ctx.definitions[iconst64.Return()] = &backend.SSAValueDefinition{Instr: iconst64}
-				ctx.definitions[ishl.Return()] = &backend.SSAValueDefinition{Instr: ishl}
+				ctx.vRegMap[p] = raxVReg
+				ctx.definitions[p] = backend.SSAValueDefinition{V: p}
+				ctx.definitions[iconst64.Return()] = backend.SSAValueDefinition{Instr: iconst64}
+				ctx.definitions[ishl.Return()] = backend.SSAValueDefinition{Instr: ishl}
 				return ishl.Return(), 1 << 30
 			},
 			insts: []string{
@@ -127,11 +132,13 @@ func TestMachine_lowerToAddressMode(t *testing.T) {
 				const2 := b.AllocateInstruction().AsIconst64(2).Insert(b)
 				ishl := b.AllocateInstruction().AsIshl(p1, const2.Return()).Insert(b)
 				iadd := b.AllocateInstruction().AsIadd(p2, ishl.Return()).Insert(b)
-				ctx.definitions[p1] = &backend.SSAValueDefinition{BlockParamValue: p1, BlkParamVReg: raxVReg}
-				ctx.definitions[p2] = &backend.SSAValueDefinition{BlockParamValue: p2, BlkParamVReg: rcxVReg}
-				ctx.definitions[const2.Return()] = &backend.SSAValueDefinition{Instr: const2}
-				ctx.definitions[ishl.Return()] = &backend.SSAValueDefinition{Instr: ishl}
-				ctx.definitions[iadd.Return()] = &backend.SSAValueDefinition{Instr: iadd}
+				ctx.vRegMap[p1] = raxVReg
+				ctx.definitions[p1] = backend.SSAValueDefinition{V: p1}
+				ctx.vRegMap[p2] = rcxVReg
+				ctx.definitions[p2] = backend.SSAValueDefinition{V: p2}
+				ctx.definitions[const2.Return()] = backend.SSAValueDefinition{Instr: const2}
+				ctx.definitions[ishl.Return()] = backend.SSAValueDefinition{Instr: ishl}
+				ctx.definitions[iadd.Return()] = backend.SSAValueDefinition{Instr: iadd}
 				return iadd.Return(), 1 << 30
 			},
 			am: newAmodeRegRegShift(1<<30, rcxVReg, raxVReg, 2),
@@ -172,7 +179,7 @@ func TestMachine_lowerAddendFromInstr(t *testing.T) {
 			name: "uextend const32",
 			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) *ssa.Instruction {
 				iconst32 := b.AllocateInstruction().AsIconst32(123).Insert(b)
-				ctx.definitions[iconst32.Return()] = &backend.SSAValueDefinition{Instr: iconst32}
+				ctx.definitions[iconst32.Return()] = backend.SSAValueDefinition{Instr: iconst32}
 				return b.AllocateInstruction().AsUExtend(iconst32.Return(), 32, 64).Insert(b)
 			},
 			exp: addend{regalloc.VRegInvalid, 123, 0},
@@ -181,7 +188,8 @@ func TestMachine_lowerAddendFromInstr(t *testing.T) {
 			name: "uextend const64",
 			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) *ssa.Instruction {
 				p := b.CurrentBlock().AddParam(b, ssa.TypeI32)
-				ctx.definitions[p] = &backend.SSAValueDefinition{BlkParamVReg: raxVReg, BlockParamValue: p}
+				ctx.vRegMap[p] = raxVReg
+				ctx.definitions[p] = backend.SSAValueDefinition{V: p}
 				return b.AllocateInstruction().AsUExtend(p, 32, 64).Insert(b)
 			},
 			exp: addend{raxVReg, 0, 0},
@@ -190,7 +198,8 @@ func TestMachine_lowerAddendFromInstr(t *testing.T) {
 			name: "uextend param i32",
 			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) *ssa.Instruction {
 				p := b.CurrentBlock().AddParam(b, ssa.TypeI32)
-				ctx.definitions[p] = &backend.SSAValueDefinition{BlkParamVReg: raxVReg, BlockParamValue: p}
+				ctx.vRegMap[p] = raxVReg
+				ctx.definitions[p] = backend.SSAValueDefinition{V: p}
 				return b.AllocateInstruction().AsUExtend(p, 32, 64).Insert(b)
 			},
 			exp: addend{raxVReg, 0, 0},
@@ -199,7 +208,7 @@ func TestMachine_lowerAddendFromInstr(t *testing.T) {
 			name: "sextend const32",
 			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) *ssa.Instruction {
 				iconst32 := b.AllocateInstruction().AsIconst32(123).Insert(b)
-				ctx.definitions[iconst32.Return()] = &backend.SSAValueDefinition{Instr: iconst32}
+				ctx.definitions[iconst32.Return()] = backend.SSAValueDefinition{Instr: iconst32}
 				return b.AllocateInstruction().AsSExtend(iconst32.Return(), 32, 64).Insert(b)
 			},
 			exp: addend{regalloc.VRegInvalid, 123, 0},
@@ -208,7 +217,8 @@ func TestMachine_lowerAddendFromInstr(t *testing.T) {
 			name: "sextend const64",
 			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) *ssa.Instruction {
 				p := b.CurrentBlock().AddParam(b, ssa.TypeI32)
-				ctx.definitions[p] = &backend.SSAValueDefinition{BlkParamVReg: raxVReg, BlockParamValue: p}
+				ctx.vRegMap[p] = raxVReg
+				ctx.definitions[p] = backend.SSAValueDefinition{V: p}
 				return b.AllocateInstruction().AsSExtend(p, 32, 64).Insert(b)
 			},
 			exp: addend{raxVReg, 0, 0},
@@ -217,7 +227,8 @@ func TestMachine_lowerAddendFromInstr(t *testing.T) {
 			name: "sextend param i32",
 			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) *ssa.Instruction {
 				p := b.CurrentBlock().AddParam(b, ssa.TypeI32)
-				ctx.definitions[p] = &backend.SSAValueDefinition{BlkParamVReg: raxVReg, BlockParamValue: p}
+				ctx.vRegMap[p] = raxVReg
+				ctx.definitions[p] = backend.SSAValueDefinition{V: p}
 				return b.AllocateInstruction().AsSExtend(p, 32, 64).Insert(b)
 			},
 			exp: addend{raxVReg, 0, 0},
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine_test.go b/internal/engine/wazevo/backend/isa/amd64/machine_test.go
index 5614b45c..41cf87e9 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine_test.go
@@ -39,33 +39,34 @@ func Test_asImm32(t *testing.T) {
 func TestMachine_getOperand_Reg(t *testing.T) {
 	for _, tc := range []struct {
 		name         string
-		setup        func(*mockCompiler, ssa.Builder, *machine) *backend.SSAValueDefinition
+		setup        func(*mockCompiler, ssa.Builder, *machine) backend.SSAValueDefinition
 		exp          operand
 		instructions []string
 	}{
 		{
 			name: "block param",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
-				return &backend.SSAValueDefinition{BlkParamVReg: raxVReg, Instr: nil, N: 0}
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) backend.SSAValueDefinition {
+				ctx.vRegMap[1234] = raxVReg
+				return backend.SSAValueDefinition{V: 1234, Instr: nil}
 			},
 			exp: newOperandReg(raxVReg),
 		},
 
 		{
 			name: "const instr",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) backend.SSAValueDefinition {
 				instr := builder.AllocateInstruction()
 				instr.AsIconst32(0xf00000f)
 				builder.InsertInstruction(instr)
 				ctx.vRegCounter = 99
-				return &backend.SSAValueDefinition{Instr: instr, N: 0}
+				return backend.SSAValueDefinition{Instr: instr}
 			},
 			exp:          newOperandReg(regalloc.VReg(100).SetRegType(regalloc.RegTypeInt)),
 			instructions: []string{"movl $251658255, %r100d?"},
 		},
 		{
 			name: "non const instr (single-return)",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) backend.SSAValueDefinition {
 				c := builder.AllocateInstruction()
 				sig := &ssa.Signature{Results: []ssa.Type{ssa.TypeI64}}
 				builder.DeclareSignature(sig)
@@ -73,13 +74,13 @@ func TestMachine_getOperand_Reg(t *testing.T) {
 				builder.InsertInstruction(c)
 				r := c.Return()
 				ctx.vRegMap[r] = regalloc.VReg(50)
-				return &backend.SSAValueDefinition{Instr: c, N: 0}
+				return backend.SSAValueDefinition{V: r}
 			},
 			exp: newOperandReg(regalloc.VReg(50)),
 		},
 		{
 			name: "non const instr (multi-return)",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) backend.SSAValueDefinition {
 				c := builder.AllocateInstruction()
 				sig := &ssa.Signature{Results: []ssa.Type{ssa.TypeI64, ssa.TypeF64, ssa.TypeF64}}
 				builder.DeclareSignature(sig)
@@ -87,7 +88,7 @@ func TestMachine_getOperand_Reg(t *testing.T) {
 				builder.InsertInstruction(c)
 				_, rs := c.Returns()
 				ctx.vRegMap[rs[1]] = regalloc.VReg(50)
-				return &backend.SSAValueDefinition{Instr: c, N: 2}
+				return backend.SSAValueDefinition{V: rs[1]}
 			},
 			exp: newOperandReg(regalloc.VReg(50)),
 		},
@@ -105,26 +106,27 @@ func TestMachine_getOperand_Reg(t *testing.T) {
 func TestMachine_getOperand_Imm32_Reg(t *testing.T) {
 	for _, tc := range []struct {
 		name         string
-		setup        func(*mockCompiler, ssa.Builder, *machine) *backend.SSAValueDefinition
+		setup        func(*mockCompiler, ssa.Builder, *machine) backend.SSAValueDefinition
 		exp          operand
 		instructions []string
 	}{
 		{
 			name: "block param falls back to getOperand_Reg",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
-				return &backend.SSAValueDefinition{BlkParamVReg: raxVReg, Instr: nil, N: 0}
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) backend.SSAValueDefinition {
+				ctx.vRegMap[1234] = raxVReg
+				return backend.SSAValueDefinition{V: 1234, Instr: nil}
 			},
 			exp: newOperandReg(raxVReg),
 		},
 		{
 			name: "const imm 32",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) backend.SSAValueDefinition {
 				instr := builder.AllocateInstruction()
 				instr.AsIconst32(0xf00000f)
 				builder.InsertInstruction(instr)
 				ctx.vRegCounter = 99
 				ctx.currentGID = 0xff // const can be merged anytime, regardless of the group id.
-				return &backend.SSAValueDefinition{Instr: instr, N: 0}
+				return backend.SSAValueDefinition{Instr: instr}
 			},
 			exp: newOperandImm32(0xf00000f),
 		},
@@ -148,37 +150,39 @@ func Test_machine_getOperand_Mem_Imm32_Reg(t *testing.T) {
 
 	for _, tc := range []struct {
 		name         string
-		setup        func(*mockCompiler, ssa.Builder, *machine) *backend.SSAValueDefinition
+		setup        func(*mockCompiler, ssa.Builder, *machine) backend.SSAValueDefinition
 		exp          operand
 		instructions []string
 	}{
 		{
 			name: "block param falls back to getOperand_Imm32_Reg",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
-				return &backend.SSAValueDefinition{BlkParamVReg: raxVReg, Instr: nil, N: 0}
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) backend.SSAValueDefinition {
+				ctx.vRegMap[1234] = raxVReg
+				return backend.SSAValueDefinition{V: 1234, Instr: nil}
 			},
 			exp: newOperandReg(raxVReg),
 		},
 		{
 			name: "amode with block param",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) backend.SSAValueDefinition {
 				blk := builder.CurrentBlock()
 				ptr := blk.AddParam(builder, ssa.TypeI64)
-				ctx.definitions[ptr] = &backend.SSAValueDefinition{BlockParamValue: ptr, BlkParamVReg: raxVReg}
+				ctx.vRegMap[ptr] = raxVReg
+				ctx.definitions[ptr] = backend.SSAValueDefinition{V: ptr}
 				instr := builder.AllocateInstruction()
 				instr.AsLoad(ptr, 123, ssa.TypeI64).Insert(builder)
-				return &backend.SSAValueDefinition{Instr: instr, N: 0}
+				return backend.SSAValueDefinition{Instr: instr}
 			},
 			exp: newOperandMem(newAmodeImmReg(123, raxVReg)),
 		},
 		{
 			name: "amode with iconst",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) backend.SSAValueDefinition {
 				iconst := builder.AllocateInstruction().AsIconst64(456).Insert(builder)
 				instr := builder.AllocateInstruction()
 				instr.AsLoad(iconst.Return(), 123, ssa.TypeI64).Insert(builder)
-				ctx.definitions[iconst.Return()] = &backend.SSAValueDefinition{Instr: iconst}
-				return &backend.SSAValueDefinition{Instr: instr, N: 0}
+				ctx.definitions[iconst.Return()] = backend.SSAValueDefinition{Instr: iconst}
+				return backend.SSAValueDefinition{Instr: instr}
 			},
 			instructions: []string{
 				"movabsq $579, %r1?", // r1 := 123+456
@@ -187,17 +191,17 @@ func Test_machine_getOperand_Mem_Imm32_Reg(t *testing.T) {
 		},
 		{
 			name: "amode with iconst and extend",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) backend.SSAValueDefinition {
 				iconst := builder.AllocateInstruction().AsIconst32(0xffffff).Insert(builder)
 				uextend := builder.AllocateInstruction().AsUExtend(iconst.Return(), 32, 64).Insert(builder)
 
 				instr := builder.AllocateInstruction()
 				instr.AsLoad(uextend.Return(), 123, ssa.TypeI64).Insert(builder)
 
-				ctx.definitions[uextend.Return()] = &backend.SSAValueDefinition{Instr: uextend}
-				ctx.definitions[iconst.Return()] = &backend.SSAValueDefinition{Instr: iconst}
+				ctx.definitions[uextend.Return()] = backend.SSAValueDefinition{Instr: uextend}
+				ctx.definitions[iconst.Return()] = backend.SSAValueDefinition{Instr: iconst}
 
-				return &backend.SSAValueDefinition{Instr: instr, N: 0}
+				return backend.SSAValueDefinition{Instr: instr}
 			},
 			instructions: []string{
 				fmt.Sprintf("movabsq $%d, %%r1?", 0xffffff+123),
@@ -206,17 +210,17 @@ func Test_machine_getOperand_Mem_Imm32_Reg(t *testing.T) {
 		},
 		{
 			name: "amode with iconst and extend",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) backend.SSAValueDefinition {
 				iconst := builder.AllocateInstruction().AsIconst32(456).Insert(builder)
 				uextend := builder.AllocateInstruction().AsUExtend(iconst.Return(), 32, 64).Insert(builder)
 
 				instr := builder.AllocateInstruction()
 				instr.AsLoad(uextend.Return(), 123, ssa.TypeI64).Insert(builder)
 
-				ctx.definitions[uextend.Return()] = &backend.SSAValueDefinition{Instr: uextend}
-				ctx.definitions[iconst.Return()] = &backend.SSAValueDefinition{Instr: iconst}
+				ctx.definitions[uextend.Return()] = backend.SSAValueDefinition{Instr: uextend}
+				ctx.definitions[iconst.Return()] = backend.SSAValueDefinition{Instr: iconst}
 
-				return &backend.SSAValueDefinition{Instr: instr, N: 0}
+				return backend.SSAValueDefinition{Instr: instr}
 			},
 			instructions: []string{
 				fmt.Sprintf("movabsq $%d, %%r1?", 456+123),
@@ -225,7 +229,7 @@ func Test_machine_getOperand_Mem_Imm32_Reg(t *testing.T) {
 		},
 		{
 			name: "amode with iconst and add",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) backend.SSAValueDefinition {
 				p := builder.CurrentBlock().AddParam(builder, ssa.TypeI64)
 				iconst := builder.AllocateInstruction().AsIconst64(456).Insert(builder)
 				iadd := builder.AllocateInstruction().AsIadd(iconst.Return(), p).Insert(builder)
@@ -233,17 +237,18 @@ func Test_machine_getOperand_Mem_Imm32_Reg(t *testing.T) {
 				instr := builder.AllocateInstruction()
 				instr.AsLoad(iadd.Return(), 789, ssa.TypeI64).Insert(builder)
 
-				ctx.definitions[p] = &backend.SSAValueDefinition{BlockParamValue: p, BlkParamVReg: raxVReg}
-				ctx.definitions[iconst.Return()] = &backend.SSAValueDefinition{Instr: iconst}
-				ctx.definitions[iadd.Return()] = &backend.SSAValueDefinition{Instr: iadd}
+				ctx.vRegMap[p] = raxVReg
+				ctx.definitions[p] = backend.SSAValueDefinition{V: p}
+				ctx.definitions[iconst.Return()] = backend.SSAValueDefinition{Instr: iconst}
+				ctx.definitions[iadd.Return()] = backend.SSAValueDefinition{Instr: iadd}
 
-				return &backend.SSAValueDefinition{Instr: instr, N: 0}
+				return backend.SSAValueDefinition{Instr: instr}
 			},
 			exp: newOperandMem(newAmodeImmReg(456+789, raxVReg)),
 		},
 		{
 			name: "amode with iconst, block param and add",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) backend.SSAValueDefinition {
 				iconst1 := builder.AllocateInstruction().AsIconst64(456).Insert(builder)
 				iconst2 := builder.AllocateInstruction().AsIconst64(123).Insert(builder)
 				iadd := builder.AllocateInstruction().AsIadd(iconst1.Return(), iconst2.Return()).Insert(builder)
@@ -251,11 +256,11 @@ func Test_machine_getOperand_Mem_Imm32_Reg(t *testing.T) {
 				instr := builder.AllocateInstruction()
 				instr.AsLoad(iadd.Return(), 789, ssa.TypeI64).Insert(builder)
 
-				ctx.definitions[iconst1.Return()] = &backend.SSAValueDefinition{Instr: iconst1}
-				ctx.definitions[iconst2.Return()] = &backend.SSAValueDefinition{Instr: iconst2}
-				ctx.definitions[iadd.Return()] = &backend.SSAValueDefinition{Instr: iadd}
+				ctx.definitions[iconst1.Return()] = backend.SSAValueDefinition{Instr: iconst1}
+				ctx.definitions[iconst2.Return()] = backend.SSAValueDefinition{Instr: iconst2}
+				ctx.definitions[iadd.Return()] = backend.SSAValueDefinition{Instr: iadd}
 
-				return &backend.SSAValueDefinition{Instr: instr, N: 0}
+				return backend.SSAValueDefinition{Instr: instr}
 			},
 			instructions: []string{
 				fmt.Sprintf("movabsq $%d, %%r1?", 123+456+789),
@@ -301,7 +306,7 @@ L2:
 func Test_machine_lowerClz(t *testing.T) {
 	for _, tc := range []struct {
 		name     string
-		setup    func(*mockCompiler, ssa.Builder, *machine) *backend.SSAValueDefinition
+		setup    func(*mockCompiler, ssa.Builder, *machine) backend.SSAValueDefinition
 		cpuFlags platform.CpuFeatureFlags
 		typ      ssa.Type
 		exp      string
@@ -361,7 +366,8 @@ L2:
 			p := b.CurrentBlock().AddParam(b, tc.typ)
 			m.cpuFeatures = tc.cpuFlags
 
-			ctx.definitions[p] = &backend.SSAValueDefinition{BlockParamValue: p, BlkParamVReg: raxVReg}
+			ctx.vRegMap[p] = raxVReg
+			ctx.definitions[p] = backend.SSAValueDefinition{V: p}
 			ctx.vRegMap[0] = rcxVReg
 			instr := &ssa.Instruction{}
 			instr.AsClz(p)
@@ -376,7 +382,7 @@ L2:
 func TestMachine_lowerCtz(t *testing.T) {
 	for _, tc := range []struct {
 		name     string
-		setup    func(*mockCompiler, ssa.Builder, *machine) *backend.SSAValueDefinition
+		setup    func(*mockCompiler, ssa.Builder, *machine) backend.SSAValueDefinition
 		cpuFlags platform.CpuFeatureFlags
 		typ      ssa.Type
 		exp      string
@@ -434,7 +440,8 @@ L2:
 			p := b.CurrentBlock().AddParam(b, tc.typ)
 			m.cpuFeatures = tc.cpuFlags
 
-			ctx.definitions[p] = &backend.SSAValueDefinition{BlockParamValue: p, BlkParamVReg: raxVReg}
+			ctx.vRegMap[p] = raxVReg
+			ctx.definitions[p] = backend.SSAValueDefinition{V: p}
 			ctx.vRegMap[0] = rcxVReg
 			instr := &ssa.Instruction{}
 			instr.AsCtz(p)
diff --git a/internal/engine/wazevo/backend/isa/amd64/util_test.go b/internal/engine/wazevo/backend/isa/amd64/util_test.go
index 5344974a..d5bdd4f1 100644
--- a/internal/engine/wazevo/backend/isa/amd64/util_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/util_test.go
@@ -24,7 +24,7 @@ type mockCompiler struct {
 	currentGID  ssa.InstructionGroupID
 	vRegCounter int
 	vRegMap     map[ssa.Value]regalloc.VReg
-	definitions map[ssa.Value]*backend.SSAValueDefinition
+	definitions map[ssa.Value]backend.SSAValueDefinition
 	sigs        map[ssa.SignatureID]*ssa.Signature
 	typeOf      map[regalloc.VRegID]ssa.Type
 	relocs      []backend.RelocationInfo
@@ -76,7 +76,7 @@ func (m *mockCompiler) Init()                                {}
 func newMockCompilationContext() *mockCompiler { //nolint
 	return &mockCompiler{
 		vRegMap:     make(map[ssa.Value]regalloc.VReg),
-		definitions: make(map[ssa.Value]*backend.SSAValueDefinition),
+		definitions: make(map[ssa.Value]backend.SSAValueDefinition),
 		typeOf:      map[regalloc.VRegID]ssa.Type{},
 	}
 }
@@ -96,10 +96,10 @@ func (m *mockCompiler) AllocateVReg(typ ssa.Type) regalloc.VReg {
 }
 
 // ValueDefinition implements backend.Compiler.
-func (m *mockCompiler) ValueDefinition(value ssa.Value) *backend.SSAValueDefinition {
+func (m *mockCompiler) ValueDefinition(value ssa.Value) backend.SSAValueDefinition {
 	definition, exists := m.definitions[value]
 	if !exists {
-		return nil
+		return backend.SSAValueDefinition{V: value}
 	}
 	return definition
 }
@@ -114,7 +114,7 @@ func (m *mockCompiler) VRegOf(value ssa.Value) regalloc.VReg {
 }
 
 // MatchInstr implements backend.Compiler.
-func (m *mockCompiler) MatchInstr(def *backend.SSAValueDefinition, opcode ssa.Opcode) bool {
+func (m *mockCompiler) MatchInstr(def backend.SSAValueDefinition, opcode ssa.Opcode) bool {
 	instr := def.Instr
 	return def.IsFromInstr() &&
 		instr.Opcode() == opcode &&
@@ -123,7 +123,7 @@ func (m *mockCompiler) MatchInstr(def *backend.SSAValueDefinition, opcode ssa.Op
 }
 
 // MatchInstrOneOf implements backend.Compiler.
-func (m *mockCompiler) MatchInstrOneOf(def *backend.SSAValueDefinition, opcodes []ssa.Opcode) ssa.Opcode {
+func (m *mockCompiler) MatchInstrOneOf(def backend.SSAValueDefinition, opcodes []ssa.Opcode) ssa.Opcode {
 	for _, opcode := range opcodes {
 		if m.MatchInstr(def, opcode) {
 			return opcode
diff --git a/internal/engine/wazevo/backend/isa/arm64/abi_test.go b/internal/engine/wazevo/backend/isa/arm64/abi_test.go
index 6f36a60e..58b9e91d 100644
--- a/internal/engine/wazevo/backend/isa/arm64/abi_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/abi_test.go
@@ -41,8 +41,8 @@ func TestAbiImpl_callerGenVRegToFunctionArg_constant_inlining(t *testing.T) {
 	f64 := builder.AllocateInstruction().AsF64const(3.14).Insert(builder)
 	abi := &backend.FunctionABI{}
 	abi.Init(&ssa.Signature{Params: []ssa.Type{ssa.TypeI64, ssa.TypeF64}}, intParamResultRegs, floatParamResultRegs)
-	m.callerGenVRegToFunctionArg(abi, 0, regalloc.VReg(100).SetRegType(regalloc.RegTypeInt), &backend.SSAValueDefinition{Instr: i64, RefCount: 1}, 0)
-	m.callerGenVRegToFunctionArg(abi, 1, regalloc.VReg(50).SetRegType(regalloc.RegTypeFloat), &backend.SSAValueDefinition{Instr: f64, RefCount: 1}, 0)
+	m.callerGenVRegToFunctionArg(abi, 0, regalloc.VReg(100).SetRegType(regalloc.RegTypeInt), backend.SSAValueDefinition{Instr: i64, RefCount: 1}, 0)
+	m.callerGenVRegToFunctionArg(abi, 1, regalloc.VReg(50).SetRegType(regalloc.RegTypeFloat), backend.SSAValueDefinition{Instr: f64, RefCount: 1}, 0)
 	require.Equal(t, `movz x100?, #0xa, lsl 0
 mov x0, x100?
 ldr d50?, #8; b 16; data.f64 3.140000
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr_operands_test.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr_operands_test.go
index 11494652..1a78039f 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr_operands_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr_operands_test.go
@@ -31,26 +31,28 @@ func Test_asImm12(t *testing.T) {
 func TestMachine_getOperand_NR(t *testing.T) {
 	for _, tc := range []struct {
 		name         string
-		setup        func(*mockCompiler, ssa.Builder, *machine) (def *backend.SSAValueDefinition, mode extMode)
+		setup        func(*mockCompiler, ssa.Builder, *machine) (def backend.SSAValueDefinition, mode extMode)
 		exp          operand
 		instructions []string
 	}{
 		{
 			name: "block param - no extend",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode) {
 				blk := builder.CurrentBlock()
 				v := blk.AddParam(builder, ssa.TypeI64)
-				def = &backend.SSAValueDefinition{BlkParamVReg: regToVReg(x4), BlockParamValue: v}
+				ctx.vRegMap[v] = regToVReg(x4)
+				def = backend.SSAValueDefinition{V: v}
 				return def, extModeZeroExtend64
 			},
 			exp: operandNR(regToVReg(x4)),
 		},
 		{
 			name: "block param - zero extend",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode) {
 				blk := builder.CurrentBlock()
 				v := blk.AddParam(builder, ssa.TypeI32)
-				def = &backend.SSAValueDefinition{BlkParamVReg: regToVReg(x4), BlockParamValue: v}
+				ctx.vRegMap[v] = regToVReg(x4)
+				def = backend.SSAValueDefinition{V: v}
 				return def, extModeZeroExtend64
 			},
 			exp:          operandNR(regalloc.VReg(1).SetRegType(regalloc.RegTypeInt)),
@@ -58,10 +60,11 @@ func TestMachine_getOperand_NR(t *testing.T) {
 		},
 		{
 			name: "block param - sign extend",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode) {
 				blk := builder.CurrentBlock()
 				v := blk.AddParam(builder, ssa.TypeI32)
-				def = &backend.SSAValueDefinition{BlkParamVReg: regToVReg(x4), BlockParamValue: v}
+				ctx.vRegMap[v] = regToVReg(x4)
+				def = backend.SSAValueDefinition{V: v}
 				return def, extModeSignExtend64
 			},
 			exp:          operandNR(regalloc.VReg(1).SetRegType(regalloc.RegTypeInt)),
@@ -69,11 +72,11 @@ func TestMachine_getOperand_NR(t *testing.T) {
 		},
 		{
 			name: "const instr",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode) {
 				instr := builder.AllocateInstruction()
 				instr.AsIconst32(0xf00000f)
 				builder.InsertInstruction(instr)
-				def = &backend.SSAValueDefinition{Instr: instr, N: 0}
+				def = backend.SSAValueDefinition{Instr: instr, V: instr.Return()}
 				ctx.vRegCounter = 99
 				return
 			},
@@ -85,7 +88,7 @@ func TestMachine_getOperand_NR(t *testing.T) {
 		},
 		{
 			name: "non const instr",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode) {
 				c := builder.AllocateInstruction()
 				sig := &ssa.Signature{Results: []ssa.Type{ssa.TypeI64, ssa.TypeF64, ssa.TypeF64}}
 				builder.DeclareSignature(sig)
@@ -93,7 +96,7 @@ func TestMachine_getOperand_NR(t *testing.T) {
 				builder.InsertInstruction(c)
 				_, rs := c.Returns()
 				ctx.vRegMap[rs[1]] = regalloc.VReg(50)
-				def = &backend.SSAValueDefinition{Instr: c, N: 2}
+				def = backend.SSAValueDefinition{Instr: c, V: rs[1]}
 				return
 			},
 			exp: operandNR(regalloc.VReg(50)),
@@ -112,7 +115,7 @@ func TestMachine_getOperand_NR(t *testing.T) {
 func TestMachine_getOperand_SR_NR(t *testing.T) {
 	ishlWithConstAmount := func(
 		ctx *mockCompiler, builder ssa.Builder, m *machine,
-	) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
+	) (def backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
 		blk := builder.CurrentBlock()
 		// (p1+p2) << amount
 		p1 := blk.AddParam(builder, ssa.TypeI64)
@@ -132,14 +135,16 @@ func TestMachine_getOperand_SR_NR(t *testing.T) {
 		ishl.AsIshl(addResult, amountVal)
 		builder.InsertInstruction(ishl)
 
-		ctx.definitions[p1] = &backend.SSAValueDefinition{BlkParamVReg: regalloc.VReg(1), BlockParamValue: p1}
-		ctx.definitions[p2] = &backend.SSAValueDefinition{BlkParamVReg: regalloc.VReg(2), BlockParamValue: p2}
-		ctx.definitions[addResult] = &backend.SSAValueDefinition{Instr: add, N: 0}
-		ctx.definitions[amountVal] = &backend.SSAValueDefinition{Instr: amount, N: 0}
+		ctx.vRegMap[p1] = regalloc.VReg(1)
+		ctx.definitions[p1] = backend.SSAValueDefinition{V: p1}
+		ctx.vRegMap[p2] = regalloc.VReg(2)
+		ctx.definitions[p2] = backend.SSAValueDefinition{V: p2}
+		ctx.definitions[addResult] = backend.SSAValueDefinition{Instr: add, V: addResult}
+		ctx.definitions[amountVal] = backend.SSAValueDefinition{Instr: amount, V: amountVal}
 
 		ctx.vRegMap[addResult] = regalloc.VReg(1234)
 		ctx.vRegMap[ishl.Return()] = regalloc.VReg(10)
-		def = &backend.SSAValueDefinition{Instr: ishl, N: 0}
+		def = backend.SSAValueDefinition{Instr: ishl, V: ishl.Return()}
 		mode = extModeNone
 		verify = func(t *testing.T) {
 			require.True(t, ishl.Lowered())
@@ -150,23 +155,24 @@ func TestMachine_getOperand_SR_NR(t *testing.T) {
 
 	for _, tc := range []struct {
 		name         string
-		setup        func(*mockCompiler, ssa.Builder, *machine) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T))
+		setup        func(*mockCompiler, ssa.Builder, *machine) (def backend.SSAValueDefinition, mode extMode, verify func(t *testing.T))
 		exp          operand
 		instructions []string
 	}{
 		{
 			name: "block param",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
 				blk := builder.CurrentBlock()
 				v := blk.AddParam(builder, ssa.TypeI64)
-				def = &backend.SSAValueDefinition{BlkParamVReg: regToVReg(x4), BlockParamValue: v}
+				ctx.vRegMap[v] = regToVReg(x4)
+				def = backend.SSAValueDefinition{V: v}
 				return def, extModeNone, func(t *testing.T) {}
 			},
 			exp: operandNR(regToVReg(x4)),
 		},
 		{
 			name: "ishl but not const amount",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
 				blk := builder.CurrentBlock()
 				// (p1+p2) << p3
 				p1 := blk.AddParam(builder, ssa.TypeI64)
@@ -181,13 +187,16 @@ func TestMachine_getOperand_SR_NR(t *testing.T) {
 				ishl.AsIshl(addResult, p3)
 				builder.InsertInstruction(ishl)
 
-				ctx.definitions[p1] = &backend.SSAValueDefinition{BlkParamVReg: regalloc.VReg(1), BlockParamValue: p1}
-				ctx.definitions[p2] = &backend.SSAValueDefinition{BlkParamVReg: regalloc.VReg(2), BlockParamValue: p2}
-				ctx.definitions[p3] = &backend.SSAValueDefinition{BlkParamVReg: regalloc.VReg(3), BlockParamValue: p3}
-				ctx.definitions[addResult] = &backend.SSAValueDefinition{Instr: add, N: 0}
+				ctx.vRegMap[p1] = regalloc.VReg(1)
+				ctx.definitions[p1] = backend.SSAValueDefinition{V: p1}
+				ctx.vRegMap[p2] = regalloc.VReg(2)
+				ctx.definitions[p2] = backend.SSAValueDefinition{V: p2}
+				ctx.vRegMap[p3] = regalloc.VReg(3)
+				ctx.definitions[p3] = backend.SSAValueDefinition{V: p3}
+				ctx.definitions[addResult] = backend.SSAValueDefinition{Instr: add, V: addResult}
 				ctx.vRegMap[addResult] = regalloc.VReg(1234) // whatever is fine.
 				ctx.vRegMap[ishl.Return()] = regalloc.VReg(10)
-				def = &backend.SSAValueDefinition{Instr: ishl, N: 0}
+				def = backend.SSAValueDefinition{Instr: ishl, V: ishl.Return()}
 				return def, extModeNone, func(t *testing.T) {}
 			},
 			exp: operandNR(regalloc.VReg(10)),
@@ -201,7 +210,7 @@ func TestMachine_getOperand_SR_NR(t *testing.T) {
 			name: "ishl with const amount with i32",
 			setup: func(
 				ctx *mockCompiler, builder ssa.Builder, m *machine,
-			) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
+			) (def backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
 				blk := builder.CurrentBlock()
 				// (p1+p2) << amount
 				p1 := blk.AddParam(builder, ssa.TypeI32)
@@ -221,14 +230,16 @@ func TestMachine_getOperand_SR_NR(t *testing.T) {
 				ishl.AsIshl(addResult, amountVal)
 				builder.InsertInstruction(ishl)
 
-				ctx.definitions[p1] = &backend.SSAValueDefinition{BlkParamVReg: regalloc.VReg(1), BlockParamValue: p1}
-				ctx.definitions[p2] = &backend.SSAValueDefinition{BlkParamVReg: regalloc.VReg(2), BlockParamValue: p2}
-				ctx.definitions[addResult] = &backend.SSAValueDefinition{Instr: add, N: 0}
-				ctx.definitions[amountVal] = &backend.SSAValueDefinition{Instr: amount, N: 0}
+				ctx.vRegMap[p1] = regalloc.VReg(1)
+				ctx.definitions[p1] = backend.SSAValueDefinition{V: p1}
+				ctx.vRegMap[p2] = regalloc.VReg(2)
+				ctx.definitions[p2] = backend.SSAValueDefinition{V: p2}
+				ctx.definitions[addResult] = backend.SSAValueDefinition{Instr: add, V: addResult}
+				ctx.definitions[amountVal] = backend.SSAValueDefinition{Instr: amount, V: amountVal}
 
 				ctx.vRegMap[addResult] = regalloc.VReg(1234)
 				ctx.vRegMap[ishl.Return()] = regalloc.VReg(10)
-				def = &backend.SSAValueDefinition{Instr: ishl, N: 0}
+				def = backend.SSAValueDefinition{Instr: ishl}
 				mode = extModeNone
 				verify = func(t *testing.T) {
 					require.True(t, ishl.Lowered())
@@ -242,7 +253,7 @@ func TestMachine_getOperand_SR_NR(t *testing.T) {
 			name: "ishl with const amount with const shift target",
 			setup: func(
 				ctx *mockCompiler, builder ssa.Builder, m *machine,
-			) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
+			) (def backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
 				const nextVReg = 100
 				ctx.vRegCounter = nextVReg - 1
 
@@ -254,11 +265,11 @@ func TestMachine_getOperand_SR_NR(t *testing.T) {
 				ishl.AsIshl(target.Return(), amountVal)
 				builder.InsertInstruction(ishl)
 
-				ctx.definitions[targetVal] = &backend.SSAValueDefinition{Instr: target, N: 0}
-				ctx.definitions[amountVal] = &backend.SSAValueDefinition{Instr: amount, N: 0}
+				ctx.definitions[targetVal] = backend.SSAValueDefinition{Instr: target, V: targetVal}
+				ctx.definitions[amountVal] = backend.SSAValueDefinition{Instr: amount, V: amountVal}
 				ctx.vRegMap[targetVal] = regalloc.VReg(1234)
 				ctx.vRegMap[ishl.Return()] = regalloc.VReg(10)
-				def = &backend.SSAValueDefinition{Instr: ishl, N: 0}
+				def = backend.SSAValueDefinition{Instr: ishl}
 				mode = extModeNone
 				verify = func(t *testing.T) {
 					require.True(t, ishl.Lowered())
@@ -271,7 +282,7 @@ func TestMachine_getOperand_SR_NR(t *testing.T) {
 		},
 		{
 			name: "ishl with const amount but group id is different",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
 				def, mode, _ = ishlWithConstAmount(ctx, builder, m)
 				ctx.currentGID = 1230
 				return
@@ -280,7 +291,7 @@ func TestMachine_getOperand_SR_NR(t *testing.T) {
 		},
 		{
 			name: "ishl with const amount but ref count is larger than 1",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
 				def, mode, _ = ishlWithConstAmount(ctx, builder, m)
 				def.RefCount = 10
 				return
@@ -304,7 +315,7 @@ func TestMachine_getOperand_SR_NR(t *testing.T) {
 func TestMachine_getOperand_ER_SR_NR(t *testing.T) {
 	const nextVReg = 100
 	type testCase struct {
-		setup        func(*mockCompiler, ssa.Builder, *machine) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T))
+		setup        func(*mockCompiler, ssa.Builder, *machine) (def backend.SSAValueDefinition, mode extMode, verify func(t *testing.T))
 		exp          operand
 		instructions []string
 	}
@@ -320,10 +331,11 @@ func TestMachine_getOperand_ER_SR_NR(t *testing.T) {
 
 	t.Run("block param", func(t *testing.T) {
 		runner(testCase{
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
 				blk := builder.CurrentBlock()
 				v := blk.AddParam(builder, ssa.TypeI64)
-				def = &backend.SSAValueDefinition{BlkParamVReg: regToVReg(x4), BlockParamValue: v}
+				ctx.vRegMap[v] = regToVReg(x4)
+				def = backend.SSAValueDefinition{V: v}
 				return def, extModeZeroExtend64, func(t *testing.T) {}
 			},
 			exp: operandNR(regToVReg(x4)),
@@ -348,7 +360,7 @@ func TestMachine_getOperand_ER_SR_NR(t *testing.T) {
 			{from: 32, to: 64, signed: false, exp: operandER(regalloc.VReg(10), extendOpUXTW, 64)},
 		} {
 			runner(testCase{
-				setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
+				setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
 					blk := builder.CurrentBlock()
 					v := blk.AddParam(builder, ssa.TypeI64)
 					ext := builder.AllocateInstruction()
@@ -359,9 +371,9 @@ func TestMachine_getOperand_ER_SR_NR(t *testing.T) {
 					}
 					builder.InsertInstruction(ext)
 					extArg := ext.Arg()
-					ctx.vRegMap[ext.Arg()] = regalloc.VReg(10)
-					ctx.definitions[v] = &backend.SSAValueDefinition{BlkParamVReg: regalloc.VReg(10), BlockParamValue: extArg}
-					def = &backend.SSAValueDefinition{Instr: ext, N: 0}
+					ctx.vRegMap[extArg] = regalloc.VReg(10)
+					ctx.definitions[v] = backend.SSAValueDefinition{V: extArg}
+					def = backend.SSAValueDefinition{Instr: ext, V: ext.Return()}
 					return def, extModeNone, func(t *testing.T) {
 						require.True(t, ext.Lowered())
 					}
@@ -556,7 +568,7 @@ func TestMachine_getOperand_ER_SR_NR(t *testing.T) {
 		} {
 			t.Run(c.name, func(t *testing.T) {
 				runner(testCase{
-					setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
+					setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
 						blk := builder.CurrentBlock()
 						v := blk.AddParam(builder, ssa.TypeI64)
 						ext := builder.AllocateInstruction()
@@ -571,16 +583,11 @@ func TestMachine_getOperand_ER_SR_NR(t *testing.T) {
 						ctx.vRegMap[ext.Return()] = resultVReg
 						if c.extArgConst {
 							iconst := builder.AllocateInstruction().AsIconst32(0xffff).Insert(builder)
-							m.compiler.(*mockCompiler).definitions[extArg] = &backend.SSAValueDefinition{
-								Instr: iconst,
-							}
+							m.compiler.(*mockCompiler).definitions[extArg] = backend.SSAValueDefinition{Instr: iconst, V: iconst.Return()}
 						} else {
-							m.compiler.(*mockCompiler).definitions[extArg] = &backend.SSAValueDefinition{
-								BlkParamVReg:    argVReg,
-								BlockParamValue: extArg,
-							}
+							m.compiler.(*mockCompiler).definitions[extArg] = backend.SSAValueDefinition{V: extArg}
 						}
-						def = &backend.SSAValueDefinition{Instr: ext, N: 0}
+						def = backend.SSAValueDefinition{Instr: ext}
 						return def, c.mode, func(t *testing.T) {
 							require.Equal(t, c.lowered, ext.Lowered())
 						}
@@ -596,27 +603,28 @@ func TestMachine_getOperand_ER_SR_NR(t *testing.T) {
 func TestMachine_getOperand_Imm12_ER_SR_NR(t *testing.T) {
 	for _, tc := range []struct {
 		name         string
-		setup        func(*mockCompiler, ssa.Builder, *machine) (def *backend.SSAValueDefinition, mode extMode)
+		setup        func(*mockCompiler, ssa.Builder, *machine) (def backend.SSAValueDefinition, mode extMode)
 		exp          operand
 		instructions []string
 	}{
 		{
 			name: "block param",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode) {
 				blk := builder.CurrentBlock()
 				v := blk.AddParam(builder, ssa.TypeI64)
-				def = &backend.SSAValueDefinition{BlkParamVReg: regToVReg(x4), BlockParamValue: v}
+				ctx.vRegMap[v] = regToVReg(x4)
+				def = backend.SSAValueDefinition{V: v}
 				return def, extModeZeroExtend64
 			},
 			exp: operandNR(regToVReg(x4)),
 		},
 		{
 			name: "const imm 12 no shift",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode) {
 				cinst := builder.AllocateInstruction()
 				cinst.AsIconst32(0xfff)
 				builder.InsertInstruction(cinst)
-				def = &backend.SSAValueDefinition{Instr: cinst, N: 0}
+				def = backend.SSAValueDefinition{Instr: cinst}
 				ctx.currentGID = 0xff // const can be merged anytime, regardless of the group id.
 				return
 			},
@@ -624,11 +632,11 @@ func TestMachine_getOperand_Imm12_ER_SR_NR(t *testing.T) {
 		},
 		{
 			name: "const imm 12 with shift bit",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode) {
 				cinst := builder.AllocateInstruction()
 				cinst.AsIconst32(0xabc_000)
 				builder.InsertInstruction(cinst)
-				def = &backend.SSAValueDefinition{Instr: cinst, N: 0}
+				def = backend.SSAValueDefinition{Instr: cinst}
 				ctx.currentGID = 0xff // const can be merged anytime, regardless of the group id.
 				return
 			},
@@ -648,28 +656,29 @@ func TestMachine_getOperand_Imm12_ER_SR_NR(t *testing.T) {
 func TestMachine_getOperand_MaybeNegatedImm12_ER_SR_NR(t *testing.T) {
 	for _, tc := range []struct {
 		name         string
-		setup        func(*mockCompiler, ssa.Builder, *machine) (def *backend.SSAValueDefinition, mode extMode)
+		setup        func(*mockCompiler, ssa.Builder, *machine) (def backend.SSAValueDefinition, mode extMode)
 		exp          operand
 		expNegated   bool
 		instructions []string
 	}{
 		{
 			name: "block param",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode) {
 				blk := builder.CurrentBlock()
 				v := blk.AddParam(builder, ssa.TypeI64)
-				def = &backend.SSAValueDefinition{BlkParamVReg: regToVReg(x4), BlockParamValue: v}
+				ctx.vRegMap[v] = regToVReg(x4)
+				def = backend.SSAValueDefinition{V: v}
 				return def, extModeZeroExtend64
 			},
 			exp: operandNR(regToVReg(x4)),
 		},
 		{
 			name: "const imm 12 no shift",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode) {
 				cinst := builder.AllocateInstruction()
 				cinst.AsIconst32(0xfff)
 				builder.InsertInstruction(cinst)
-				def = &backend.SSAValueDefinition{Instr: cinst, N: 0}
+				def = backend.SSAValueDefinition{Instr: cinst}
 				ctx.currentGID = 0xff // const can be merged anytime, regardless of the group id.
 				return
 			},
@@ -677,12 +686,12 @@ func TestMachine_getOperand_MaybeNegatedImm12_ER_SR_NR(t *testing.T) {
 		},
 		{
 			name: "const negative imm 12 no shift",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode) {
 				c := int32(-1)
 				cinst := builder.AllocateInstruction()
 				cinst.AsIconst32(uint32(c))
 				builder.InsertInstruction(cinst)
-				def = &backend.SSAValueDefinition{Instr: cinst, N: 0}
+				def = backend.SSAValueDefinition{Instr: cinst, V: cinst.Return()}
 				ctx.currentGID = 0xff // const can be merged anytime, regardless of the group id.
 				return
 			},
@@ -691,11 +700,11 @@ func TestMachine_getOperand_MaybeNegatedImm12_ER_SR_NR(t *testing.T) {
 		},
 		{
 			name: "const imm 12 with shift bit",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode) {
 				cinst := builder.AllocateInstruction()
 				cinst.AsIconst32(0xabc_000)
 				builder.InsertInstruction(cinst)
-				def = &backend.SSAValueDefinition{Instr: cinst, N: 0}
+				def = backend.SSAValueDefinition{Instr: cinst}
 				ctx.currentGID = 0xff // const can be merged anytime, regardless of the group id.
 				return
 			},
@@ -703,12 +712,12 @@ func TestMachine_getOperand_MaybeNegatedImm12_ER_SR_NR(t *testing.T) {
 		},
 		{
 			name: "const negated imm 12 with shift bit",
-			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def backend.SSAValueDefinition, mode extMode) {
 				c := int32(-0xabc_000)
 				cinst := builder.AllocateInstruction()
 				cinst.AsIconst32(uint32(c))
 				builder.InsertInstruction(cinst)
-				def = &backend.SSAValueDefinition{Instr: cinst, N: 0}
+				def = backend.SSAValueDefinition{Instr: cinst, V: cinst.Return()}
 				ctx.currentGID = 0xff // const can be merged anytime, regardless of the group id.
 				return
 			},
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
index 524811d7..28101934 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
@@ -46,9 +46,9 @@ func TestMachine_LowerConditionalBranch(t *testing.T) {
 		cmpVal := cmpInstr.Return()
 		ctx.vRegMap[cmpVal] = 3
 
-		ctx.definitions[val1] = &backend.SSAValueDefinition{BlkParamVReg: ctx.vRegMap[val1], BlockParamValue: val1}
-		ctx.definitions[val2] = &backend.SSAValueDefinition{BlkParamVReg: ctx.vRegMap[val2], BlockParamValue: val2}
-		ctx.definitions[cmpVal] = &backend.SSAValueDefinition{Instr: cmpInstr}
+		ctx.definitions[val1] = backend.SSAValueDefinition{V: val1}
+		ctx.definitions[val2] = backend.SSAValueDefinition{V: val2}
+		ctx.definitions[cmpVal] = backend.SSAValueDefinition{Instr: cmpInstr}
 		b := builder.AllocateInstruction()
 		if brz {
 			b.AsBrz(cmpVal, ssa.ValuesNil, builder.AllocateBasicBlock())
@@ -74,9 +74,9 @@ func TestMachine_LowerConditionalBranch(t *testing.T) {
 		icmp.AsIcmp(v1, v2, ssa.IntegerCmpCondEqual)
 		builder.InsertInstruction(icmp)
 		icmpVal := icmp.Return()
-		ctx.definitions[v1] = &backend.SSAValueDefinition{BlkParamVReg: intToVReg(1), BlockParamValue: v1}
-		ctx.definitions[v2] = &backend.SSAValueDefinition{Instr: iconst}
-		ctx.definitions[icmpVal] = &backend.SSAValueDefinition{Instr: icmp}
+		ctx.definitions[v1] = backend.SSAValueDefinition{V: v1}
+		ctx.definitions[v2] = backend.SSAValueDefinition{Instr: iconst}
+		ctx.definitions[icmpVal] = backend.SSAValueDefinition{Instr: icmp}
 		ctx.vRegMap[v1], ctx.vRegMap[v2], ctx.vRegMap[icmpVal] = intToVReg(1), intToVReg(2), intToVReg(3)
 		b := builder.AllocateInstruction()
 		if brz {
@@ -105,7 +105,7 @@ func TestMachine_LowerConditionalBranch(t *testing.T) {
 				icmp.AsIcmp(v1, v2, ssa.IntegerCmpCondEqual)
 				builder.InsertInstruction(icmp)
 				icmpVal := icmp.Return()
-				ctx.definitions[icmpVal] = &backend.SSAValueDefinition{Instr: icmp}
+				ctx.definitions[icmpVal] = backend.SSAValueDefinition{Instr: icmp, V: icmpVal}
 				ctx.vRegMap[v1], ctx.vRegMap[v2], ctx.vRegMap[icmpVal] = intToVReg(1), intToVReg(2), intToVReg(3)
 
 				brz := builder.AllocateInstruction()
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_mem_test.go b/internal/engine/wazevo/backend/isa/arm64/lower_mem_test.go
index e7a054d7..f3a4384d 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_mem_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_mem_test.go
@@ -296,28 +296,29 @@ func TestMachine_collectAddends(t *testing.T) {
 	v1000, v2000 := regalloc.VReg(1000).SetRegType(regalloc.RegTypeInt), regalloc.VReg(2000).SetRegType(regalloc.RegTypeInt)
 	addParam := func(ctx *mockCompiler, b ssa.Builder, typ ssa.Type) ssa.Value {
 		p := b.CurrentBlock().AddParam(b, typ)
-		ctx.definitions[p] = &backend.SSAValueDefinition{BlockParamValue: p, BlkParamVReg: v1000}
+		ctx.vRegMap[p] = v1000
+		ctx.definitions[p] = backend.SSAValueDefinition{V: p}
 		return p
 	}
 	insertI32Const := func(m *mockCompiler, b ssa.Builder, v uint32) *ssa.Instruction {
 		inst := b.AllocateInstruction()
 		inst.AsIconst32(v)
 		b.InsertInstruction(inst)
-		m.definitions[inst.Return()] = &backend.SSAValueDefinition{Instr: inst}
+		m.definitions[inst.Return()] = backend.SSAValueDefinition{Instr: inst}
 		return inst
 	}
 	insertI64Const := func(m *mockCompiler, b ssa.Builder, v uint64) *ssa.Instruction {
 		inst := b.AllocateInstruction()
 		inst.AsIconst64(v)
 		b.InsertInstruction(inst)
-		m.definitions[inst.Return()] = &backend.SSAValueDefinition{Instr: inst}
+		m.definitions[inst.Return()] = backend.SSAValueDefinition{Instr: inst, V: inst.Return()}
 		return inst
 	}
 	insertIadd := func(m *mockCompiler, b ssa.Builder, lhs, rhs ssa.Value) *ssa.Instruction {
 		inst := b.AllocateInstruction()
 		inst.AsIadd(lhs, rhs)
 		b.InsertInstruction(inst)
-		m.definitions[inst.Return()] = &backend.SSAValueDefinition{Instr: inst}
+		m.definitions[inst.Return()] = backend.SSAValueDefinition{Instr: inst, V: inst.Return()}
 		return inst
 	}
 	insertExt := func(m *mockCompiler, b ssa.Builder, v ssa.Value, from, to byte, signed bool) *ssa.Instruction {
@@ -328,7 +329,7 @@ func TestMachine_collectAddends(t *testing.T) {
 			inst.AsUExtend(v, from, to)
 		}
 		b.InsertInstruction(inst)
-		m.definitions[inst.Return()] = &backend.SSAValueDefinition{Instr: inst}
+		m.definitions[inst.Return()] = backend.SSAValueDefinition{Instr: inst, V: v}
 		return inst
 	}
 
diff --git a/internal/engine/wazevo/backend/isa/arm64/util_test.go b/internal/engine/wazevo/backend/isa/arm64/util_test.go
index bac89885..6f44a852 100644
--- a/internal/engine/wazevo/backend/isa/arm64/util_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/util_test.go
@@ -55,7 +55,7 @@ type mockCompiler struct {
 	currentGID  ssa.InstructionGroupID
 	vRegCounter int
 	vRegMap     map[ssa.Value]regalloc.VReg
-	definitions map[ssa.Value]*backend.SSAValueDefinition
+	definitions map[ssa.Value]backend.SSAValueDefinition
 	sigs        map[ssa.SignatureID]*ssa.Signature
 	typeOf      map[regalloc.VRegID]ssa.Type
 	ssaBuilder  ssa.Builder
@@ -108,7 +108,7 @@ func (m *mockCompiler) Init()                                {}
 func newMockCompilationContext() *mockCompiler {
 	return &mockCompiler{
 		vRegMap:     make(map[ssa.Value]regalloc.VReg),
-		definitions: make(map[ssa.Value]*backend.SSAValueDefinition),
+		definitions: make(map[ssa.Value]backend.SSAValueDefinition),
 		typeOf:      map[regalloc.VRegID]ssa.Type{},
 	}
 }
@@ -128,10 +128,10 @@ func (m *mockCompiler) AllocateVReg(typ ssa.Type) regalloc.VReg {
 }
 
 // ValueDefinition implements backend.Compiler.
-func (m *mockCompiler) ValueDefinition(value ssa.Value) *backend.SSAValueDefinition {
+func (m *mockCompiler) ValueDefinition(value ssa.Value) backend.SSAValueDefinition {
 	definition, exists := m.definitions[value]
 	if !exists {
-		return nil
+		return backend.SSAValueDefinition{}
 	}
 	return definition
 }
@@ -146,7 +146,7 @@ func (m *mockCompiler) VRegOf(value ssa.Value) regalloc.VReg {
 }
 
 // MatchInstr implements backend.Compiler.
-func (m *mockCompiler) MatchInstr(def *backend.SSAValueDefinition, opcode ssa.Opcode) bool {
+func (m *mockCompiler) MatchInstr(def backend.SSAValueDefinition, opcode ssa.Opcode) bool {
 	instr := def.Instr
 	return def.IsFromInstr() &&
 		instr.Opcode() == opcode &&
@@ -155,7 +155,7 @@ func (m *mockCompiler) MatchInstr(def *backend.SSAValueDefinition, opcode ssa.Op
 }
 
 // MatchInstrOneOf implements backend.Compiler.
-func (m *mockCompiler) MatchInstrOneOf(def *backend.SSAValueDefinition, opcodes []ssa.Opcode) ssa.Opcode {
+func (m *mockCompiler) MatchInstrOneOf(def backend.SSAValueDefinition, opcodes []ssa.Opcode) ssa.Opcode {
 	for _, opcode := range opcodes {
 		if m.MatchInstr(def, opcode) {
 			return opcode
diff --git a/internal/engine/wazevo/ssa/builder_test.go b/internal/engine/wazevo/ssa/builder_test.go
new file mode 100644
index 00000000..94ee7e5e
--- /dev/null
+++ b/internal/engine/wazevo/ssa/builder_test.go
@@ -0,0 +1,26 @@
+package ssa
+
+import (
+	"testing"
+
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+func TestBuilder_resolveAlias(t *testing.T) {
+	b := NewBuilder().(*builder)
+	v1 := b.allocateValue(TypeI32)
+	v2 := b.allocateValue(TypeI32)
+	v3 := b.allocateValue(TypeI32)
+	v4 := b.allocateValue(TypeI32)
+	v5 := b.allocateValue(TypeI32)
+
+	b.alias(v1, v2)
+	b.alias(v2, v3)
+	b.alias(v3, v4)
+	b.alias(v4, v5)
+	require.Equal(t, v5, b.resolveAlias(v1))
+	require.Equal(t, v5, b.resolveAlias(v2))
+	require.Equal(t, v5, b.resolveAlias(v3))
+	require.Equal(t, v5, b.resolveAlias(v4))
+	require.Equal(t, v5, b.resolveAlias(v5))
+}
diff --git a/internal/engine/wazevo/ssa/pass_test.go b/internal/engine/wazevo/ssa/pass_test.go
index 015a10d0..4b7919b2 100644
--- a/internal/engine/wazevo/ssa/pass_test.go
+++ b/internal/engine/wazevo/ssa/pass_test.go
@@ -264,9 +264,9 @@ blk2: () <-- (blk1)
 					require.True(t, jmp.live)
 					require.True(t, ret.live)
 
-					require.Equal(t, 1, b.valueRefCounts[refOnceVal.ID()])
-					require.Equal(t, 1, b.valueRefCounts[addRes.ID()])
-					require.Equal(t, 3, b.valueRefCounts[refThriceVal.ID()])
+					require.Equal(t, uint32(1), b.valuesInfo[refOnceVal.ID()].RefCount)
+					require.Equal(t, uint32(1), b.valuesInfo[addRes.ID()].RefCount)
+					require.Equal(t, uint32(3), b.valuesInfo[refThriceVal.ID()].RefCount)
 				}
 			},
 			before: `
diff --git a/internal/engine/wazevo/ssa/vs_test.go b/internal/engine/wazevo/ssa/vs_test.go
new file mode 100644
index 00000000..322b1d91
--- /dev/null
+++ b/internal/engine/wazevo/ssa/vs_test.go
@@ -0,0 +1,14 @@
+package ssa
+
+import (
+	"testing"
+
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+func TestValue_InstructionID(t *testing.T) {
+	v := Value(1234).setType(TypeI32).setInstructionID(5678)
+	require.Equal(t, ValueID(1234), v.ID())
+	require.Equal(t, 5678, v.instructionID())
+	require.Equal(t, TypeI32, v.Type())
+}
diff --git a/internal/integration_test/stdlibs/bench_test.go b/internal/integration_test/stdlibs/bench_test.go
index 544cef8f..2c5f8634 100644
--- a/internal/integration_test/stdlibs/bench_test.go
+++ b/internal/integration_test/stdlibs/bench_test.go
@@ -103,7 +103,7 @@ var (
 				args = append(args,
 					"-test.skip=TestRenameCaseDifference/dir|"+
 						"TestDirFSPathsValid|TestDirFS|TestDevNullFile|"+
-						"TestOpenError|TestSymlinkWithTrailingSlash")
+						"TestOpenError|TestSymlinkWithTrailingSlash|TestCopyFS")
 			}
 			c = c.WithArgs(args...)
 
diff --git a/sys/stat_example_test.go b/sys/stat_example_test.go
deleted file mode 100644
index 9b75eb58..00000000
--- a/sys/stat_example_test.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package sys_test
-
-import (
-	"io/fs"
-	"math"
-
-	"github.com/tetratelabs/wazero/sys"
-)
-
-var (
-	walltime sys.Walltime
-	info     fs.FileInfo
-	st       sys.Stat_t
-)
-
-// This shows typical conversions to sys.EpochNanos type, for sys.Stat_t fields.
-func Example_epochNanos() {
-	// Convert an adapted fs.File's fs.FileInfo to Mtim.
-	st.Mtim = info.ModTime().UnixNano()
-
-	// Generate a fake Atim using sys.Walltime passed to wazero.ModuleConfig.
-	sec, nsec := walltime()
-	st.Atim = sec*1e9 + int64(nsec)
-}
-
-type fileInfoWithSys struct {
-	fs.FileInfo
-	st sys.Stat_t
-}
-
-func (f *fileInfoWithSys) Sys() any { return &f.st }
-
-// This shows how to return data not defined in fs.FileInfo, notably sys.Inode.
-func Example_inode() {
-	st := sys.NewStat_t(info)
-	st.Ino = math.MaxUint64 // arbitrary non-zero value
-	info = &fileInfoWithSys{info, st}
-}