Repository: tetratelabs/wazero
Pull request #2286 (2024-07-10 00:23:26)

ssa: removes valueIDToInstruction slice
```
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero
                      │  old.txt   │             new.txt              │
                      │   sec/op   │   sec/op    vs base              │
Compilation/wazero-10   1.564 ± 0%   1.536 ± 1%  -1.78% (p=0.002 n=6)
Compilation/zig-10      3.461 ± 0%   3.421 ± 0%  -1.13% (p=0.002 n=6)
Compilation/zz-10       14.28 ± 0%   14.06 ± 1%  -1.57% (p=0.002 n=6)
geomean                 4.260        4.196       -1.49%

                      │   old.txt    │              new.txt               │
                      │     B/op     │     B/op      vs base              │
Compilation/wazero-10   286.2Mi ± 0%   283.0Mi ± 0%  -1.13% (p=0.002 n=6)
Compilation/zig-10      598.2Mi ± 0%   596.7Mi ± 0%  -0.25% (p=0.002 n=6)
Compilation/zz-10       538.0Mi ± 0%   536.5Mi ± 0%  -0.28% (p=0.002 n=6)
geomean                 451.6Mi        449.1Mi       -0.55%

                      │   old.txt   │              new.txt              │
                      │  allocs/op  │  allocs/op   vs base              │
Compilation/wazero-10   485.5k ± 0%   485.3k ± 0%       ~ (p=0.394 n=6)
Compilation/zig-10      275.5k ± 0%   275.5k ± 0%       ~ (p=0.937 n=6)
Compilation/zz-10       627.0k ± 0%   627.0k ± 0%       ~ (p=0.589 n=6)
geomean                 437.7k        437.7k       -0.01%
```

#2182 


Patch:
diff --git a/internal/engine/wazevo/ssa/builder.go b/internal/engine/wazevo/ssa/builder.go
index 60deb535..950e5767 100644
--- a/internal/engine/wazevo/ssa/builder.go
+++ b/internal/engine/wazevo/ssa/builder.go
@@ -182,11 +182,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
@@ -291,7 +290,6 @@ func (b *builder) Init(s *Signature) {
 	for v := ValueID(0); v < b.nextValueID; v++ {
 		delete(b.valueAnnotations, v)
 		b.valuesInfo[v] = ValueInfo{alias: ValueInvalid}
-		b.valueIDToInstruction[v] = nil
 	}
 	b.nextValueID = 0
 	b.reversePostOrderedBasicBlocks = b.reversePostOrderedBasicBlocks[:0]
@@ -397,7 +395,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 {
@@ -406,7 +404,8 @@ 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
 }
@@ -776,3 +775,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 64dc5f5c..6ee26a3e 100644
--- a/internal/engine/wazevo/ssa/pass.go
+++ b/internal/engine/wazevo/ssa/pass.go
@@ -243,9 +243,6 @@ func passDeadCodeEliminationOpt(b *builder) {
 			view[i].alias = ValueInvalid
 		}
 	}
-	if nvid >= len(b.valueIDToInstruction) {
-		b.valueIDToInstruction = append(b.valueIDToInstruction, make([]*Instruction, nvid-len(b.valueIDToInstruction)+1)...)
-	}
 
 	// First, we gather all the instructions with side effects.
 	liveInstructions := b.instStack[:0]
@@ -264,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
-			}
 		}
 	}
 
@@ -292,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)
 			}
@@ -367,29 +356,13 @@ func (b *builder) incRefCount(id ValueID, from *Instruction) {
 
 // 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 2fd12cc6..d906e7e3 100644
--- a/internal/engine/wazevo/ssa/vs.go
+++ b/internal/engine/wazevo/ssa/vs.go
@@ -38,7 +38,8 @@ func (v Variable) getType() Type {
 // 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.
@@ -80,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.
@@ -90,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.


Test patch:
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())
+}