Repository: tetratelabs/wazero
Pull request #650 (2022-06-22 05:52:33)

compiler: remove embedding of pointers of jump tables
This removes the embedding of pointers of jump tables (uintptr of []byte)
used by BrTable operations. That is the last usage of unsafe.Pointer in
compiler implementations.
Alternatively, we treat jump tables as asm.StaticConst and emit them 
into the constPool already implemented and used by various places.


Notably, now the native code compiled by compilers can be reusable
across multiple processes, meaning that they are independent of 
any runtime pointers.

part of #618

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>


Patch:
diff --git a/builder.go b/builder.go
index c528d73e..74aa50e1 100644
--- a/builder.go
+++ b/builder.go
@@ -174,6 +174,19 @@ type ModuleBuilder interface {
 
 	// Instantiate is a convenience that calls Compile, then Namespace.InstantiateModule.
 	//
+	// Ex.
+	//
+	//	ctx := context.Background()
+	//	r := wazero.NewRuntime()
+	//	defer r.Close(ctx) // This closes everything this Runtime created.
+	//
+	//	hello := func() {
+	//		fmt.Fprintln(stdout, "hello!")
+	//	}
+	//	env, _ := r.NewModuleBuilder("env").
+	//		ExportFunction("hello", hello).
+	//		Instantiate(ctx, r)
+	//
 	// Notes
 	//
 	//	* Closing the Namespace has the same effect as closing the result.
diff --git a/internal/asm/amd64/assembler.go b/internal/asm/amd64/assembler.go
index 1191699f..0c84c085 100644
--- a/internal/asm/amd64/assembler.go
+++ b/internal/asm/amd64/assembler.go
@@ -90,9 +90,9 @@ type Assembler interface {
 
 	// CompileStaticConstToRegister adds an instruction where the source operand is asm.StaticConst located in the
 	// memory and the destination is the dstReg.
-	CompileStaticConstToRegister(instruction asm.Instruction, c asm.StaticConst, dstReg asm.Register) error
+	CompileStaticConstToRegister(instruction asm.Instruction, c *asm.StaticConst, dstReg asm.Register) error
 
 	// CompileRegisterToStaticConst adds an instruction where the destination operand is asm.StaticConst located in the
 	// memory and the source is the srcReg.
-	CompileRegisterToStaticConst(instruction asm.Instruction, srcReg asm.Register, c asm.StaticConst) error
+	CompileRegisterToStaticConst(instruction asm.Instruction, srcReg asm.Register, c *asm.StaticConst) error
 }
diff --git a/internal/asm/amd64/impl.go b/internal/asm/amd64/impl.go
index da52bd96..2c8ee3a0 100644
--- a/internal/asm/amd64/impl.go
+++ b/internal/asm/amd64/impl.go
@@ -39,7 +39,7 @@ type NodeImpl struct {
 	// JumpOrigins hold all the nodes trying to jump into this node. In other words, all the nodes with .JumpTarget == this.
 	JumpOrigins map[*NodeImpl]struct{}
 
-	staticConst asm.StaticConst
+	staticConst *asm.StaticConst
 }
 
 type NodeFlag byte
@@ -224,14 +224,14 @@ type AssemblerImpl struct {
 	// but have it as a field here for testability.
 	MaxDisplacementForConstantPool int
 
-	pool constPool
+	pool *asm.StaticConstPool
 }
 
 // compile-time check to ensure AssemblerImpl implements Assembler.
 var _ Assembler = &AssemblerImpl{}
 
 func NewAssemblerImpl() *AssemblerImpl {
-	return &AssemblerImpl{Buf: bytes.NewBuffer(nil), EnablePadding: true, pool: newConstPool(),
+	return &AssemblerImpl{Buf: bytes.NewBuffer(nil), EnablePadding: true, pool: asm.NewStaticConstPool(),
 		MaxDisplacementForConstantPool: defaultMaxDisplacementForConstantPool}
 }
 
diff --git a/internal/asm/amd64/impl_staticconst.go b/internal/asm/amd64/impl_staticconst.go
index f8e3f094..16700b30 100644
--- a/internal/asm/amd64/impl_staticconst.go
+++ b/internal/asm/amd64/impl_staticconst.go
@@ -8,36 +8,13 @@ import (
 	"github.com/tetratelabs/wazero/internal/asm"
 )
 
-type constPool struct {
-	firstUseOffsetInBinary *asm.NodeOffsetInBinary
-	consts                 []asm.StaticConst
-	poolSizeInBytes        int
-
-	// offsetFinalizedCallbacks are functions called when the offsets of the
-	// constants in the binary have been determined.
-	offsetFinalizedCallbacks map[string][]func(offsetOfConstInBinary int)
-}
-
-func newConstPool() constPool {
-	return constPool{offsetFinalizedCallbacks: map[string][]func(offsetOfConstInBinary int){}}
-}
-
-func (p *constPool) addConst(c asm.StaticConst) {
-	key := asm.StaticConstKey(c)
-	if _, ok := p.offsetFinalizedCallbacks[key]; !ok {
-		p.consts = append(p.consts, c)
-		p.poolSizeInBytes += len(c)
-		p.offsetFinalizedCallbacks[key] = []func(int){}
-	}
-}
-
 // defaultMaxDisplacementForConstantPool is the maximum displacement allowed for literal move instructions which access
 // the constant pool. This is set as 2 ^30 conservatively while the actual limit is 2^31 since we actually allow this
 // limit plus max(length(c) for c in the pool) so we must ensure that limit is less than 2^31.
 const defaultMaxDisplacementForConstantPool = 1 << 30
 
 func (a *AssemblerImpl) maybeFlushConstants(isEndOfFunction bool) {
-	if a.pool.firstUseOffsetInBinary == nil {
+	if a.pool.FirstUseOffsetInBinary == nil {
 		return
 	}
 
@@ -45,32 +22,29 @@ func (a *AssemblerImpl) maybeFlushConstants(isEndOfFunction bool) {
 		// If the distance between (the first use in binary) and (end of constant pool) can be larger
 		// than MaxDisplacementForConstantPool, we have to emit the constant pool now, otherwise
 		// a const might be unreachable by a literal move whose maximum offset is +- 2^31.
-		((a.pool.poolSizeInBytes+a.Buf.Len())-int(*a.pool.firstUseOffsetInBinary)) >= a.MaxDisplacementForConstantPool {
+		((a.pool.PoolSizeInBytes+a.Buf.Len())-int(*a.pool.FirstUseOffsetInBinary)) >= a.MaxDisplacementForConstantPool {
 		if !isEndOfFunction {
 			// Adds the jump instruction to skip the constants if this is not the end of function.
 			//
 			// TODO: consider NOP padding for this jump, though this rarely happens as most functions should be
 			// small enough to fit all consts after the end of function.
-			if a.pool.poolSizeInBytes >= math.MaxInt8-2 {
+			if a.pool.PoolSizeInBytes >= math.MaxInt8-2 {
 				// long (near-relative) jump: https://www.felixcloutier.com/x86/jmp
 				a.Buf.WriteByte(0xe9)
-				a.WriteConst(int64(a.pool.poolSizeInBytes), 32)
+				a.WriteConst(int64(a.pool.PoolSizeInBytes), 32)
 			} else {
 				// short jump: https://www.felixcloutier.com/x86/jmp
 				a.Buf.WriteByte(0xeb)
-				a.WriteConst(int64(a.pool.poolSizeInBytes), 8)
+				a.WriteConst(int64(a.pool.PoolSizeInBytes), 8)
 			}
 		}
 
-		for _, c := range a.pool.consts {
-			offset := a.Buf.Len()
-			a.Buf.Write(c)
-			for _, callback := range a.pool.offsetFinalizedCallbacks[asm.StaticConstKey(c)] {
-				callback(offset)
-			}
+		for _, c := range a.pool.Consts {
+			c.SetOffsetInBinary(uint64(a.Buf.Len()))
+			a.Buf.Write(c.Raw)
 		}
 
-		a.pool = newConstPool() // reset
+		a.pool = asm.NewStaticConstPool() // reset
 	}
 }
 
@@ -147,7 +121,7 @@ func (a *AssemblerImpl) encodeStaticConstToRegister(n *NodeImpl) (err error) {
 // encodeStaticConstImpl encodes an instruction where mod:r/m points to the memory location of the static constant n.staticConst,
 // and the other operand is the register given at n.SrcReg or n.DstReg.
 func (a *AssemblerImpl) encodeStaticConstImpl(n *NodeImpl, opcode []byte, rex RexPrefix, mandatoryPrefix byte) (err error) {
-	a.pool.addConst(n.staticConst)
+	a.pool.AddConst(n.staticConst, uint64(a.Buf.Len()))
 
 	var reg asm.Register
 	if n.DstReg != asm.NilRegister {
@@ -164,17 +138,12 @@ func (a *AssemblerImpl) encodeStaticConstImpl(n *NodeImpl, opcode []byte, rex Re
 	rexPrefix |= rex
 
 	var inst []byte
-	key := asm.StaticConstKey(n.staticConst)
-	a.pool.offsetFinalizedCallbacks[key] = append(a.pool.offsetFinalizedCallbacks[key],
-		func(offsetOfConstInBinary int) {
-			bin := a.Buf.Bytes()
-			displacement := offsetOfConstInBinary - int(n.OffsetInBinary()) - len(inst)
-			displacementOffsetInInstruction := n.OffsetInBinary() + uint64(len(inst)-4)
-			binary.LittleEndian.PutUint32(bin[displacementOffsetInInstruction:], uint32(int32(displacement)))
-		})
-
-	nodeOffset := uint64(a.Buf.Len())
-	a.pool.firstUseOffsetInBinary = &nodeOffset
+	n.staticConst.AddOffsetFinalizedCallback(func(offsetOfConstInBinary uint64) {
+		bin := a.Buf.Bytes()
+		displacement := int(offsetOfConstInBinary) - int(n.OffsetInBinary()) - len(inst)
+		displacementOffsetInInstruction := n.OffsetInBinary() + uint64(len(inst)-4)
+		binary.LittleEndian.PutUint32(bin[displacementOffsetInInstruction:], uint32(int32(displacement)))
+	})
 
 	// https://wiki.osdev.org/X86-64_Instruction_Encoding#32.2F64-bit_addressing
 	modRM := 0b00_000_101 | // Indicate "[RIP + 32bit displacement]" encoding.
@@ -198,9 +167,9 @@ func (a *AssemblerImpl) encodeStaticConstImpl(n *NodeImpl, opcode []byte, rex Re
 }
 
 // CompileStaticConstToRegister implements Assembler.CompileStaticConstToRegister.
-func (a *AssemblerImpl) CompileStaticConstToRegister(instruction asm.Instruction, c asm.StaticConst, dstReg asm.Register) (err error) {
-	if len(c)%2 != 0 {
-		err = fmt.Errorf("the length of a static constant must be even but was %d", len(c))
+func (a *AssemblerImpl) CompileStaticConstToRegister(instruction asm.Instruction, c *asm.StaticConst, dstReg asm.Register) (err error) {
+	if len(c.Raw)%2 != 0 {
+		err = fmt.Errorf("the length of a static constant must be even but was %d", len(c.Raw))
 		return
 	}
 
@@ -211,9 +180,9 @@ func (a *AssemblerImpl) CompileStaticConstToRegister(instruction asm.Instruction
 }
 
 // CompileRegisterToStaticConst implements Assembler.CompileRegisterToStaticConst.
-func (a *AssemblerImpl) CompileRegisterToStaticConst(instruction asm.Instruction, srcReg asm.Register, c asm.StaticConst) (err error) {
-	if len(c)%2 != 0 {
-		err = fmt.Errorf("the length of a static constant must be even but was %d", len(c))
+func (a *AssemblerImpl) CompileRegisterToStaticConst(instruction asm.Instruction, srcReg asm.Register, c *asm.StaticConst) (err error) {
+	if len(c.Raw)%2 != 0 {
+		err = fmt.Errorf("the length of a static constant must be even but was %d", len(c.Raw))
 		return
 	}
 
diff --git a/internal/asm/arm64/assembler.go b/internal/asm/arm64/assembler.go
index 316d7cce..218db416 100644
--- a/internal/asm/arm64/assembler.go
+++ b/internal/asm/arm64/assembler.go
@@ -96,9 +96,13 @@ type Assembler interface {
 	CompileVectorRegisterToVectorRegisterWithConst(instruction asm.Instruction, srcReg, dstReg asm.Register,
 		arrangement VectorArrangement, c asm.ConstantValue)
 
-	// CompileLoadStaticConstToVectorRegister adds an instruction where the source operand is StaticConstant located in
+	// CompileStaticConstToRegister adds an instruction where the source operand is StaticConstant located in
 	// the memory and the destination is the dstReg.
-	CompileLoadStaticConstToVectorRegister(instruction asm.Instruction, c asm.StaticConst, dstReg asm.Register,
+	CompileStaticConstToRegister(instruction asm.Instruction, c *asm.StaticConst, dstReg asm.Register)
+
+	// CompileStaticConstToVectorRegister adds an instruction where the source operand is StaticConstant located in
+	// the memory and the destination is the dstReg.
+	CompileStaticConstToVectorRegister(instruction asm.Instruction, c *asm.StaticConst, dstReg asm.Register,
 		arrangement VectorArrangement)
 
 	// CompileTwoVectorRegistersToVectorRegister adds an instruction where source are two vectors and destination is one
diff --git a/internal/asm/arm64/impl.go b/internal/asm/arm64/impl.go
index 6ff17a12..9f55041d 100644
--- a/internal/asm/arm64/impl.go
+++ b/internal/asm/arm64/impl.go
@@ -36,7 +36,7 @@ type NodeImpl struct {
 	// JumpOrigins hold all the nodes trying to jump into this node. In other words, all the nodes with .JumpTarget == this.
 	JumpOrigins map[*NodeImpl]struct{}
 
-	staticConst asm.StaticConst
+	staticConst *asm.StaticConst
 }
 
 // AssignJumpTarget implements the same method as documented on asm.Node.
@@ -211,29 +211,16 @@ type AssemblerImpl struct {
 	Buf               *bytes.Buffer
 	temporaryRegister asm.Register
 	nodeCount         int
-	pool              constPool
+	pool              *asm.StaticConstPool
 	// MaxDisplacementForConstantPool is fixed to defaultMaxDisplacementForConstPool
 	// but have it as a field here for testability.
 	MaxDisplacementForConstantPool int
 }
 
-// constPool holds asm.StaticConst which are used by ldr(literal) instructions
-// emitted by memory access.
-type constPool struct {
-	// firstUseOffsetInBinary is the offset of the first ldr(literal) instruction
-	// which needs to access the const in this constPool.
-	firstUseOffsetInBinary *asm.NodeOffsetInBinary
-	consts                 []asm.StaticConst
-	constSizeInBytes       int
-	// offsetFinalizedCallbacks are functions called when the offsets of the
-	// constants in the binary have been determined.
-	offsetFinalizedCallbacks map[string][]func(offsetOfConstInBinary int)
-}
-
 func NewAssemblerImpl(temporaryRegister asm.Register) *AssemblerImpl {
 	return &AssemblerImpl{
 		Buf: bytes.NewBuffer(nil), temporaryRegister: temporaryRegister,
-		pool:                           constPool{offsetFinalizedCallbacks: map[string][]func(int){}},
+		pool:                           asm.NewStaticConstPool(),
 		MaxDisplacementForConstantPool: defaultMaxDisplacementForConstPool,
 	}
 }
@@ -299,7 +286,7 @@ const defaultMaxDisplacementForConstPool = (1 << 20) - 1 - 4 // -4 for unconditi
 
 // maybeFlushConstPool flushes the constant pool if endOfBinary or a boundary condition was met.
 func (a *AssemblerImpl) maybeFlushConstPool(endOfBinary bool) {
-	if a.pool.firstUseOffsetInBinary == nil {
+	if a.pool.FirstUseOffsetInBinary == nil {
 		return
 	}
 
@@ -308,13 +295,13 @@ func (a *AssemblerImpl) maybeFlushConstPool(endOfBinary bool) {
 	if endOfBinary ||
 		// Also, if the offset between the first usage of the constant pool and
 		// the first constant would exceed 2^20 -1(= 2MiB-1), which is the maximum offset
-		// for load(literal) instruction, flush all the constants in the pool.
-		(a.Buf.Len()+a.pool.constSizeInBytes-int(*a.pool.firstUseOffsetInBinary)) >= a.MaxDisplacementForConstantPool {
+		// for LDR(literal)/ADR instruction, flush all the constants in the pool.
+		(a.Buf.Len()+a.pool.PoolSizeInBytes-int(*a.pool.FirstUseOffsetInBinary)) >= a.MaxDisplacementForConstantPool {
 
 		// Before emitting consts, we have to add br instruction to skip the const pool.
 		// https://github.com/golang/go/blob/release-branch.go1.15/src/cmd/internal/obj/arm64/asm7.go#L1123-L1129
-		skipOffset := a.pool.constSizeInBytes/4 + 1
-		if a.pool.constSizeInBytes%4 != 0 {
+		skipOffset := a.pool.PoolSizeInBytes/4 + 1
+		if a.pool.PoolSizeInBytes%4 != 0 {
 			skipOffset++
 		}
 		if endOfBinary {
@@ -331,14 +318,9 @@ func (a *AssemblerImpl) maybeFlushConstPool(endOfBinary bool) {
 		})
 
 		// Then adding the consts into the binary.
-		for _, c := range a.pool.consts {
-			offsetOfConst := a.Buf.Len()
-			a.Buf.Write(c)
-
-			// Invoke callbacks for `c` with the offset of binary where we store `c`.
-			for _, cb := range a.pool.offsetFinalizedCallbacks[asm.StaticConstKey(c)] {
-				cb(offsetOfConst)
-			}
+		for _, c := range a.pool.Consts {
+			c.SetOffsetInBinary(uint64(a.Buf.Len()))
+			a.Buf.Write(c.Raw)
 		}
 
 		// arm64 instructions are 4-byte (32-bit) aligned, so we must pad the zero consts here.
@@ -347,25 +329,7 @@ func (a *AssemblerImpl) maybeFlushConstPool(endOfBinary bool) {
 		}
 
 		// After the flush, reset the constant pool.
-		a.pool = constPool{offsetFinalizedCallbacks: map[string][]func(int){}}
-	}
-}
-
-func (a *AssemblerImpl) setConstPoolCallback(c asm.StaticConst, cb func(int)) {
-	key := asm.StaticConstKey(c)
-	a.pool.offsetFinalizedCallbacks[key] = append(a.pool.offsetFinalizedCallbacks[key], cb)
-}
-
-func (a *AssemblerImpl) addConstPool(c asm.StaticConst, useOffset asm.NodeOffsetInBinary) {
-	if a.pool.firstUseOffsetInBinary == nil {
-		a.pool.firstUseOffsetInBinary = &useOffset
-	}
-
-	key := asm.StaticConstKey(c)
-	if _, ok := a.pool.offsetFinalizedCallbacks[key]; !ok {
-		a.pool.consts = append(a.pool.consts, c)
-		a.pool.offsetFinalizedCallbacks[key] = []func(int){}
-		a.pool.constSizeInBytes += len(c)
+		a.pool = asm.NewStaticConstPool()
 	}
 }
 
@@ -672,9 +636,16 @@ func (a *AssemblerImpl) CompileVectorRegisterToVectorRegisterWithConst(instructi
 	n.VectorArrangement = arrangement
 }
 
-// CompileLoadStaticConstToVectorRegister implements Assembler.CompileLoadStaticConstToVectorRegister
-func (a *AssemblerImpl) CompileLoadStaticConstToVectorRegister(instruction asm.Instruction,
-	c asm.StaticConst, dstReg asm.Register, arrangement VectorArrangement) {
+// CompileStaticConstToRegister implements Assembler.CompileStaticConstToVectorRegister
+func (a *AssemblerImpl) CompileStaticConstToRegister(instruction asm.Instruction, c *asm.StaticConst, dstReg asm.Register) {
+	n := a.newNode(instruction, OperandTypesMemoryToRegister)
+	n.staticConst = c
+	n.DstReg = dstReg
+}
+
+// CompileStaticConstToVectorRegister implements Assembler.CompileStaticConstToVectorRegister
+func (a *AssemblerImpl) CompileStaticConstToVectorRegister(instruction asm.Instruction,
+	c *asm.StaticConst, dstReg asm.Register, arrangement VectorArrangement) {
 	n := a.newNode(instruction, OperandTypesStaticConstToVectorRegister)
 	n.staticConst = c
 	n.DstReg = dstReg
@@ -1722,9 +1693,9 @@ func (a *AssemblerImpl) encodeLoadOrStoreWithConstOffset(
 	// Go's assembler adds a const into the const pool at this point,
 	// regardless of its usage; e.g. if we enter the then block of the following if statement,
 	// the const is not used but it is added into the const pool.
-	var c = make([]byte, 4)
-	binary.LittleEndian.PutUint32(c, uint32(offset))
-	a.addConstPool(c, uint64(a.Buf.Len()))
+	var c = asm.NewStaticConst(make([]byte, 4))
+	binary.LittleEndian.PutUint32(c.Raw, uint32(offset))
+	a.pool.AddConst(c, uint64(a.Buf.Len()))
 
 	// https://github.com/golang/go/blob/release-branch.go1.15/src/cmd/internal/obj/arm64/asm7.go#L3529-L3532
 	// If the offset is within 24-bits, we can load it with two ADD instructions.
@@ -1758,9 +1729,10 @@ func (a *AssemblerImpl) encodeLoadOrStoreWithConstOffset(
 		a.Buf.Write([]byte{tmpRegBits, 0x0, 0x0, 0b00_011_0_00})
 
 		// Set the callback for the constant, and we set properly the offset in the callback.
-		a.setConstPoolCallback(c, func(offsetOfConst int) {
+
+		c.AddOffsetFinalizedCallback(func(offsetOfConst uint64) {
 			// ldr(literal) encodes offset divided by 4.
-			offset := (offsetOfConst - int(loadLiteralOffsetInBinary)) / 4
+			offset := (int(offsetOfConst) - int(loadLiteralOffsetInBinary)) / 4
 			bin := a.Buf.Bytes()
 			bin[loadLiteralOffsetInBinary] |= byte(offset << 5)
 			bin[loadLiteralOffsetInBinary+1] |= byte(offset >> 3)
@@ -1837,11 +1809,32 @@ func (a *AssemblerImpl) encodeADR(n *NodeImpl) (err error) {
 		return err
 	}
 
-	// At this point, we don't yet know the target instruction's offset,
+	adrInstructionOffsetInBinary := uint64(a.Buf.Len())
+
+	// At this point, we don't yet know the target offset to read from,
 	// so we emit the ADR instruction with 0 offset, and replace later in the callback.
 	// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADR--Form-PC-relative-address-?lang=en
 	a.Buf.Write([]byte{dstRegBits, 0x0, 0x0, 0b10000})
 
+	// This case, the ADR's target offset is for the staticConst's initial address.
+	if sc := n.staticConst; sc != nil {
+		a.pool.AddConst(sc, adrInstructionOffsetInBinary)
+		sc.AddOffsetFinalizedCallback(func(offsetOfConst uint64) {
+			adrInstructionBytes := a.Buf.Bytes()[adrInstructionOffsetInBinary : adrInstructionOffsetInBinary+4]
+			offset := int(offsetOfConst) - int(adrInstructionOffsetInBinary)
+
+			// See https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADR--Form-PC-relative-address-?lang=en
+			adrInstructionBytes[3] |= byte(offset & 0b00000011 << 5)
+			offset >>= 2
+			adrInstructionBytes[0] |= byte(offset << 5)
+			offset >>= 3
+			adrInstructionBytes[1] |= byte(offset)
+			offset >>= 8
+			adrInstructionBytes[2] |= byte(offset)
+		})
+		return
+	}
+
 	a.AddOnGenerateCallBack(func(code []byte) error {
 		// Find the target instruction node.
 		targetNode := n
@@ -2737,9 +2730,6 @@ func (a *AssemblerImpl) EncodeStaticConstToVectorRegister(n *NodeImpl) (err erro
 		return err
 	}
 
-	loadLiteralOffsetInBinary := uint64(a.Buf.Len())
-	a.addConstPool(n.staticConst, loadLiteralOffsetInBinary)
-
 	// LDR (literal, SIMD&FP)
 	// https://developer.arm.com/documentation/ddi0596/2020-12/SIMD-FP-Instructions/LDR--literal--SIMD-FP---Load-SIMD-FP-Register--PC-relative-literal--
 	var opc byte
@@ -2753,15 +2743,18 @@ func (a *AssemblerImpl) EncodeStaticConstToVectorRegister(n *NodeImpl) (err erro
 		opc, constLength = 0b10, 16
 	}
 
-	if len(n.staticConst) != constLength {
+	loadLiteralOffsetInBinary := uint64(a.Buf.Len())
+	a.pool.AddConst(n.staticConst, loadLiteralOffsetInBinary)
+
+	if len(n.staticConst.Raw) != constLength {
 		return fmt.Errorf("invalid const length for %s: want %d but was %d",
-			n.VectorArrangement, constLength, len(n.staticConst))
+			n.VectorArrangement, constLength, len(n.staticConst.Raw))
 	}
 
 	a.Buf.Write([]byte{dstRegBits, 0x0, 0x0, opc<<6 | 0b11100})
-	a.setConstPoolCallback(n.staticConst, func(offsetOfConst int) {
+	n.staticConst.AddOffsetFinalizedCallback(func(offsetOfConst uint64) {
 		// LDR (literal, SIMD&FP) encodes offset divided by 4.
-		offset := (offsetOfConst - int(loadLiteralOffsetInBinary)) / 4
+		offset := (int(offsetOfConst) - int(loadLiteralOffsetInBinary)) / 4
 		bin := a.Buf.Bytes()
 		bin[loadLiteralOffsetInBinary] |= byte(offset << 5)
 		bin[loadLiteralOffsetInBinary+1] |= byte(offset >> 3)
diff --git a/internal/asm/assembler.go b/internal/asm/assembler.go
index ff9d4b98..c7f96f5e 100644
--- a/internal/asm/assembler.go
+++ b/internal/asm/assembler.go
@@ -53,11 +53,63 @@ type ConstantValue = int64
 
 // StaticConst represents an arbitrary constant bytes which are pooled and emitted by assembler into the binary.
 // These constants can be referenced by instructions.
-type StaticConst = []byte
+type StaticConst struct {
+	Raw []byte
+	// OffsetInBinary is the offset of this static const in the result binary.
+	offsetInBinary uint64
+	// offsetFinalizedCallbacks holds callbacks which are called when .OffsetInBinary is finalized by assembler implementation.
+	offsetFinalizedCallbacks []func(offsetOfConstInBinary uint64)
+}
+
+// NewStaticConst returns the pointer to the new NewStaticConst for given bytes.
+func NewStaticConst(raw []byte) *StaticConst {
+	return &StaticConst{Raw: raw}
+}
+
+// AddOffsetFinalizedCallback adds a callback into offsetFinalizedCallbacks.
+func (s *StaticConst) AddOffsetFinalizedCallback(cb func(offsetOfConstInBinary uint64)) {
+	s.offsetFinalizedCallbacks = append(s.offsetFinalizedCallbacks, cb)
+}
+
+// SetOffsetInBinary finalizes the offset of this StaticConst, and invokes callbacks.
+func (s *StaticConst) SetOffsetInBinary(offset uint64) {
+	s.offsetInBinary = offset
+	for _, cb := range s.offsetFinalizedCallbacks {
+		cb(offset)
+	}
+}
 
-// StaticConstKey returns a string whose underlying bytes equal the original const.
-func StaticConstKey(c StaticConst) string {
-	return string(c)
+// StaticConstPool holds a bulk of StaticConst which are yet to be emitted into the binary.
+type StaticConstPool struct {
+	// FirstUseOffsetInBinary holds the offset of the first instruction which accesses this const pool .
+	FirstUseOffsetInBinary *NodeOffsetInBinary
+	Consts                 []*StaticConst
+	// addedConsts is used to deduplicate the consts to reduce the final size of binary.
+	// Note: we can use map on .consts field and remove this field,
+	// but we have the separate field for deduplication in order to have deterministic assembling behavior.
+	addedConsts map[*StaticConst]struct{}
+	// PoolSizeInBytes is the current size of the pool in bytes.
+	PoolSizeInBytes int
+}
+
+// NewStaticConstPool returns the pointer to a new StaticConstPool.
+func NewStaticConstPool() *StaticConstPool {
+	return &StaticConstPool{addedConsts: map[*StaticConst]struct{}{}}
+}
+
+// AddConst adds a *StaticConst into the pool if it's not already added.
+func (p *StaticConstPool) AddConst(c *StaticConst, useOffset NodeOffsetInBinary) {
+	if _, ok := p.addedConsts[c]; ok {
+		return
+	}
+
+	if p.FirstUseOffsetInBinary == nil {
+		p.FirstUseOffsetInBinary = &useOffset
+	}
+
+	p.Consts = append(p.Consts, c)
+	p.PoolSizeInBytes += len(c.Raw)
+	p.addedConsts[c] = struct{}{}
 }
 
 // AssemblerBase is the common interface for assemblers among multiple architectures.
@@ -78,10 +130,8 @@ type AssemblerBase interface {
 
 	// BuildJumpTable calculates the offsets between the first instruction `initialInstructions[0]`
 	// and others (e.g. initialInstructions[3]), and wrote the calculated offsets into pre-allocated
-	// `table` slice in little endian.
-	//
-	// TODO: This can be hidden into assembler implementation after golang-asm removal.
-	BuildJumpTable(table []byte, initialInstructions []Node)
+	// `table` StaticConst in little endian.
+	BuildJumpTable(table *StaticConst, initialInstructions []Node)
 
 	// CompileStandAlone adds an instruction to take no arguments.
 	CompileStandAlone(instruction Instruction) Node
diff --git a/internal/asm/impl.go b/internal/asm/impl.go
index 0659b664..81cf796b 100644
--- a/internal/asm/impl.go
+++ b/internal/asm/impl.go
@@ -29,16 +29,17 @@ func (a *BaseAssemblerImpl) AddOnGenerateCallBack(cb func([]byte) error) {
 }
 
 // BuildJumpTable implements AssemblerBase.BuildJumpTable
-func (a *BaseAssemblerImpl) BuildJumpTable(table []byte, labelInitialInstructions []Node) {
+func (a *BaseAssemblerImpl) BuildJumpTable(table *StaticConst, labelInitialInstructions []Node) {
 	a.AddOnGenerateCallBack(func(code []byte) error {
 		// Compile the offset table for each target.
 		base := labelInitialInstructions[0].OffsetInBinary()
 		for i, nop := range labelInitialInstructions {
-			if uint64(nop.OffsetInBinary())-uint64(base) >= JumpTableMaximumOffset {
+			if nop.OffsetInBinary()-base >= JumpTableMaximumOffset {
 				return fmt.Errorf("too large br_table")
 			}
 			// We store the offset from the beginning of the L0's initial instruction.
-			binary.LittleEndian.PutUint32(table[i*4:(i+1)*4], uint32(nop.OffsetInBinary())-uint32(base))
+			binary.LittleEndian.PutUint32(code[table.offsetInBinary+uint64(i*4):table.offsetInBinary+uint64((i+1)*4)],
+				uint32(nop.OffsetInBinary())-uint32(base))
 		}
 		return nil
 	})
diff --git a/internal/engine/compiler/compiler.go b/internal/engine/compiler/compiler.go
index 3fd689e2..41b19815 100644
--- a/internal/engine/compiler/compiler.go
+++ b/internal/engine/compiler/compiler.go
@@ -14,8 +14,7 @@ type compiler interface {
 	compilePreamble() error
 	// compile generates the byte slice of native code.
 	// stackPointerCeil is the max stack pointer that the target function would reach.
-	// staticData is codeStaticData for the resulting native code.
-	compile() (code []byte, staticData codeStaticData, stackPointerCeil uint64, err error)
+	compile() (code []byte, stackPointerCeil uint64, err error)
 	// compileHostFunction adds the trampoline code from which native code can jump into the host function.
 	// TODO: maybe we wouldn't need to have trampoline for host functions.
 	compileHostFunction() error
diff --git a/internal/engine/compiler/engine.go b/internal/engine/compiler/engine.go
index e68c5da1..4baae839 100644
--- a/internal/engine/compiler/engine.go
+++ b/internal/engine/compiler/engine.go
@@ -193,7 +193,6 @@ type (
 		// codeSegment is holding the compiled native code as a byte slice.
 		codeSegment []byte
 		// See the doc for codeStaticData type.
-		staticData codeStaticData
 		// stackPointerCeil is the max of the stack pointer this function can reach. Lazily applied via maybeGrowValueStack.
 		stackPointerCeil uint64
 
@@ -202,12 +201,6 @@ type (
 		// sourceModule is the module from which this function is compiled. For logging purpose.
 		sourceModule *wasm.Module
 	}
-
-	// staticData holds the read-only data (i.e. outside codeSegment which is marked as executable) per function.
-	// This is used to store jump tables for br_table instructions.
-	// The primary index is the logical separation of multiple data, for example data[0] and data[1]
-	// correspond to different jump tables for different br_table instructions.
-	codeStaticData = [][]byte
 )
 
 // createFunction creates a new function which uses the native code compiled.
@@ -837,7 +830,7 @@ func compileHostFunction(sig *wasm.FunctionType) (*code, error) {
 		return nil, err
 	}
 
-	c, _, _, err := compiler.compile()
+	c, _, err := compiler.compile()
 	if err != nil {
 		return nil, err
 	}
@@ -1156,10 +1149,10 @@ func compileWasmFunction(_ wasm.Features, ir *wazeroir.CompilationResult) (*code
 		}
 	}
 
-	c, staticData, stackPointerCeil, err := compiler.compile()
+	c, stackPointerCeil, err := compiler.compile()
 	if err != nil {
 		return nil, fmt.Errorf("failed to compile: %w", err)
 	}
 
-	return &code{codeSegment: c, stackPointerCeil: stackPointerCeil, staticData: staticData}, nil
+	return &code{codeSegment: c, stackPointerCeil: stackPointerCeil}, nil
 }
diff --git a/internal/engine/compiler/impl_amd64.go b/internal/engine/compiler/impl_amd64.go
index 0d19d7c3..3ae69347 100644
--- a/internal/engine/compiler/impl_amd64.go
+++ b/internal/engine/compiler/impl_amd64.go
@@ -10,7 +10,6 @@ import (
 	"fmt"
 	"math"
 	"runtime"
-	"unsafe"
 
 	"github.com/tetratelabs/wazero/internal/asm"
 	"github.com/tetratelabs/wazero/internal/asm/amd64"
@@ -96,7 +95,6 @@ type amd64Compiler struct {
 	currentLabel string
 	// onStackPointerCeilDeterminedCallBack hold a callback which are called when the max stack pointer is determined BEFORE generating native code.
 	onStackPointerCeilDeterminedCallBack func(stackPointerCeil uint64)
-	staticData                           codeStaticData
 }
 
 func newAmd64Compiler(ir *wazeroir.CompilationResult) (compiler, error) {
@@ -120,10 +118,6 @@ func (c *amd64Compiler) setLocationStack(newStack *runtimeValueLocationStack) {
 	c.locationStack = newStack
 }
 
-func (c *amd64Compiler) addStaticData(d []byte) {
-	c.staticData = append(c.staticData, d)
-}
-
 func (c *amd64Compiler) pushRuntimeValueLocationOnRegister(reg asm.Register, vt runtimeValueType) (ret *runtimeValueLocation) {
 	ret = c.locationStack.pushRuntimeValueLocationOnRegister(reg, vt)
 	c.locationStack.markRegisterUsed(reg)
@@ -167,7 +161,7 @@ func (c *amd64Compiler) compileHostFunction() error {
 }
 
 // compile implements compiler.compile for the amd64 architecture.
-func (c *amd64Compiler) compile() (code []byte, staticData codeStaticData, stackPointerCeil uint64, err error) {
+func (c *amd64Compiler) compile() (code []byte, stackPointerCeil uint64, err error) {
 	// c.stackPointerCeil tracks the stack pointer ceiling (max seen) value across all runtimeValueLocationStack(s)
 	// used for all labels (via setLocationStack), excluding the current one.
 	// Hence, we check here if the final block's max one exceeds the current c.stackPointerCeil.
@@ -192,8 +186,6 @@ func (c *amd64Compiler) compile() (code []byte, staticData codeStaticData, stack
 	if err != nil {
 		return
 	}
-
-	staticData = c.staticData
 	return
 }
 
@@ -566,10 +558,12 @@ func (c *amd64Compiler) compileBrTable(o *wazeroir.OperationBrTable) error {
 	// the above example's offsetData would be [0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0].
 	//
 	// Note: this is similar to how GCC implements Switch statements in C.
-	offsetData := make([]byte, 4*(len(o.Targets)+1))
-	c.addStaticData(offsetData)
+	offsetData := asm.NewStaticConst(make([]byte, 4*(len(o.Targets)+1)))
 
-	c.assembler.CompileConstToRegister(amd64.MOVQ, int64(uintptr(unsafe.Pointer(&offsetData[0]))), tmp)
+	// Load the offsetData's address into tmp.
+	if err = c.assembler.CompileStaticConstToRegister(amd64.LEAQ, offsetData, tmp); err != nil {
+		return err
+	}
 
 	// Now we have the address of first byte of offsetData in tmp register.
 	// So the target offset's first byte is at tmp+index*4 as we store
@@ -1481,11 +1475,13 @@ func (c *amd64Compiler) performDivisionOnInts(isRem, is32Bit, signed bool) error
 		// Next we check if the quotient is the most negative value for the signed integer.
 		// That means whether or not we try to do (math.MaxInt32 / -1) or (math.Math.Int64 / -1) respectively.
 		if is32Bit {
-			if err := c.assembler.CompileRegisterToStaticConst(amd64.CMPL, x1.register, u32.LeBytes(uint32(minimum32BitSignedInt))); err != nil {
+			if err := c.assembler.CompileRegisterToStaticConst(amd64.CMPL, x1.register,
+				asm.NewStaticConst(u32.LeBytes(uint32(minimum32BitSignedInt)))); err != nil {
 				return err
 			}
 		} else {
-			if err := c.assembler.CompileRegisterToStaticConst(amd64.CMPQ, x1.register, u64.LeBytes(uint64(minimum64BitSignedInt))); err != nil {
+			if err := c.assembler.CompileRegisterToStaticConst(amd64.CMPQ, x1.register,
+				asm.NewStaticConst(u64.LeBytes(uint64(minimum64BitSignedInt)))); err != nil {
 				return err
 			}
 		}
@@ -1742,13 +1738,13 @@ func (c *amd64Compiler) compileNeg(o *wazeroir.OperationNeg) (err error) {
 	// since we cannot take XOR directly with float reg and const.
 	// And then negate the value by XOR it with the sign-bit mask.
 	if o.Type == wazeroir.Float32 {
-		err = c.assembler.CompileStaticConstToRegister(amd64.MOVL, u32.LeBytes(float32SignBitMask), tmpReg)
+		err = c.assembler.CompileStaticConstToRegister(amd64.MOVL, asm.NewStaticConst(u32.LeBytes(float32SignBitMask)), tmpReg)
 		if err != nil {
 			return err
 		}
 		c.assembler.CompileRegisterToRegister(amd64.XORPS, tmpReg, target.register)
 	} else {
-		err = c.assembler.CompileStaticConstToRegister(amd64.MOVQ, u64.LeBytes(float64SignBitMask), tmpReg)
+		err = c.assembler.CompileStaticConstToRegister(amd64.MOVQ, asm.NewStaticConst(u64.LeBytes(float64SignBitMask)), tmpReg)
 		if err != nil {
 			return err
 		}
@@ -1912,9 +1908,9 @@ func (c *amd64Compiler) compileCopysign(o *wazeroir.OperationCopysign) error {
 
 	// Move the rest bit mask to the temp register.
 	if is32Bit {
-		err = c.assembler.CompileStaticConstToRegister(amd64.MOVL, u32.LeBytes(float32RestBitMask), tmpReg)
+		err = c.assembler.CompileStaticConstToRegister(amd64.MOVL, asm.NewStaticConst(u32.LeBytes(float32RestBitMask)), tmpReg)
 	} else {
-		err = c.assembler.CompileStaticConstToRegister(amd64.MOVQ, u64.LeBytes(float64RestBitMask), tmpReg)
+		err = c.assembler.CompileStaticConstToRegister(amd64.MOVQ, asm.NewStaticConst(u64.LeBytes(float64RestBitMask)), tmpReg)
 	}
 	if err != nil {
 		return err
@@ -1929,9 +1925,9 @@ func (c *amd64Compiler) compileCopysign(o *wazeroir.OperationCopysign) error {
 
 	// Move the sign bit mask to the temp register.
 	if is32Bit {
-		err = c.assembler.CompileStaticConstToRegister(amd64.MOVL, u32.LeBytes(float32SignBitMask), tmpReg)
+		err = c.assembler.CompileStaticConstToRegister(amd64.MOVL, asm.NewStaticConst(u32.LeBytes(float32SignBitMask)), tmpReg)
 	} else {
-		err = c.assembler.CompileStaticConstToRegister(amd64.MOVQ, u64.LeBytes(float64SignBitMask), tmpReg)
+		err = c.assembler.CompileStaticConstToRegister(amd64.MOVQ, asm.NewStaticConst(u64.LeBytes(float64SignBitMask)), tmpReg)
 	}
 	if err != nil {
 		return err
@@ -2025,9 +2021,11 @@ func (c *amd64Compiler) emitUnsignedI32TruncFromFloat(isFloat32Bit, nonTrapping
 
 	// First, we check the source float value is above or equal math.MaxInt32+1.
 	if isFloat32Bit {
-		err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISS, u32.LeBytes(float32ForMaximumSigned32bitIntPlusOne), source.register)
+		err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISS,
+			asm.NewStaticConst(u32.LeBytes(float32ForMaximumSigned32bitIntPlusOne)), source.register)
 	} else {
-		err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISD, u64.LeBytes(float64ForMaximumSigned32bitIntPlusOne), source.register)
+		err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISD,
+			asm.NewStaticConst(u64.LeBytes(float64ForMaximumSigned32bitIntPlusOne)), source.register)
 	}
 	if err != nil {
 		return err
@@ -2082,9 +2080,11 @@ func (c *amd64Compiler) emitUnsignedI32TruncFromFloat(isFloat32Bit, nonTrapping
 	// First, we subtract the math.MaxInt32+1 from the original value so it can fit in signed 32-bit integer.
 	c.assembler.SetJumpTargetOnNext(jmpAboveOrEqualMaxIn32PlusOne)
 	if isFloat32Bit {
-		err = c.assembler.CompileStaticConstToRegister(amd64.SUBSS, u32.LeBytes(float32ForMaximumSigned32bitIntPlusOne), source.register)
+		err = c.assembler.CompileStaticConstToRegister(amd64.SUBSS,
+			asm.NewStaticConst(u32.LeBytes(float32ForMaximumSigned32bitIntPlusOne)), source.register)
 	} else {
-		err = c.assembler.CompileStaticConstToRegister(amd64.SUBSD, u64.LeBytes(float64ForMaximumSigned32bitIntPlusOne), source.register)
+		err = c.assembler.CompileStaticConstToRegister(amd64.SUBSD,
+			asm.NewStaticConst(u64.LeBytes(float64ForMaximumSigned32bitIntPlusOne)), source.register)
 	}
 	if err != nil {
 		return err
@@ -2107,7 +2107,8 @@ func (c *amd64Compiler) emitUnsignedI32TruncFromFloat(isFloat32Bit, nonTrapping
 
 	// Otherwise, we successfully converted the source float minus (math.MaxInt32+1) to int.
 	// So, we retrieve the original source float value by adding the sign mask.
-	if err = c.assembler.CompileStaticConstToRegister(amd64.ADDL, u32.LeBytes(float32SignBitMask), result); err != nil {
+	if err = c.assembler.CompileStaticConstToRegister(amd64.ADDL,
+		asm.NewStaticConst(u32.LeBytes(float32SignBitMask)), result); err != nil {
 		return err
 	}
 
@@ -2117,7 +2118,8 @@ func (c *amd64Compiler) emitUnsignedI32TruncFromFloat(isFloat32Bit, nonTrapping
 	if !nonTrapping {
 		c.compileExitFromNativeCode(nativeCallStatusIntegerOverflow)
 	} else {
-		err = c.assembler.CompileStaticConstToRegister(amd64.MOVL, u32.LeBytes(maximum32BitUnsignedInt), result)
+		err = c.assembler.CompileStaticConstToRegister(amd64.MOVL,
+			asm.NewStaticConst(u32.LeBytes(maximum32BitUnsignedInt)), result)
 		if err != nil {
 			return err
 		}
@@ -2150,9 +2152,11 @@ func (c *amd64Compiler) emitUnsignedI64TruncFromFloat(isFloat32Bit, nonTrapping
 
 	// First, we check the source float value is above or equal math.MaxInt64+1.
 	if isFloat32Bit {
-		err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISS, u32.LeBytes(float32ForMaximumSigned64bitIntPlusOne), source.register)
+		err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISS,
+			asm.NewStaticConst(u32.LeBytes(float32ForMaximumSigned64bitIntPlusOne)), source.register)
 	} else {
-		err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISD, u64.LeBytes(float64ForMaximumSigned64bitIntPlusOne), source.register)
+		err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISD,
+			asm.NewStaticConst(u64.LeBytes(float64ForMaximumSigned64bitIntPlusOne)), source.register)
 	}
 	if err != nil {
 		return err
@@ -2207,9 +2211,11 @@ func (c *amd64Compiler) emitUnsignedI64TruncFromFloat(isFloat32Bit, nonTrapping
 	// First, we subtract the math.MaxInt64+1 from the original value so it can fit in signed 64-bit integer.
 	c.assembler.SetJumpTargetOnNext(jmpAboveOrEqualMaxIn32PlusOne)
 	if isFloat32Bit {
-		err = c.assembler.CompileStaticConstToRegister(amd64.SUBSS, u32.LeBytes(float32ForMaximumSigned64bitIntPlusOne), source.register)
+		err = c.assembler.CompileStaticConstToRegister(amd64.SUBSS,
+			asm.NewStaticConst(u32.LeBytes(float32ForMaximumSigned64bitIntPlusOne)), source.register)
 	} else {
-		err = c.assembler.CompileStaticConstToRegister(amd64.SUBSD, u64.LeBytes(float64ForMaximumSigned64bitIntPlusOne), source.register)
+		err = c.assembler.CompileStaticConstToRegister(amd64.SUBSD,
+			asm.NewStaticConst(u64.LeBytes(float64ForMaximumSigned64bitIntPlusOne)), source.register)
 	}
 	if err != nil {
 		return err
@@ -2232,7 +2238,8 @@ func (c *amd64Compiler) emitUnsignedI64TruncFromFloat(isFloat32Bit, nonTrapping
 
 	// Otherwise, we successfully converted the the source float minus (math.MaxInt64+1) to int.
 	// So, we retrieve the original source float value by adding the sign mask.
-	if err = c.assembler.CompileStaticConstToRegister(amd64.ADDQ, u64.LeBytes(float64SignBitMask), result); err != nil {
+	if err = c.assembler.CompileStaticConstToRegister(amd64.ADDQ,
+		asm.NewStaticConst(u64.LeBytes(float64SignBitMask)), result); err != nil {
 		return err
 	}
 
@@ -2242,7 +2249,8 @@ func (c *amd64Compiler) emitUnsignedI64TruncFromFloat(isFloat32Bit, nonTrapping
 	if !nonTrapping {
 		c.compileExitFromNativeCode(nativeCallStatusIntegerOverflow)
 	} else {
-		err = c.assembler.CompileStaticConstToRegister(amd64.MOVQ, u64.LeBytes(maximum64BitUnsignedInt), result)
+		err = c.assembler.CompileStaticConstToRegister(amd64.MOVQ,
+			asm.NewStaticConst(u64.LeBytes(maximum64BitUnsignedInt)), result)
 		if err != nil {
 			return err
 		}
@@ -2284,7 +2292,7 @@ func (c *amd64Compiler) emitSignedI32TruncFromFloat(isFloat32Bit, nonTrapping bo
 	// 1) the source float value is either +-Inf or NaN, or it exceeds representative ranges of 32bit signed integer, or
 	// 2) the source equals the minimum signed 32-bit (=-2147483648.000000) whose bit pattern is float32ForMinimumSigned32bitIntegerAddress for 32 bit float
 	// 	  or float64ForMinimumSigned32bitIntegerAddress for 64bit float.
-	err = c.assembler.CompileStaticConstToRegister(amd64.CMPL, u32.LeBytes(float32SignBitMask), result)
+	err = c.assembler.CompileStaticConstToRegister(amd64.CMPL, asm.NewStaticConst(u32.LeBytes(float32SignBitMask)), result)
 	if err != nil {
 		return err
 	}
@@ -2318,9 +2326,11 @@ func (c *amd64Compiler) emitSignedI32TruncFromFloat(isFloat32Bit, nonTrapping bo
 	// meaning that the value exceeds the lower bound of 32-bit signed integer range.
 	c.assembler.SetJumpTargetOnNext(jmpIfNotNaN)
 	if isFloat32Bit {
-		err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISS, u32.LeBytes(float32ForMinimumSigned32bitInteger), source.register)
+		err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISS,
+			asm.NewStaticConst(u32.LeBytes(float32ForMinimumSigned32bitInteger)), source.register)
 	} else {
-		err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISD, u64.LeBytes(float64ForMinimumSigned32bitInteger), source.register)
+		err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISD,
+			asm.NewStaticConst(u64.LeBytes(float64ForMinimumSigned32bitInteger)), source.register)
 	}
 	if err != nil {
 		return err
@@ -2338,9 +2348,11 @@ func (c *amd64Compiler) emitSignedI32TruncFromFloat(isFloat32Bit, nonTrapping bo
 		// At this point, the value is the minimum signed 32-bit int (=-2147483648.000000) or larger than 32-bit maximum.
 		// So, check if the value equals the minimum signed 32-bit int.
 		if isFloat32Bit {
-			err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISS, []byte{0, 0, 0, 0}, source.register)
+			err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISS,
+				asm.NewStaticConst([]byte{0, 0, 0, 0}), source.register)
 		} else {
-			err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISD, []byte{0, 0, 0, 0, 0, 0, 0, 0}, source.register)
+			err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISD,
+				asm.NewStaticConst([]byte{0, 0, 0, 0, 0, 0, 0, 0}), source.register)
 		}
 		if err != nil {
 			return err
@@ -2363,7 +2375,8 @@ func (c *amd64Compiler) emitSignedI32TruncFromFloat(isFloat32Bit, nonTrapping bo
 		}
 
 		// If the value exceeds the lower bound, we "saturate" it to the minimum.
-		if err = c.assembler.CompileStaticConstToRegister(amd64.MOVL, u32.LeBytes(uint32(minimum32BitSignedInt)), result); err != nil {
+		if err = c.assembler.CompileStaticConstToRegister(amd64.MOVL,
+			asm.NewStaticConst(u32.LeBytes(uint32(minimum32BitSignedInt))), result); err != nil {
 			return err
 		}
 		nonTrappingSaturatedMinimumJump := c.assembler.CompileJump(amd64.JMP)
@@ -2371,9 +2384,11 @@ func (c *amd64Compiler) emitSignedI32TruncFromFloat(isFloat32Bit, nonTrapping bo
 		// Otherwise, the value is the minimum signed 32-bit int (=-2147483648.000000) or larger than 32-bit maximum.
 		c.assembler.SetJumpTargetOnNext(jmpIfNotExceedsLowerBound)
 		if isFloat32Bit {
-			err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISS, []byte{0, 0, 0, 0}, source.register)
+			err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISS,
+				asm.NewStaticConst([]byte{0, 0, 0, 0}), source.register)
 		} else {
-			err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISD, []byte{0, 0, 0, 0, 0, 0, 0, 0}, source.register)
+			err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISD,
+				asm.NewStaticConst([]byte{0, 0, 0, 0, 0, 0, 0, 0}), source.register)
 		}
 		if err != nil {
 			return err
@@ -2381,7 +2396,8 @@ func (c *amd64Compiler) emitSignedI32TruncFromFloat(isFloat32Bit, nonTrapping bo
 		jmpIfMinimumSignedInt := c.assembler.CompileJump(amd64.JCS) // jump if the value is minus (= the minimum signed 32-bit int).
 
 		// If the value exceeds signed 32-bit maximum, we saturate it to the maximum.
-		if err = c.assembler.CompileStaticConstToRegister(amd64.MOVL, u32.LeBytes(uint32(maximum32BitSignedInt)), result); err != nil {
+		if err = c.assembler.CompileStaticConstToRegister(amd64.MOVL,
+			asm.NewStaticConst(u32.LeBytes(uint32(maximum32BitSignedInt))), result); err != nil {
 			return err
 		}
 
@@ -2418,7 +2434,8 @@ func (c *amd64Compiler) emitSignedI64TruncFromFloat(isFloat32Bit, nonTrapping bo
 	// 1) the source float value is either +-Inf or NaN, or it exceeds representative ranges of 32bit signed integer, or
 	// 2) the source equals the minimum signed 32-bit (=-9223372036854775808.0) whose bit pattern is float32ForMinimumSigned64bitIntegerAddress for 32 bit float
 	// 	  or float64ForMinimumSigned64bitIntegerAddress for 64bit float.
-	err = c.assembler.CompileStaticConstToRegister(amd64.CMPQ, u64.LeBytes(float64SignBitMask), result)
+	err = c.assembler.CompileStaticConstToRegister(amd64.CMPQ,
+		asm.NewStaticConst(u64.LeBytes(float64SignBitMask)), result)
 	if err != nil {
 		return err
 	}
@@ -2451,9 +2468,11 @@ func (c *amd64Compiler) emitSignedI64TruncFromFloat(isFloat32Bit, nonTrapping bo
 	// meaning that the value exceeds the lower bound of 64-bit signed integer range.
 	c.assembler.SetJumpTargetOnNext(jmpIfNotNaN)
 	if isFloat32Bit {
-		err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISS, u32.LeBytes(float32ForMinimumSigned64bitInteger), source.register)
+		err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISS,
+			asm.NewStaticConst(u32.LeBytes(float32ForMinimumSigned64bitInteger)), source.register)
 	} else {
-		err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISD, u64.LeBytes(float64ForMinimumSigned64bitInteger), source.register)
+		err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISD,
+			asm.NewStaticConst(u64.LeBytes(float64ForMinimumSigned64bitInteger)), source.register)
 	}
 	if err != nil {
 		return err
@@ -2466,9 +2485,11 @@ func (c *amd64Compiler) emitSignedI64TruncFromFloat(isFloat32Bit, nonTrapping bo
 		// At this point, the value is the minimum signed 64-bit int (=-9223372036854775808.0) or larger than 64-bit maximum.
 		// So, check if the value equals the minimum signed 64-bit int.
 		if isFloat32Bit {
-			err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISS, []byte{0, 0, 0, 0}, source.register)
+			err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISS,
+				asm.NewStaticConst([]byte{0, 0, 0, 0}), source.register)
 		} else {
-			err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISD, []byte{0, 0, 0, 0, 0, 0, 0, 0}, source.register)
+			err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISD,
+				asm.NewStaticConst([]byte{0, 0, 0, 0, 0, 0, 0, 0}), source.register)
 		}
 		if err != nil {
 			return err
@@ -2486,7 +2507,8 @@ func (c *amd64Compiler) emitSignedI64TruncFromFloat(isFloat32Bit, nonTrapping bo
 		jmpIfNotExceedsLowerBound := c.assembler.CompileJump(amd64.JCC)
 
 		// If the value exceeds the lower bound, we "saturate" it to the minimum.
-		err = c.assembler.CompileStaticConstToRegister(amd64.MOVQ, u64.LeBytes(uint64(minimum64BitSignedInt)), result)
+		err = c.assembler.CompileStaticConstToRegister(amd64.MOVQ,
+			asm.NewStaticConst(u64.LeBytes(uint64(minimum64BitSignedInt))), result)
 		if err != nil {
 			return err
 		}
@@ -2497,9 +2519,9 @@ func (c *amd64Compiler) emitSignedI64TruncFromFloat(isFloat32Bit, nonTrapping bo
 		// So, check if the value equals the minimum signed 64-bit int.
 		c.assembler.SetJumpTargetOnNext(jmpIfNotExceedsLowerBound)
 		if isFloat32Bit {
-			err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISS, []byte{0, 0, 0, 0}, source.register)
+			err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISS, asm.NewStaticConst([]byte{0, 0, 0, 0}), source.register)
 		} else {
-			err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISD, []byte{0, 0, 0, 0, 0, 0, 0, 0}, source.register)
+			err = c.assembler.CompileStaticConstToRegister(amd64.UCOMISD, asm.NewStaticConst([]byte{0, 0, 0, 0, 0, 0, 0, 0}), source.register)
 		}
 		if err != nil {
 			return err
@@ -2508,7 +2530,7 @@ func (c *amd64Compiler) emitSignedI64TruncFromFloat(isFloat32Bit, nonTrapping bo
 		jmpIfMinimumSignedInt := c.assembler.CompileJump(amd64.JCS) // jump if the value is minus (= the minimum signed 64-bit int).
 
 		// If the value exceeds signed 64-bit maximum, we saturate it to the maximum.
-		if err = c.assembler.CompileStaticConstToRegister(amd64.MOVQ, u64.LeBytes(uint64(maximum64BitSignedInt)), result); err != nil {
+		if err = c.assembler.CompileStaticConstToRegister(amd64.MOVQ, asm.NewStaticConst(u64.LeBytes(uint64(maximum64BitSignedInt))), result); err != nil {
 			return err
 		}
 
@@ -2910,9 +2932,9 @@ func (c *amd64Compiler) compileEqz(o *wazeroir.OperationEqz) (err error) {
 
 	switch o.Type {
 	case wazeroir.UnsignedInt32:
-		err = c.assembler.CompileStaticConstToRegister(amd64.CMPL, []byte{0, 0, 0, 0}, v.register)
+		err = c.assembler.CompileStaticConstToRegister(amd64.CMPL, asm.NewStaticConst([]byte{0, 0, 0, 0}), v.register)
 	case wazeroir.UnsignedInt64:
-		err = c.assembler.CompileStaticConstToRegister(amd64.CMPQ, []byte{0, 0, 0, 0, 0, 0, 0, 0}, v.register)
+		err = c.assembler.CompileStaticConstToRegister(amd64.CMPQ, asm.NewStaticConst([]byte{0, 0, 0, 0, 0, 0, 0, 0}), v.register)
 	}
 	if err != nil {
 		return err
diff --git a/internal/engine/compiler/impl_arm64.go b/internal/engine/compiler/impl_arm64.go
index fad9c7ed..3c512dbe 100644
--- a/internal/engine/compiler/impl_arm64.go
+++ b/internal/engine/compiler/impl_arm64.go
@@ -13,7 +13,6 @@ import (
 	"errors"
 	"fmt"
 	"math"
-	"unsafe"
 
 	"github.com/tetratelabs/wazero/internal/asm"
 	"github.com/tetratelabs/wazero/internal/asm/arm64"
@@ -34,9 +33,6 @@ type arm64Compiler struct {
 	stackPointerCeil uint64
 	// onStackPointerCeilDeterminedCallBack hold a callback which are called when the ceil of stack pointer is determined before generating native code.
 	onStackPointerCeilDeterminedCallBack func(stackPointerCeil uint64)
-	// codeStaticData holds br_table offset tables.
-	// See codeStaticData and arm64Compiler.compileBrTable.
-	staticData codeStaticData
 }
 
 func newArm64Compiler(ir *wazeroir.CompilationResult) (compiler, error) {
@@ -100,12 +96,8 @@ func isZeroRegister(r asm.Register) bool {
 	return r == arm64.RegRZR
 }
 
-func (c *arm64Compiler) addStaticData(d []byte) {
-	c.staticData = append(c.staticData, d)
-}
-
 // compile implements compiler.compile for the arm64 architecture.
-func (c *arm64Compiler) compile() (code []byte, staticData codeStaticData, stackPointerCeil uint64, err error) {
+func (c *arm64Compiler) compile() (code []byte, stackPointerCeil uint64, err error) {
 	// c.stackPointerCeil tracks the stack pointer ceiling (max seen) value across all runtimeValueLocationStack(s)
 	// used for all labels (via setLocationStack), excluding the current one.
 	// Hence, we check here if the final block's max one exceeds the current c.stackPointerCeil.
@@ -130,8 +122,6 @@ func (c *arm64Compiler) compile() (code []byte, staticData codeStaticData, stack
 	if err != nil {
 		return
 	}
-
-	staticData = c.staticData
 	return
 }
 
@@ -819,7 +809,7 @@ func (c *arm64Compiler) compileBrTable(o *wazeroir.OperationBrTable) error {
 	c.assembler.CompileRegisterToRegister(arm64.MOVWU, tmpReg, index.register)
 	c.assembler.SetJumpTargetOnNext(brDefaultIndex)
 
-	// We prepare the static data which holds the offset of
+	// We prepare the asm.StaticConst which holds the offset of
 	// each target's first instruction (incl. default)
 	// relative to the beginning of label tables.
 	//
@@ -844,16 +834,10 @@ func (c *arm64Compiler) compileBrTable(o *wazeroir.OperationBrTable) error {
 	// the above example's offsetData would be [0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0].
 	//
 	// Note: this is similar to how GCC implements Switch statements in C.
-	offsetData := make([]byte, 4*(len(o.Targets)+1))
-	c.addStaticData(offsetData)
+	offsetData := asm.NewStaticConst(make([]byte, 4*(len(o.Targets)+1)))
 
 	// "tmpReg = &offsetData[0]"
-	c.assembler.CompileConstToRegister(
-		arm64.MOVD,
-		// Note: this should be modified to support Clone() functionality per #179.
-		int64(uintptr(unsafe.Pointer(&offsetData[0]))),
-		tmpReg,
-	)
+	c.assembler.CompileStaticConstToRegister(arm64.ADR, offsetData, tmpReg)
 
 	// "index.register = tmpReg + (index.register << 2) (== &offsetData[offset])"
 	c.assembler.CompileLeftShiftedRegisterToRegister(arm64.ADD, index.register, 2, tmpReg, index.register)
diff --git a/internal/engine/compiler/impl_vec_amd64.go b/internal/engine/compiler/impl_vec_amd64.go
index 3e59bea0..872d18c8 100644
--- a/internal/engine/compiler/impl_vec_amd64.go
+++ b/internal/engine/compiler/impl_vec_amd64.go
@@ -495,12 +495,12 @@ func (c *amd64Compiler) compileV128Shuffle(o *wazeroir.OperationV128Shuffle) err
 		}
 	}
 
-	err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, consts[:16], tmp)
+	err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, asm.NewStaticConst(consts[:16]), tmp)
 	if err != nil {
 		return err
 	}
 	c.assembler.CompileRegisterToRegister(amd64.PSHUFB, tmp, v.register)
-	err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, consts[16:], tmp)
+	err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, asm.NewStaticConst(consts[16:]), tmp)
 	if err != nil {
 		return err
 	}
@@ -534,7 +534,7 @@ func (c *amd64Compiler) compileV128Swizzle(*wazeroir.OperationV128Swizzle) error
 		return err
 	}
 
-	err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, swizzleConst[:], tmp)
+	err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, asm.NewStaticConst(swizzleConst[:]), tmp)
 	if err != nil {
 		return err
 	}
@@ -957,7 +957,7 @@ func (c *amd64Compiler) compileV128ShrI8x16Impl(signed bool) error {
 		}
 
 		// Read the initial address of the mask table into gpTmp register.
-		err = c.assembler.CompileStaticConstToRegister(amd64.LEAQ, i8x16LogicalSHRMaskTable[:], gpTmp)
+		err = c.assembler.CompileStaticConstToRegister(amd64.LEAQ, asm.NewStaticConst(i8x16LogicalSHRMaskTable[:]), gpTmp)
 		if err != nil {
 			return err
 		}
@@ -1042,7 +1042,7 @@ func (c *amd64Compiler) compileV128Shl(o *wazeroir.OperationV128Shl) error {
 		}
 
 		// Read the initial address of the mask table into gpTmp register.
-		err = c.assembler.CompileStaticConstToRegister(amd64.LEAQ, i8x16SHLMaskTable[:], gpTmp)
+		err = c.assembler.CompileStaticConstToRegister(amd64.LEAQ, asm.NewStaticConst(i8x16SHLMaskTable[:]), gpTmp)
 		if err != nil {
 			return err
 		}
@@ -1754,7 +1754,7 @@ func (c *amd64Compiler) compileV128Popcnt(*wazeroir.OperationV128Popcnt) error {
 
 	// Read the popcntMask into tmp1, and we have
 	//  tmp1 = [0xf, ..., 0xf]
-	if err := c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, popcntMask[:], tmp1); err != nil {
+	if err := c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, asm.NewStaticConst(popcntMask[:]), tmp1); err != nil {
 		return err
 	}
 
@@ -1775,7 +1775,7 @@ func (c *amd64Compiler) compileV128Popcnt(*wazeroir.OperationV128Popcnt) error {
 
 	// Read the popcntTable into tmp1, and we have
 	//  tmp1 = [0x00, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x03, 0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04]
-	if err := c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, popcntTable[:], tmp1); err != nil {
+	if err := c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, asm.NewStaticConst(popcntTable[:]), tmp1); err != nil {
 		return err
 	}
 
@@ -2261,7 +2261,7 @@ func (c *amd64Compiler) compileV128Q15mulrSatS(*wazeroir.OperationV128Q15mulrSat
 	x1r, x2r := x1.register, x2.register
 
 	// See https://github.com/WebAssembly/simd/pull/365 for the following logic.
-	if err := c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, q15mulrSatSMask[:], tmp); err != nil {
+	if err := c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, asm.NewStaticConst(q15mulrSatSMask[:]), tmp); err != nil {
 		return err
 	}
 
@@ -2300,7 +2300,7 @@ func (c *amd64Compiler) compileV128ExtAddPairwise(o *wazeroir.OperationV128ExtAd
 		}
 
 		if err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU,
-			allOnesI8x16[:], allOnesReg); err != nil {
+			asm.NewStaticConst(allOnesI8x16[:]), allOnesReg); err != nil {
 			return err
 		}
 
@@ -2329,7 +2329,8 @@ func (c *amd64Compiler) compileV128ExtAddPairwise(o *wazeroir.OperationV128ExtAd
 
 		if o.Signed {
 			// See https://www.felixcloutier.com/x86/pmaddwd
-			if err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, allOnesI16x8[:], tmp); err != nil {
+			if err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU,
+				asm.NewStaticConst(allOnesI16x8[:]), tmp); err != nil {
 				return err
 			}
 
@@ -2337,7 +2338,8 @@ func (c *amd64Compiler) compileV128ExtAddPairwise(o *wazeroir.OperationV128ExtAd
 			c.pushVectorRuntimeValueLocationOnRegister(vr)
 		} else {
 
-			if err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, extAddPairwiseI16x8uMask[:16], tmp); err != nil {
+			if err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU,
+				asm.NewStaticConst(extAddPairwiseI16x8uMask[:16]), tmp); err != nil {
 				return err
 			}
 
@@ -2347,7 +2349,8 @@ func (c *amd64Compiler) compileV128ExtAddPairwise(o *wazeroir.OperationV128ExtAd
 			// 	vr[i] = int8(-w1) for i = 0...8
 			c.assembler.CompileRegisterToRegister(amd64.PXOR, tmp, vr)
 
-			if err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, allOnesI16x8[:], tmp); err != nil {
+			if err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU,
+				asm.NewStaticConst(allOnesI16x8[:]), tmp); err != nil {
 				return err
 			}
 
@@ -2356,7 +2359,8 @@ func (c *amd64Compiler) compileV128ExtAddPairwise(o *wazeroir.OperationV128ExtAd
 			c.assembler.CompileRegisterToRegister(amd64.PMADDWD, tmp, vr)
 
 			// tmp[i] = [0, 0, 1, 0] = int32(math.MaxInt16+1)
-			if err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, extAddPairwiseI16x8uMask[16:], tmp); err != nil {
+			if err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU,
+				asm.NewStaticConst(extAddPairwiseI16x8uMask[16:]), tmp); err != nil {
 				return err
 			}
 
@@ -2468,7 +2472,7 @@ func (c *amd64Compiler) compileV128FConvertFromI(o *wazeroir.OperationV128FConve
 			}
 
 			// tmp = [0x00, 0x00, 0x30, 0x43, 0x00, 0x00, 0x30, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
-			if err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, fConvertFromIMask[:16], tmp); err != nil {
+			if err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, asm.NewStaticConst(fConvertFromIMask[:16]), tmp); err != nil {
 				return err
 			}
 
@@ -2479,7 +2483,8 @@ func (c *amd64Compiler) compileV128FConvertFromI(o *wazeroir.OperationV128FConve
 			c.assembler.CompileRegisterToRegister(amd64.UNPCKLPS, tmp, vr)
 
 			// tmp = [float64(0x1.0p52), float64(0x1.0p52)]
-			if err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU, twop52[:], tmp); err != nil {
+			if err = c.assembler.CompileStaticConstToRegister(amd64.MOVDQU,
+				asm.NewStaticConst(twop52[:]), tmp); err != nil {
 				return err
 			}
 
@@ -2649,7 +2654,7 @@ func (c *amd64Compiler) compileV128ITruncSatFromF(o *wazeroir.OperationV128ITrun
 			c.assembler.CompileRegisterToRegister(amd64.CMPEQPD, tmp, tmp)
 
 			// Load the 2147483647 into tmp2's each lane.
-			if err = c.assembler.CompileStaticConstToRegister(amd64.MOVUPD, i32sMaxOnF64x2[:], tmp2); err != nil {
+			if err = c.assembler.CompileStaticConstToRegister(amd64.MOVUPD, asm.NewStaticConst(i32sMaxOnF64x2[:]), tmp2); err != nil {
 				return err
 			}
 
@@ -2671,7 +2676,7 @@ func (c *amd64Compiler) compileV128ITruncSatFromF(o *wazeroir.OperationV128ITrun
 			c.assembler.CompileRegisterToRegister(amd64.MAXPD, tmp, vr)
 
 			// tmp2[i] = float64(math.MaxUint32) = math.MaxUint32
-			if err = c.assembler.CompileStaticConstToRegister(amd64.MOVUPD, i32uMaxOnF64x2[:], tmp2); err != nil {
+			if err = c.assembler.CompileStaticConstToRegister(amd64.MOVUPD, asm.NewStaticConst(i32uMaxOnF64x2[:]), tmp2); err != nil {
 				return err
 			}
 
@@ -2683,7 +2688,7 @@ func (c *amd64Compiler) compileV128ITruncSatFromF(o *wazeroir.OperationV128ITrun
 			c.assembler.CompileRegisterToRegisterWithArg(amd64.ROUNDPD, vr, vr, 0x3)
 
 			// tmp2[i] = float64(0x1.0p52)
-			if err = c.assembler.CompileStaticConstToRegister(amd64.MOVUPD, twop52[:], tmp2); err != nil {
+			if err = c.assembler.CompileStaticConstToRegister(amd64.MOVUPD, asm.NewStaticConst(twop52[:]), tmp2); err != nil {
 				return err
 			}
 
diff --git a/internal/engine/compiler/impl_vec_arm64.go b/internal/engine/compiler/impl_vec_arm64.go
index f5b678d6..941e99f6 100644
--- a/internal/engine/compiler/impl_vec_arm64.go
+++ b/internal/engine/compiler/impl_vec_arm64.go
@@ -567,7 +567,7 @@ func (c *arm64Compiler) compileV128Shuffle(o *wazeroir.OperationV128Shuffle) (er
 		return err
 	}
 
-	c.assembler.CompileLoadStaticConstToVectorRegister(arm64.VMOV, o.Lanes[:], result, arm64.VectorArrangementQ)
+	c.assembler.CompileStaticConstToVectorRegister(arm64.VMOV, asm.NewStaticConst(o.Lanes[:]), result, arm64.VectorArrangementQ)
 	c.assembler.CompileVectorRegisterToVectorRegister(arm64.TBL2, vReg, result, arm64.VectorArrangement16B,
 		arm64.VectorIndexNone, arm64.VectorIndexNone)
 
@@ -691,7 +691,7 @@ func (c *arm64Compiler) compileV128BitMask(o *wazeroir.OperationV128BitMask) (er
 		c.assembler.CompileVectorRegisterToVectorRegisterWithConst(arm64.SSHR, v, v, arm64.VectorArrangement16B, 7)
 
 		// Load the bit mask into vecTmp.
-		c.assembler.CompileLoadStaticConstToVectorRegister(arm64.VMOV, i8x16BitmaskConst[:], vecTmp, arm64.VectorArrangementQ)
+		c.assembler.CompileStaticConstToVectorRegister(arm64.VMOV, asm.NewStaticConst(i8x16BitmaskConst[:]), vecTmp, arm64.VectorArrangementQ)
 
 		// Lane-wise logical AND with i8x16BitmaskConst, meaning that we have
 		// v[i] = (1 << i) if vi<0, 0 otherwise.
@@ -725,7 +725,7 @@ func (c *arm64Compiler) compileV128BitMask(o *wazeroir.OperationV128BitMask) (er
 		c.assembler.CompileVectorRegisterToVectorRegisterWithConst(arm64.SSHR, v, v, arm64.VectorArrangement8H, 15)
 
 		// Load the bit mask into vecTmp.
-		c.assembler.CompileLoadStaticConstToVectorRegister(arm64.VMOV, i16x8BitmaskConst[:], vecTmp, arm64.VectorArrangementQ)
+		c.assembler.CompileStaticConstToVectorRegister(arm64.VMOV, asm.NewStaticConst(i16x8BitmaskConst[:]), vecTmp, arm64.VectorArrangementQ)
 
 		// Lane-wise logical AND with i16x8BitmaskConst, meaning that we have
 		// v[i] = (1 << i)     if vi<0, 0 otherwise for i=0..3
@@ -748,7 +748,8 @@ func (c *arm64Compiler) compileV128BitMask(o *wazeroir.OperationV128BitMask) (er
 		c.assembler.CompileVectorRegisterToVectorRegisterWithConst(arm64.SSHR, v, v, arm64.VectorArrangement4S, 32)
 
 		// Load the bit mask into vecTmp.
-		c.assembler.CompileLoadStaticConstToVectorRegister(arm64.VMOV, i32x4BitmaskConst[:], vecTmp, arm64.VectorArrangementQ)
+		c.assembler.CompileStaticConstToVectorRegister(arm64.VMOV,
+			asm.NewStaticConst(i32x4BitmaskConst[:]), vecTmp, arm64.VectorArrangementQ)
 
 		// Lane-wise logical AND with i16x8BitmaskConst, meaning that we have
 		// v[i] = (1 << i)     if vi<0, 0 otherwise for i in [0, 1]


Test patch:
diff --git a/internal/asm/amd64/impl_staticconst_test.go b/internal/asm/amd64/impl_staticconst_test.go
index c78adff5..ec0c9452 100644
--- a/internal/asm/amd64/impl_staticconst_test.go
+++ b/internal/asm/amd64/impl_staticconst_test.go
@@ -8,28 +8,14 @@ import (
 	"github.com/tetratelabs/wazero/internal/testing/require"
 )
 
-func TestConstPool_addConst(t *testing.T) {
-	p := newConstPool()
-	cons := []byte{1, 2, 3, 4}
-
-	// Loop twice to ensure that the same constant is cached and not added twice.
-	for i := 0; i < 2; i++ {
-		p.addConst(cons)
-		require.Equal(t, 1, len(p.consts))
-		require.Equal(t, len(cons), p.poolSizeInBytes)
-		_, ok := p.offsetFinalizedCallbacks[asm.StaticConstKey(cons)]
-		require.True(t, ok)
-	}
-}
-
 func TestAssemblerImpl_CompileStaticConstToRegister(t *testing.T) {
 	a := NewAssemblerImpl()
 	t.Run("odd count of bytes", func(t *testing.T) {
-		err := a.CompileStaticConstToRegister(MOVDQU, []byte{1}, RegAX)
+		err := a.CompileStaticConstToRegister(MOVDQU, asm.NewStaticConst([]byte{1}), RegAX)
 		require.Error(t, err)
 	})
 	t.Run("ok", func(t *testing.T) {
-		cons := []byte{1, 2, 3, 4}
+		cons := asm.NewStaticConst([]byte{1, 2, 3, 4})
 		err := a.CompileStaticConstToRegister(MOVDQU, cons, RegAX)
 		require.NoError(t, err)
 		actualNode := a.Current
@@ -43,11 +29,11 @@ func TestAssemblerImpl_CompileStaticConstToRegister(t *testing.T) {
 func TestAssemblerImpl_CompileRegisterToStaticConst(t *testing.T) {
 	a := NewAssemblerImpl()
 	t.Run("odd count of bytes", func(t *testing.T) {
-		err := a.CompileRegisterToStaticConst(MOVDQU, RegAX, []byte{1})
+		err := a.CompileRegisterToStaticConst(MOVDQU, RegAX, asm.NewStaticConst([]byte{1}))
 		require.Error(t, err)
 	})
 	t.Run("ok", func(t *testing.T) {
-		cons := []byte{1, 2, 3, 4}
+		cons := asm.NewStaticConst([]byte{1, 2, 3, 4})
 		err := a.CompileRegisterToStaticConst(MOVDQU, RegAX, cons)
 		require.NoError(t, err)
 		actualNode := a.Current
@@ -73,8 +59,8 @@ func TestAssemblerImpl_maybeFlushConstants(t *testing.T) {
 		endOfFunction           bool
 		dummyBodyBeforeFlush    []byte
 		firstUseOffsetInBinary  uint64
-		consts                  []asm.StaticConst
-		expectedOffsetForConsts []int
+		consts                  [][]byte
+		expectedOffsetForConsts []uint64
 		exp                     []byte
 		maxDisplacement         int
 	}{
@@ -82,8 +68,8 @@ func TestAssemblerImpl_maybeFlushConstants(t *testing.T) {
 			name:                    "end of function",
 			endOfFunction:           true,
 			dummyBodyBeforeFlush:    []byte{'?', '?', '?', '?'},
-			consts:                  []asm.StaticConst{{1, 2, 3, 4, 5, 6, 7, 8}, {10, 11, 12, 13}},
-			expectedOffsetForConsts: []int{4, 4 + 8}, // 4 = len(dummyBodyBeforeFlush)
+			consts:                  [][]byte{{1, 2, 3, 4, 5, 6, 7, 8}, {10, 11, 12, 13}},
+			expectedOffsetForConsts: []uint64{4, 4 + 8}, // 4 = len(dummyBodyBeforeFlush)
 			firstUseOffsetInBinary:  0,
 			exp:                     []byte{'?', '?', '?', '?', 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13},
 			maxDisplacement:         1 << 31, // large displacement will emit the consts at the end of function.
@@ -92,7 +78,7 @@ func TestAssemblerImpl_maybeFlushConstants(t *testing.T) {
 			name:                   "not flush",
 			endOfFunction:          false,
 			dummyBodyBeforeFlush:   []byte{'?', '?', '?', '?'},
-			consts:                 []asm.StaticConst{{1, 2, 3, 4, 5, 6, 7, 8}, {10, 11, 12, 13}},
+			consts:                 [][]byte{{1, 2, 3, 4, 5, 6, 7, 8}, {10, 11, 12, 13}},
 			firstUseOffsetInBinary: 0,
 			exp:                    []byte{'?', '?', '?', '?'},
 			maxDisplacement:        1 << 31, // large displacement will emit the consts at the end of function.
@@ -101,8 +87,8 @@ func TestAssemblerImpl_maybeFlushConstants(t *testing.T) {
 			name:                    "not end of function but flush - short jump",
 			endOfFunction:           false,
 			dummyBodyBeforeFlush:    []byte{'?', '?', '?', '?'},
-			consts:                  []asm.StaticConst{{1, 2, 3, 4, 5, 6, 7, 8}, {10, 11, 12, 13}},
-			expectedOffsetForConsts: []int{4 + 2, 4 + 2 + 8}, // 4 = len(dummyBodyBeforeFlush), 2 = the size of jump
+			consts:                  [][]byte{{1, 2, 3, 4, 5, 6, 7, 8}, {10, 11, 12, 13}},
+			expectedOffsetForConsts: []uint64{4 + 2, 4 + 2 + 8}, // 4 = len(dummyBodyBeforeFlush), 2 = the size of jump
 			firstUseOffsetInBinary:  0,
 			exp: []byte{'?', '?', '?', '?',
 				0xeb, 0x0c, // short jump with offset = len(consts[0]) + len(consts[1]) = 12 = 0xc.
@@ -113,8 +99,8 @@ func TestAssemblerImpl_maybeFlushConstants(t *testing.T) {
 			name:                    "not end of function but flush - long jump",
 			endOfFunction:           false,
 			dummyBodyBeforeFlush:    []byte{'?', '?', '?', '?'},
-			consts:                  []asm.StaticConst{largeData},
-			expectedOffsetForConsts: []int{4 + 5}, // 4 = len(dummyBodyBeforeFlush), 5 = the size of jump
+			consts:                  [][]byte{largeData},
+			expectedOffsetForConsts: []uint64{4 + 5}, // 4 = len(dummyBodyBeforeFlush), 5 = the size of jump
 			firstUseOffsetInBinary:  0,
 			exp: append([]byte{'?', '?', '?', '?',
 				0xe9, 0x0, 0x1, 0x0, 0x0, // short jump with offset = 256 = 0x0, 0x1, 0x0, 0x0 (in Little Endian).
@@ -130,15 +116,15 @@ func TestAssemblerImpl_maybeFlushConstants(t *testing.T) {
 			a.Buf.Write(tc.dummyBodyBeforeFlush)
 
 			for i, c := range tc.consts {
-				a.pool.addConst(c)
-				key := asm.StaticConstKey(c)
+				sc := asm.NewStaticConst(c)
+				a.pool.AddConst(sc, 100)
 				i := i
-				a.pool.offsetFinalizedCallbacks[key] = append(a.pool.offsetFinalizedCallbacks[key], func(offsetOfConstInBinary int) {
+				sc.AddOffsetFinalizedCallback(func(offsetOfConstInBinary uint64) {
 					require.Equal(t, tc.expectedOffsetForConsts[i], offsetOfConstInBinary)
 				})
 			}
 
-			a.pool.firstUseOffsetInBinary = &tc.firstUseOffsetInBinary
+			a.pool.FirstUseOffsetInBinary = &tc.firstUseOffsetInBinary
 			a.maybeFlushConstants(tc.endOfFunction)
 
 			require.Equal(t, tc.exp, a.Buf.Bytes())
@@ -150,7 +136,7 @@ func TestAssemblerImpl_encodeRegisterToStaticConst(t *testing.T) {
 	tests := []struct {
 		name            string
 		ins             asm.Instruction
-		c               asm.StaticConst
+		c               []byte
 		reg             asm.Register
 		ud2sBeforeConst int
 		exp             []byte
@@ -220,7 +206,7 @@ func TestAssemblerImpl_encodeRegisterToStaticConst(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			a := NewAssemblerImpl()
 
-			err := a.CompileRegisterToStaticConst(tc.ins, tc.reg, tc.c)
+			err := a.CompileRegisterToStaticConst(tc.ins, tc.reg, asm.NewStaticConst(tc.c))
 			require.NoError(t, err)
 
 			for i := 0; i < tc.ud2sBeforeConst; i++ {
@@ -239,7 +225,7 @@ func TestAssemblerImpl_encodeStaticConstToRegister(t *testing.T) {
 	tests := []struct {
 		name            string
 		ins             asm.Instruction
-		c               asm.StaticConst
+		c               []byte
 		reg             asm.Register
 		ud2sBeforeConst int
 		exp             []byte
@@ -612,7 +598,7 @@ func TestAssemblerImpl_encodeStaticConstToRegister(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			a := NewAssemblerImpl()
 
-			err := a.CompileStaticConstToRegister(tc.ins, tc.c, tc.reg)
+			err := a.CompileStaticConstToRegister(tc.ins, asm.NewStaticConst(tc.c), tc.reg)
 			require.NoError(t, err)
 
 			for i := 0; i < tc.ud2sBeforeConst; i++ {
diff --git a/internal/asm/arm64/impl_test.go b/internal/asm/arm64/impl_test.go
index 47bcd52a..cc27a4ec 100644
--- a/internal/asm/arm64/impl_test.go
+++ b/internal/asm/arm64/impl_test.go
@@ -3246,7 +3246,7 @@ func TestAssemblerImpl_EncodeRegisterToVectorRegister(t *testing.T) {
 func TestAssemblerImpl_maybeFlushConstPool(t *testing.T) {
 	tests := []struct {
 		name string
-		c    asm.StaticConst
+		c    []byte
 		exp  []byte
 	}{
 		{
@@ -3335,10 +3335,11 @@ func TestAssemblerImpl_maybeFlushConstPool(t *testing.T) {
 		tc := tc
 		t.Run(tc.name, func(t *testing.T) {
 			a := NewAssemblerImpl(asm.NilRegister)
-			a.addConstPool(tc.c, 0)
+			sc := asm.NewStaticConst(tc.c)
+			a.pool.AddConst(sc, 0)
 
 			var called bool
-			a.setConstPoolCallback(tc.c, func(int) {
+			sc.AddOffsetFinalizedCallback(func(uint64) {
 				called = true
 			})
 
@@ -3364,7 +3365,7 @@ func TestAssemblerImpl_EncodeStaticConstToVectorRegister(t *testing.T) {
 				Instruction:       VMOV,
 				DstReg:            RegV8,
 				VectorArrangement: VectorArrangementQ,
-				staticConst:       []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
+				staticConst:       asm.NewStaticConst([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}),
 			},
 			exp: []byte{
 				// 0x0: ldr q8, #8
@@ -3383,7 +3384,7 @@ func TestAssemblerImpl_EncodeStaticConstToVectorRegister(t *testing.T) {
 				Instruction:       VMOV,
 				DstReg:            RegV30,
 				VectorArrangement: VectorArrangementD,
-				staticConst:       []byte{1, 2, 3, 4, 5, 6, 7, 8},
+				staticConst:       asm.NewStaticConst([]byte{1, 2, 3, 4, 5, 6, 7, 8}),
 			},
 			exp: []byte{
 				// 0x0: ldr d30, #8
@@ -3402,7 +3403,7 @@ func TestAssemblerImpl_EncodeStaticConstToVectorRegister(t *testing.T) {
 				Instruction:       VMOV,
 				DstReg:            RegV8,
 				VectorArrangement: VectorArrangementS,
-				staticConst:       []byte{1, 2, 3, 4},
+				staticConst:       asm.NewStaticConst([]byte{1, 2, 3, 4}),
 			},
 			exp: []byte{
 				// 0x0: ldr s8, #8
@@ -3432,3 +3433,54 @@ func TestAssemblerImpl_EncodeStaticConstToVectorRegister(t *testing.T) {
 		})
 	}
 }
+
+func TestAssemblerImpl_encodeADR_staticConst(t *testing.T) {
+	const beforeADRByteNum uint64 = 2
+
+	tests := []struct {
+		name                   string
+		reg                    asm.Register
+		offsetOfConstInBinary  uint64
+		expADRInstructionBytes []byte
+	}{
+		{
+			// #8 = offsetOfConstInBinary - beforeADRByteNum.
+			name:                   "adr x12, #8",
+			reg:                    RegR12,
+			offsetOfConstInBinary:  10,
+			expADRInstructionBytes: []byte{0x4c, 0x0, 0x0, 0x10},
+		},
+		{
+			// #0x7fffd = offsetOfConstInBinary - beforeADRByteNum.
+			name:                   "adr x12, #0x7fffd",
+			reg:                    RegR12,
+			offsetOfConstInBinary:  0x7ffff,
+			expADRInstructionBytes: []byte{0xec, 0xff, 0x3f, 0x30},
+		},
+	}
+
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			sc := asm.NewStaticConst([]byte{1, 2, 3, 4}) // Arbitrary data is fine.
+
+			a := NewAssemblerImpl(asm.NilRegister)
+
+			a.Buf.Write(make([]byte, beforeADRByteNum))
+
+			err := a.encodeADR(&NodeImpl{Instruction: ADR, DstReg: tc.reg, staticConst: sc})
+			require.NoError(t, err)
+
+			require.Equal(t, 1, len(a.pool.Consts))
+			require.Equal(t, sc, a.pool.Consts[0])
+
+			require.Equal(t, beforeADRByteNum, *a.pool.FirstUseOffsetInBinary)
+
+			// Finalize the ADR instruction bytes.
+			sc.SetOffsetInBinary(tc.offsetOfConstInBinary)
+
+			actualBytes := a.Buf.Bytes()[beforeADRByteNum : beforeADRByteNum+4]
+			require.Equal(t, tc.expADRInstructionBytes, actualBytes, hex.EncodeToString(actualBytes))
+		})
+	}
+}
diff --git a/internal/asm/assembler_test.go b/internal/asm/assembler_test.go
new file mode 100644
index 00000000..12521e8e
--- /dev/null
+++ b/internal/asm/assembler_test.go
@@ -0,0 +1,46 @@
+package asm
+
+import (
+	"testing"
+
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+func TestNewStaticConstPool(t *testing.T) {
+	p := NewStaticConstPool()
+	require.NotNil(t, p.addedConsts)
+}
+
+func TestStaticConst_AddOffsetFinalizedCallback(t *testing.T) {
+	p := NewStaticConstPool()
+	const firstUseOffset uint64 = 100
+
+	// Add first const.
+	c := NewStaticConst([]byte{1})
+	p.AddConst(c, firstUseOffset)
+	require.Equal(t, firstUseOffset, *p.FirstUseOffsetInBinary)
+	require.Equal(t, 1, len(p.Consts))
+	require.Equal(t, 1, len(p.addedConsts))
+
+	// Adding the same *StaticConst doesn't affect the state.
+	p.AddConst(c, firstUseOffset+10000)
+	require.Equal(t, firstUseOffset, *p.FirstUseOffsetInBinary)
+	require.Equal(t, 1, len(p.Consts))
+	require.Equal(t, 1, len(p.addedConsts))
+
+	// Add another const.
+	c2 := NewStaticConst([]byte{1, 2})
+	p.AddConst(c2, firstUseOffset+100)
+	require.Equal(t, firstUseOffset, *p.FirstUseOffsetInBinary) // first use doesn't change!
+	require.Equal(t, 2, len(p.Consts))
+	require.Equal(t, 2, len(p.addedConsts))
+}
+
+func TestStaticConst_SetOffsetInBinary(t *testing.T) {
+	sc := NewStaticConst([]byte{1})
+	const offset uint64 = 100
+	sc.AddOffsetFinalizedCallback(func(offsetOfConstInBinary uint64) {
+		require.Equal(t, offset, offsetOfConstInBinary)
+	})
+	sc.SetOffsetInBinary(offset)
+}
diff --git a/internal/engine/compiler/compiler_controlflow_test.go b/internal/engine/compiler/compiler_controlflow_test.go
index c154d3bc..d67a41fc 100644
--- a/internal/engine/compiler/compiler_controlflow_test.go
+++ b/internal/engine/compiler/compiler_controlflow_test.go
@@ -22,7 +22,7 @@ func TestCompiler_compileHostFunction(t *testing.T) {
 	require.NoError(t, err)
 
 	// Generate and run the code under test.
-	code, _, _, err := compiler.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 	env.exec(code)
 
@@ -253,7 +253,7 @@ func TestCompiler_compileBrIf(t *testing.T) {
 					require.False(t, skip)
 					compiler.compileExitFromNativeCode(elseLabelExitStatus)
 
-					code, _, _, err := compiler.compile()
+					code, _, err := compiler.compile()
 					require.NoError(t, err)
 
 					// The generated code looks like this:
@@ -295,7 +295,7 @@ func TestCompiler_compileBrTable(t *testing.T) {
 		}
 
 		// Generate the code under test and run.
-		code, _, _, err := c.compile()
+		code, _, err := c.compile()
 		require.NoError(t, err)
 		env.exec(code)
 
@@ -472,7 +472,7 @@ func TestCompiler_compileBr(t *testing.T) {
 
 		// Compile and execute the code under test.
 		// Note: we don't invoke "compiler.return()" as the code emitted by compilerBr is enough to exit.
-		code, _, _, err := compiler.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 		env.exec(code)
 
@@ -509,7 +509,7 @@ func TestCompiler_compileBr(t *testing.T) {
 		err = compiler.compileBr(&wazeroir.OperationBr{Target: &wazeroir.BranchTarget{Label: exitLabel}})
 		require.NoError(t, err)
 
-		code, _, _, err := compiler.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 
 		// The generated code looks like this:
@@ -554,7 +554,7 @@ func TestCompiler_compileCallIndirect(t *testing.T) {
 		compiler.compileExitFromNativeCode(nativeCallStatusCodeUnreachable)
 
 		// Generate the code under test and run.
-		code, _, _, err := compiler.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 		env.exec(code)
 
@@ -590,7 +590,7 @@ func TestCompiler_compileCallIndirect(t *testing.T) {
 		require.NoError(t, err)
 
 		// Generate the code under test and run.
-		code, _, _, err := compiler.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 		env.exec(code)
 
@@ -630,7 +630,7 @@ func TestCompiler_compileCallIndirect(t *testing.T) {
 		require.NoError(t, err)
 
 		// Generate the code under test and run.
-		code, _, _, err := compiler.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 		env.exec(code)
 
@@ -675,7 +675,7 @@ func TestCompiler_compileCallIndirect(t *testing.T) {
 						err = compiler.compileReturnFunction()
 						require.NoError(t, err)
 
-						c, _, _, err := compiler.compile()
+						c, _, err := compiler.compile()
 						require.NoError(t, err)
 
 						f := &function{
@@ -723,7 +723,7 @@ func TestCompiler_compileCallIndirect(t *testing.T) {
 						require.NoError(t, err)
 
 						// Generate the code under test and run.
-						code, _, _, err := compiler.compile()
+						code, _, err := compiler.compile()
 						require.NoError(t, err)
 						env.exec(code)
 
@@ -777,7 +777,7 @@ func TestCompiler_callIndirect_largeTypeIndex(t *testing.T) {
 		err = compiler.compileReturnFunction()
 		require.NoError(t, err)
 
-		c, _, _, err := compiler.compile()
+		c, _, err := compiler.compile()
 		require.NoError(t, err)
 
 		f := &function{
@@ -807,7 +807,7 @@ func TestCompiler_callIndirect_largeTypeIndex(t *testing.T) {
 	require.NoError(t, err)
 
 	// Generate the code under test and run.
-	code, _, _, err := compiler.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 	env.exec(code)
 }
@@ -853,7 +853,7 @@ func TestCompiler_compileCall(t *testing.T) {
 					err = compiler.compileReturnFunction()
 					require.NoError(t, err)
 
-					c, _, _, err := compiler.compile()
+					c, _, err := compiler.compile()
 					require.NoError(t, err)
 					index := wasm.Index(i)
 					me.functions = append(me.functions, &function{
@@ -892,7 +892,7 @@ func TestCompiler_compileCall(t *testing.T) {
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
 
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -931,7 +931,7 @@ func TestCompiler_returnFunction(t *testing.T) {
 		err = compiler.compileReturnFunction()
 		require.NoError(t, err)
 
-		code, _, _, err := compiler.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 
 		env.exec(code)
@@ -967,7 +967,7 @@ func TestCompiler_returnFunction(t *testing.T) {
 				err = compiler.compileReturnFunction()
 				require.NoError(t, err)
 
-				c, _, _, err := compiler.compile()
+				c, _, err := compiler.compile()
 				require.NoError(t, err)
 
 				// Compiles and adds to the engine.
diff --git a/internal/engine/compiler/compiler_conversion_test.go b/internal/engine/compiler/compiler_conversion_test.go
index e4e16992..c6ee17e7 100644
--- a/internal/engine/compiler/compiler_conversion_test.go
+++ b/internal/engine/compiler/compiler_conversion_test.go
@@ -78,7 +78,7 @@ func TestCompiler_compileReinterpret(t *testing.T) {
 							require.NoError(t, err)
 
 							// Generate and run the code under test.
-							code, _, _, err := compiler.compile()
+							code, _, err := compiler.compile()
 							require.NoError(t, err)
 							env.exec(code)
 
@@ -121,7 +121,7 @@ func TestCompiler_compileExtend(t *testing.T) {
 					require.NoError(t, err)
 
 					// Generate and run the code under test.
-					code, _, _, err := compiler.compile()
+					code, _, err := compiler.compile()
 					require.NoError(t, err)
 					env.exec(code)
 
@@ -215,7 +215,7 @@ func TestCompiler_compileITruncFromF(t *testing.T) {
 					require.NoError(t, err)
 
 					// Generate and run the code under test.
-					code, _, _, err := compiler.compile()
+					code, _, err := compiler.compile()
 					require.NoError(t, err)
 					env.exec(code)
 
@@ -414,7 +414,7 @@ func TestCompiler_compileFConvertFromI(t *testing.T) {
 					require.NoError(t, err)
 
 					// Generate and run the code under test.
-					code, _, _, err := compiler.compile()
+					code, _, err := compiler.compile()
 					require.NoError(t, err)
 					env.exec(code)
 
@@ -485,7 +485,7 @@ func TestCompiler_compileF64PromoteFromF32(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -531,7 +531,7 @@ func TestCompiler_compileF32DemoteFromF64(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
diff --git a/internal/engine/compiler/compiler_global_test.go b/internal/engine/compiler/compiler_global_test.go
index 0747aa76..31d3350f 100644
--- a/internal/engine/compiler/compiler_global_test.go
+++ b/internal/engine/compiler/compiler_global_test.go
@@ -46,7 +46,7 @@ func TestCompiler_compileGlobalGet(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Run the code assembled above.
@@ -88,7 +88,7 @@ func TestCompiler_compileGlobalGet_v128(t *testing.T) {
 	require.NoError(t, err)
 
 	// Generate the code under test.
-	code, _, _, err := compiler.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 
 	// Run the code assembled above.
@@ -147,7 +147,7 @@ func TestCompiler_compileGlobalSet(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -193,7 +193,7 @@ func TestCompiler_compileGlobalSet_v128(t *testing.T) {
 	require.NoError(t, err)
 
 	// Generate the code under test.
-	code, _, _, err := compiler.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 	env.exec(code)
 
diff --git a/internal/engine/compiler/compiler_initialization_test.go b/internal/engine/compiler/compiler_initialization_test.go
index ab1ec05f..499bc91e 100644
--- a/internal/engine/compiler/compiler_initialization_test.go
+++ b/internal/engine/compiler/compiler_initialization_test.go
@@ -134,7 +134,7 @@ func TestCompiler_compileModuleContextInitialization(t *testing.T) {
 			compiler.compileExitFromNativeCode(nativeCallStatusCodeReturned)
 
 			// Generate the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			env.exec(code)
@@ -200,7 +200,7 @@ func TestCompiler_compileMaybeGrowValueStack(t *testing.T) {
 				compiler.compileExitFromNativeCode(nativeCallStatusCodeReturned)
 
 				// Generate and run the code under test.
-				code, _, _, err := compiler.compile()
+				code, _, err := compiler.compile()
 				require.NoError(t, err)
 				env.exec(code)
 
@@ -231,7 +231,7 @@ func TestCompiler_compileMaybeGrowValueStack(t *testing.T) {
 		env.setValueStackBasePointer(stackBasePointer)
 
 		// Generate and run the code under test.
-		code, _, _, err := compiler.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 		env.exec(code)
 
diff --git a/internal/engine/compiler/compiler_memory_test.go b/internal/engine/compiler/compiler_memory_test.go
index 6cb01832..3b46d75d 100644
--- a/internal/engine/compiler/compiler_memory_test.go
+++ b/internal/engine/compiler/compiler_memory_test.go
@@ -30,7 +30,7 @@ func TestCompiler_compileMemoryGrow(t *testing.T) {
 	require.NoError(t, err)
 
 	// Generate and run the code under test.
-	code, _, _, err := compiler.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 	env.exec(code)
 
@@ -66,7 +66,7 @@ func TestCompiler_compileMemorySize(t *testing.T) {
 	require.NoError(t, err)
 
 	// Generate and run the code under test.
-	code, _, _, err := compiler.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 	env.exec(code)
 
@@ -262,7 +262,7 @@ func TestCompiler_compileLoad(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -396,7 +396,7 @@ func TestCompiler_compileStore(t *testing.T) {
 			// Generate the code under test.
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Set the value on the left and right neighboring memoryregion,
@@ -464,7 +464,7 @@ func TestCompiler_MemoryOutOfBounds(t *testing.T) {
 					require.NoError(t, compiler.compileReturnFunction())
 
 					// Generate the code under test and run.
-					code, _, _, err := compiler.compile()
+					code, _, err := compiler.compile()
 					require.NoError(t, err)
 					env.exec(code)
 
diff --git a/internal/engine/compiler/compiler_numeric_test.go b/internal/engine/compiler/compiler_numeric_test.go
index ad749c05..0d8d39e3 100644
--- a/internal/engine/compiler/compiler_numeric_test.go
+++ b/internal/engine/compiler/compiler_numeric_test.go
@@ -68,7 +68,7 @@ func TestCompiler_compileConsts(t *testing.T) {
 					require.NoError(t, err)
 
 					// Generate the code under test.
-					code, _, _, err := compiler.compile()
+					code, _, err := compiler.compile()
 					require.NoError(t, err)
 
 					// Run native code.
@@ -184,7 +184,7 @@ func TestCompiler_compile_Add_Sub_Mul(t *testing.T) {
 							require.NoError(t, err)
 
 							// Compile and execute the code under test.
-							code, _, _, err := compiler.compile()
+							code, _, err := compiler.compile()
 							require.NoError(t, err)
 							env.exec(code)
 
@@ -354,7 +354,7 @@ func TestCompiler_compile_And_Or_Xor_Shl_Rotl_Rotr(t *testing.T) {
 								require.NoError(t, err)
 
 								// Compile and execute the code under test.
-								code, _, _, err := compiler.compile()
+								code, _, err := compiler.compile()
 								require.NoError(t, err)
 								env.exec(code)
 
@@ -471,7 +471,7 @@ func TestCompiler_compileShr(t *testing.T) {
 						require.NoError(t, err)
 
 						// Compile and execute the code under test.
-						code, _, _, err := compiler.compile()
+						code, _, err := compiler.compile()
 						require.NoError(t, err)
 						env.exec(code)
 
@@ -633,7 +633,7 @@ func TestCompiler_compile_Le_Lt_Gt_Ge_Eq_Eqz_Ne(t *testing.T) {
 							require.NoError(t, err)
 
 							// Compile and execute the code under test.
-							code, _, _, err := compiler.compile()
+							code, _, err := compiler.compile()
 							require.NoError(t, err)
 							env.exec(code)
 
@@ -788,7 +788,7 @@ func TestCompiler_compile_Clz_Ctz_Popcnt(t *testing.T) {
 							require.NoError(t, err)
 
 							// Generate and run the code under test.
-							code, _, _, err := compiler.compile()
+							code, _, err := compiler.compile()
 							require.NoError(t, err)
 							env.exec(code)
 
@@ -987,7 +987,7 @@ func TestCompiler_compile_Min_Max_Copysign(t *testing.T) {
 					require.NoError(t, err)
 
 					// Generate and run the code under test.
-					code, _, _, err := compiler.compile()
+					code, _, err := compiler.compile()
 					require.NoError(t, err)
 					env.exec(code)
 
@@ -1290,7 +1290,7 @@ func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
 					require.NoError(t, err)
 
 					// Generate and run the code under test.
-					code, _, _, err := compiler.compile()
+					code, _, err := compiler.compile()
 					require.NoError(t, err)
 					env.exec(code)
 
@@ -1419,7 +1419,7 @@ func TestCompiler_compile_Div_Rem(t *testing.T) {
 							require.NoError(t, err)
 
 							// Compile and execute the code under test.
-							code, _, _, err := compiler.compile()
+							code, _, err := compiler.compile()
 							require.NoError(t, err)
 							env.exec(code)
 
diff --git a/internal/engine/compiler/compiler_post1_0_test.go b/internal/engine/compiler/compiler_post1_0_test.go
index a958f870..229483d9 100644
--- a/internal/engine/compiler/compiler_post1_0_test.go
+++ b/internal/engine/compiler/compiler_post1_0_test.go
@@ -65,7 +65,7 @@ func TestCompiler_compileSignExtend(t *testing.T) {
 				require.NoError(t, err)
 
 				// Generate and run the code under test.
-				code, _, _, err := compiler.compile()
+				code, _, err := compiler.compile()
 				require.NoError(t, err)
 				env.exec(code)
 
@@ -138,7 +138,7 @@ func TestCompiler_compileSignExtend(t *testing.T) {
 				require.NoError(t, err)
 
 				// Generate and run the code under test.
-				code, _, _, err := compiler.compile()
+				code, _, err := compiler.compile()
 				require.NoError(t, err)
 				env.exec(code)
 
@@ -208,7 +208,7 @@ func TestCompiler_compileMemoryCopy(t *testing.T) {
 			// Generate the code under test.
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Setup the source memory region.
@@ -287,7 +287,7 @@ func TestCompiler_compileMemoryFill(t *testing.T) {
 			// Generate the code under test.
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Setup the memory region.
@@ -345,7 +345,7 @@ func TestCompiler_compileDataDrop(t *testing.T) {
 			// Generate the code under test.
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Run code.
@@ -428,7 +428,7 @@ func TestCompiler_compileMemoryInit(t *testing.T) {
 			// Generate the code under test.
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Run code.
@@ -484,7 +484,7 @@ func TestCompiler_compileElemDrop(t *testing.T) {
 			// Generate the code under test.
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Run code.
@@ -562,7 +562,7 @@ func TestCompiler_compileTableCopy(t *testing.T) {
 			// Generate the code under test.
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Setup the table.
@@ -662,7 +662,7 @@ func TestCompiler_compileTableInit(t *testing.T) {
 			// Generate the code under test.
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Run code.
@@ -777,7 +777,7 @@ func TestCompiler_compileTableSet(t *testing.T) {
 			// Generate the code under test.
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Run code.
@@ -906,7 +906,7 @@ func TestCompiler_compileTableGet(t *testing.T) {
 			// Generate the code under test.
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Run code.
@@ -950,7 +950,7 @@ func TestCompiler_compileRefFunc(t *testing.T) {
 			// Generate the code under test.
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Run code.
diff --git a/internal/engine/compiler/compiler_stack_test.go b/internal/engine/compiler/compiler_stack_test.go
index 8497634c..2fb2eb3d 100644
--- a/internal/engine/compiler/compiler_stack_test.go
+++ b/internal/engine/compiler/compiler_stack_test.go
@@ -54,7 +54,7 @@ func TestCompiler_releaseRegisterToStack(t *testing.T) {
 			compiler.compileExitFromNativeCode(nativeCallStatusCodeReturned)
 
 			// Generate the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Run native code after growing the value stack.
@@ -137,7 +137,7 @@ func TestCompiler_compileLoadValueOnStackToRegister(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Run native code after growing the value stack, and place the original value.
@@ -212,7 +212,7 @@ func TestCompiler_compilePick_v128(t *testing.T) {
 			require.NoError(t, err)
 
 			// Compile and execute the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -308,7 +308,7 @@ func TestCompiler_compilePick(t *testing.T) {
 			require.NoError(t, err)
 
 			// Compile and execute the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -352,7 +352,7 @@ func TestCompiler_compileDrop(t *testing.T) {
 		err = compiler.compileReturnFunction()
 		require.NoError(t, err)
 
-		code, _, _, err := compiler.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 
 		env.exec(code)
@@ -393,7 +393,7 @@ func TestCompiler_compileDrop(t *testing.T) {
 		err = compiler.compileReturnFunction()
 		require.NoError(t, err)
 
-		code, _, _, err := compiler.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 
 		env.exec(code)
@@ -441,7 +441,7 @@ func TestCompiler_compileDrop(t *testing.T) {
 		err = compiler.compileReturnFunction()
 		require.NoError(t, err)
 
-		code, _, _, err := compiler.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 
 		env.exec(code)
@@ -579,7 +579,7 @@ func TestCompiler_compileSelect(t *testing.T) {
 					require.NoError(t, err)
 
 					// Run code.
-					code, _, _, err := compiler.compile()
+					code, _, err := compiler.compile()
 					require.NoError(t, err)
 					env.exec(code)
 
@@ -650,7 +650,7 @@ func TestCompiler_compileSwap_v128(t *testing.T) {
 			require.NoError(t, compiler.compileReturnFunction())
 
 			// Generate the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Run code.
@@ -723,7 +723,7 @@ func TestCompiler_compileSwap(t *testing.T) {
 			require.NoError(t, compiler.compileReturnFunction())
 
 			// Generate the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Run code.
diff --git a/internal/engine/compiler/compiler_vec_test.go b/internal/engine/compiler/compiler_vec_test.go
index b321bfd2..e05a75cc 100644
--- a/internal/engine/compiler/compiler_vec_test.go
+++ b/internal/engine/compiler/compiler_vec_test.go
@@ -93,7 +93,7 @@ func TestCompiler_compileV128Add(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -191,7 +191,7 @@ func TestCompiler_compileV128Sub(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -544,7 +544,7 @@ func TestCompiler_compileV128Load(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -750,7 +750,7 @@ func TestCompiler_compileV128LoadLane(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -802,7 +802,7 @@ func TestCompiler_compileV128Store(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -944,7 +944,7 @@ func TestCompiler_compileV128StoreLane(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -1127,7 +1127,7 @@ func TestCompiler_compileV128ExtractLane(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -1364,7 +1364,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -1462,7 +1462,7 @@ func TestCompiler_compileV128Splat(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -1509,7 +1509,7 @@ func TestCompiler_compileV128AnyTrue(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -1671,7 +1671,7 @@ func TestCompiler_compileV128AllTrue(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -1773,7 +1773,7 @@ func TestCompiler_compileV128Swizzle(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -1883,7 +1883,7 @@ func TestCompiler_compileV128Shuffle(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -2020,7 +2020,7 @@ func TestCompiler_compileV128Bitmask(t *testing.T) {
 
 			// Generate and run the code under test.
 
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -2056,7 +2056,7 @@ func TestCompiler_compileV128_Not(t *testing.T) {
 	require.NoError(t, err)
 
 	// Generate and run the code under test.
-	code, _, _, err := compiler.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 	env.exec(code)
 
@@ -2284,7 +2284,7 @@ func TestCompiler_compileV128_And_Or_Xor_AndNot(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -2378,7 +2378,7 @@ func TestCompiler_compileV128Bitselect(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -2663,7 +2663,7 @@ func TestCompiler_compileV128Shl(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -2939,7 +2939,7 @@ func TestCompiler_compileV128Shr(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -3370,7 +3370,7 @@ func TestCompiler_compileV128Cmp(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -3451,7 +3451,7 @@ func TestCompiler_compileV128AvgrU(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -3518,7 +3518,7 @@ func TestCompiler_compileV128Sqrt(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -3608,7 +3608,7 @@ func TestCompiler_compileV128Mul(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -3705,7 +3705,7 @@ func TestCompiler_compileV128Neg(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -3802,7 +3802,7 @@ func TestCompiler_compileV128Abs(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -3878,7 +3878,7 @@ func TestCompiler_compileV128Div(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -4039,7 +4039,7 @@ func TestCompiler_compileV128Min(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -4242,7 +4242,7 @@ func TestCompiler_compileV128Max(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -4383,7 +4383,7 @@ func TestCompiler_compileV128AddSat(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -4495,7 +4495,7 @@ func TestCompiler_compileV128SubSat(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -4565,7 +4565,7 @@ func TestCompiler_compileV128Popcnt(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -4745,7 +4745,7 @@ func TestCompiler_compileV128Round(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -5038,7 +5038,7 @@ func TestCompiler_compileV128_Pmax_Pmin(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -5732,7 +5732,7 @@ func TestCompiler_compileV128ExtMul(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -6205,7 +6205,7 @@ func TestCompiler_compileV128Extend(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -6290,7 +6290,7 @@ func TestCompiler_compileV128Q15mulrSatS(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -6363,7 +6363,7 @@ func TestCompiler_compileFloatPromote(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -6447,7 +6447,7 @@ func TestCompiler_compileV128FloatDemote(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -6657,7 +6657,7 @@ func TestCompiler_compileV128ExtAddPairwise(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -6905,7 +6905,7 @@ func TestCompiler_compileV128Narrow(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -7042,7 +7042,7 @@ func TestCompiler_compileV128FConvertFromI(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -7114,7 +7114,7 @@ func TestCompiler_compileV128Dot(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -7266,7 +7266,7 @@ func TestCompiler_compileV128ITruncSatFromF(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
diff --git a/internal/engine/compiler/impl_amd64_test.go b/internal/engine/compiler/impl_amd64_test.go
index 12c992e9..e6a29f9a 100644
--- a/internal/engine/compiler/impl_amd64_test.go
+++ b/internal/engine/compiler/impl_amd64_test.go
@@ -32,7 +32,7 @@ func TestAmd64Compiler_indirectCallWithTargetOnCallingConvReg(t *testing.T) {
 		err = compiler.compileReturnFunction()
 		require.NoError(t, err)
 
-		c, _, _, err := compiler.compile()
+		c, _, err := compiler.compile()
 		require.NoError(t, err)
 
 		f := &function{
@@ -64,7 +64,7 @@ func TestAmd64Compiler_indirectCallWithTargetOnCallingConvReg(t *testing.T) {
 	require.NoError(t, err)
 
 	// Generate the code under test and run.
-	code, _, _, err := compiler.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 	env.exec(code)
 }
@@ -184,7 +184,7 @@ func TestAmd64Compiler_compile_Mul_Div_Rem(t *testing.T) {
 						require.NoError(t, compiler.compileReturnFunction())
 
 						// Generate the code under test.
-						code, _, _, err := compiler.compile()
+						code, _, err := compiler.compile()
 						require.NoError(t, err)
 						// Run code.
 						env.exec(code)
@@ -308,7 +308,7 @@ func TestAmd64Compiler_compile_Mul_Div_Rem(t *testing.T) {
 						require.NoError(t, compiler.compileReturnFunction())
 
 						// Generate the code under test.
-						code, _, _, err := compiler.compile()
+						code, _, err := compiler.compile()
 						require.NoError(t, err)
 
 						// Run code.
@@ -346,7 +346,7 @@ func TestAmd64Compiler_readInstructionAddress(t *testing.T) {
 
 		// If generate the code without JMP after readInstructionAddress,
 		// the call back added must return error.
-		_, _, _, err = compiler.compile()
+		_, _, err = compiler.compile()
 		require.Error(t, err)
 	})
 
@@ -379,7 +379,7 @@ func TestAmd64Compiler_readInstructionAddress(t *testing.T) {
 		require.NoError(t, err)
 
 		// Generate the code under test.
-		code, _, _, err := compiler.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 
 		// Run code.
diff --git a/internal/engine/compiler/impl_arm64_test.go b/internal/engine/compiler/impl_arm64_test.go
index 03361f77..9e5564f1 100644
--- a/internal/engine/compiler/impl_arm64_test.go
+++ b/internal/engine/compiler/impl_arm64_test.go
@@ -31,7 +31,7 @@ func TestArm64Compiler_indirectCallWithTargetOnCallingConvReg(t *testing.T) {
 		err = compiler.compileReturnFunction()
 		require.NoError(t, err)
 
-		c, _, _, err := compiler.compile()
+		c, _, err := compiler.compile()
 		require.NoError(t, err)
 
 		f := &function{
@@ -63,7 +63,7 @@ func TestArm64Compiler_indirectCallWithTargetOnCallingConvReg(t *testing.T) {
 	require.NoError(t, err)
 
 	// Generate the code under test and run.
-	code, _, _, err := compiler.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 	env.exec(code)
 }
@@ -94,7 +94,7 @@ func TestArm64Compiler_readInstructionAddress(t *testing.T) {
 	err = compiler.compileReturnFunction()
 	require.NoError(t, err)
 
-	code, _, _, err := compiler.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 
 	env.exec(code)
diff --git a/internal/engine/compiler/impl_vec_amd64_test.go b/internal/engine/compiler/impl_vec_amd64_test.go
index cd9903a7..f1671ad3 100644
--- a/internal/engine/compiler/impl_vec_amd64_test.go
+++ b/internal/engine/compiler/impl_vec_amd64_test.go
@@ -53,7 +53,7 @@ func TestAmd64Compiler_V128Shuffle_ConstTable_MiddleOfFunction(t *testing.T) {
 	require.NoError(t, err)
 
 	// Generate and run the code under test.
-	code, _, _, err := compiler.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 	env.exec(code)
 
@@ -203,7 +203,7 @@ func TestAmd64Compiler_compileV128ShrI64x2SignedImpl(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
@@ -288,7 +288,7 @@ func TestAmd64Compiler_compileV128Neg_NaNOnTemporary(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			env.exec(code)
 
diff --git a/internal/engine/compiler/impl_vec_arm64_test.go b/internal/engine/compiler/impl_vec_arm64_test.go
index 03d25e37..7a6b8c02 100644
--- a/internal/engine/compiler/impl_vec_arm64_test.go
+++ b/internal/engine/compiler/impl_vec_arm64_test.go
@@ -53,7 +53,7 @@ func TestArm64Compiler_V128Shuffle_ConstTable_MiddleOfFunction(t *testing.T) {
 	require.NoError(t, err)
 
 	// Generate and run the code under test.
-	code, _, _, err := compiler.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 
 	env.exec(code)
@@ -202,7 +202,7 @@ func TestArm64Compiler_V128Shuffle_combinations(t *testing.T) {
 			require.NoError(t, err)
 
 			// Generate and run the code under test.
-			code, _, _, err := compiler.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			env.exec(code)
diff --git a/internal/integration_test/asm/amd64_debug/debug_assembler.go b/internal/integration_test/asm/amd64_debug/debug_assembler.go
index 037e8092..c22d3450 100644
--- a/internal/integration_test/asm/amd64_debug/debug_assembler.go
+++ b/internal/integration_test/asm/amd64_debug/debug_assembler.go
@@ -109,9 +109,8 @@ func (ta *testAssembler) SetJumpTargetOnNext(nodes ...asm.Node) {
 }
 
 // BuildJumpTable implements the same method as documented on asm_amd64.Assembler.
-func (ta *testAssembler) BuildJumpTable(table []byte, initialInstructions []asm.Node) {
-	ta.goasm.BuildJumpTable(table, initialInstructions)
-	ta.a.BuildJumpTable(table, initialInstructions)
+func (ta *testAssembler) BuildJumpTable(table *asm.StaticConst, initialInstructions []asm.Node) {
+	panic("BuildJumpTable is not supported by golang-asm")
 }
 
 // CompileStandAlone implements the same method as documented on asm_amd64.Assembler.
@@ -302,11 +301,11 @@ func (ta *testAssembler) CompileMemoryToConst(
 }
 
 // CompileStaticConstToRegister implements Assembler.CompileStaticConstToRegister.
-func (ta *testAssembler) CompileStaticConstToRegister(asm.Instruction, []byte, asm.Register) (err error) {
+func (ta *testAssembler) CompileStaticConstToRegister(asm.Instruction, *asm.StaticConst, asm.Register) (err error) {
 	panic("CompileStaticConstToRegister cannot be supported by golang-asm")
 }
 
 // CompileRegisterToStaticConst implements Assembler.CompileRegisterToStaticConst.
-func (ta *testAssembler) CompileRegisterToStaticConst(asm.Instruction, asm.Register, []byte) (err error) {
+func (ta *testAssembler) CompileRegisterToStaticConst(asm.Instruction, asm.Register, *asm.StaticConst) (err error) {
 	panic("CompileRegisterToStaticConst cannot be supported by golang-asm")
 }
diff --git a/internal/integration_test/asm/amd64_debug/golang_asm.go b/internal/integration_test/asm/amd64_debug/golang_asm.go
index 8f57922c..55e33034 100644
--- a/internal/integration_test/asm/amd64_debug/golang_asm.go
+++ b/internal/integration_test/asm/amd64_debug/golang_asm.go
@@ -381,12 +381,12 @@ func (a *assemblerGoAsmImpl) CompileReadInstructionAddress(
 }
 
 // CompileStaticConstToRegister implements Assembler.CompileStaticConstToRegister.
-func (a *assemblerGoAsmImpl) CompileStaticConstToRegister(instruction asm.Instruction, c []byte, dstReg asm.Register) (err error) {
+func (a *assemblerGoAsmImpl) CompileStaticConstToRegister(asm.Instruction, *asm.StaticConst, asm.Register) (err error) {
 	panic("CompileStaticConstToRegister cannot be supported by golangasm")
 }
 
 // CompileRegisterToStaticConst implements Assembler.CompileRegisterToStaticConst.
-func (ta *assemblerGoAsmImpl) CompileRegisterToStaticConst(asm.Instruction, asm.Register, []byte) (err error) {
+func (a *assemblerGoAsmImpl) CompileRegisterToStaticConst(asm.Instruction, asm.Register, *asm.StaticConst) (err error) {
 	panic("CompileRegisterToStaticConst cannot be supported by golang-asm")
 }
 
diff --git a/internal/integration_test/asm/arm64_debug/debug_assembler.go b/internal/integration_test/asm/arm64_debug/debug_assembler.go
index 1a891276..ac6fe8b3 100644
--- a/internal/integration_test/asm/arm64_debug/debug_assembler.go
+++ b/internal/integration_test/asm/arm64_debug/debug_assembler.go
@@ -101,7 +101,7 @@ func (ta *testAssembler) SetJumpTargetOnNext(nodes ...asm.Node) {
 }
 
 // BuildJumpTable implements the same method as documented on arm64.Assembler.
-func (ta *testAssembler) BuildJumpTable(table []byte, initialInstructions []asm.Node) {
+func (ta *testAssembler) BuildJumpTable(table *asm.StaticConst, initialInstructions []asm.Node) {
 	ta.goasm.BuildJumpTable(table, initialInstructions)
 	ta.a.BuildJumpTable(table, initialInstructions)
 }
@@ -284,12 +284,16 @@ func (ta *testAssembler) CompileVectorRegisterToRegister(instruction asm.Instruc
 	ta.a.CompileVectorRegisterToRegister(instruction, srcReg, dstReg, arrangement, index)
 }
 
-// CompileLoadStaticConstToVectorRegister adds an instruction where the source operand is StaticConstant located in the memory
-// and the destination is the dstReg.
-func (ta *testAssembler) CompileLoadStaticConstToVectorRegister(instruction asm.Instruction,
-	c asm.StaticConst, dstReg asm.Register, arrangement arm64.VectorArrangement) {
-	ta.goasm.CompileLoadStaticConstToVectorRegister(instruction, c, dstReg, arrangement)
-	ta.a.CompileLoadStaticConstToVectorRegister(instruction, c, dstReg, arrangement)
+// CompileStaticConstToVectorRegister implements the same method as documented on arm64.Assembler.
+func (ta *testAssembler) CompileStaticConstToVectorRegister(instruction asm.Instruction,
+	c *asm.StaticConst, dstReg asm.Register, arrangement arm64.VectorArrangement) {
+	ta.goasm.CompileStaticConstToVectorRegister(instruction, c, dstReg, arrangement)
+	ta.a.CompileStaticConstToVectorRegister(instruction, c, dstReg, arrangement)
+}
+
+// CompileStaticConstToRegister implements the same method as documented on arm64.Assembler.
+func (ta *testAssembler) CompileStaticConstToRegister(asm.Instruction, *asm.StaticConst, asm.Register) {
+	panic("CompileStaticConstToRegister is not supported by golang-asm")
 }
 
 // CompileTwoVectorRegistersToVectorRegister implements the same method as documented on arm64.Assembler.
diff --git a/internal/integration_test/asm/arm64_debug/golang_asm.go b/internal/integration_test/asm/arm64_debug/golang_asm.go
index 05c769dd..0f8c46e3 100644
--- a/internal/integration_test/asm/arm64_debug/golang_asm.go
+++ b/internal/integration_test/asm/arm64_debug/golang_asm.go
@@ -397,10 +397,16 @@ func (a *assemblerGoAsmImpl) CompileVectorRegisterToRegister(instruction asm.Ins
 	a.AddInstruction(inst)
 }
 
-// CompileLoadStaticConstToVectorRegister adds an instruction where the source operand is StaticConstant located in the memory
+// CompileStaticConstToVectorRegister adds an instruction where the source operand is StaticConstant located in the memory
 // and the destination is the dstReg.
-func (a *assemblerGoAsmImpl) CompileLoadStaticConstToVectorRegister(asm.Instruction,
-	asm.StaticConst, asm.Register, asm_arm64.VectorArrangement) {
+func (a *assemblerGoAsmImpl) CompileStaticConstToVectorRegister(asm.Instruction,
+	*asm.StaticConst, asm.Register, asm_arm64.VectorArrangement) {
+	panic("unsupported in golang-asm")
+}
+
+// CompileStaticConstToRegister adds an instruction where the source operand is StaticConstant located in the memory
+// and the destination is the dstReg.
+func (a *assemblerGoAsmImpl) CompileStaticConstToRegister(asm.Instruction, *asm.StaticConst, asm.Register) {
 	panic("unsupported in golang-asm")
 }
 
diff --git a/internal/integration_test/asm/arm64_debug/impl_test.go b/internal/integration_test/asm/arm64_debug/impl_test.go
index c9652400..64c432f9 100644
--- a/internal/integration_test/asm/arm64_debug/impl_test.go
+++ b/internal/integration_test/asm/arm64_debug/impl_test.go
@@ -1118,28 +1118,6 @@ func TestAssemblerImpl_EncodeRelativeJump(t *testing.T) {
 	}
 }
 
-// TestAssemblerImpl_multipleLargeOffest ensures that the const pool flushing strategy matches
-// the one of Go's assembler.
-func TestAssemblerImpl_multipleLargeOffest(t *testing.T) {
-	goasm := newGoasmAssembler(t, asm.NilRegister)
-	a := arm64.NewAssemblerImpl(arm64.RegR27)
-
-	for _, assembler := range []arm64.Assembler{a, goasm} {
-		for i := 0; i < 10000; i++ {
-			// This will be put into const pool, but the callback won't be set for it.
-			assembler.CompileRegisterToMemory(arm64.MOVD, arm64.RegR11, arm64.RegR12, 0xfff0+int64(i*8))
-			// This will also set the call back for it.
-			assembler.CompileRegisterToMemory(arm64.MOVD, arm64.RegR11, arm64.RegR12, (0xfff0+int64(i*8)<<16+8)%(1<<31))
-		}
-	}
-
-	actual, err := a.Assemble()
-	require.NoError(t, err)
-	expected, err := goasm.Assemble()
-	require.NoError(t, err)
-	require.Equal(t, expected, actual)
-}
-
 func conditionalRegisterToState(r asm.Register) asm.ConditionalRegisterState {
 	switch r {
 	case arm64.RegCondEQ:
diff --git a/internal/integration_test/asm/golang_asm/golang_asm.go b/internal/integration_test/asm/golang_asm/golang_asm.go
index 1d9805d7..1a217afd 100644
--- a/internal/integration_test/asm/golang_asm/golang_asm.go
+++ b/internal/integration_test/asm/golang_asm/golang_asm.go
@@ -1,7 +1,6 @@
 package golang_asm
 
 import (
-	"encoding/binary"
 	"fmt"
 
 	goasm "github.com/twitchyliquid64/golang-asm"
@@ -87,19 +86,8 @@ func (a *GolangAsmBaseAssembler) AddOnGenerateCallBack(cb func([]byte) error) {
 }
 
 // BuildJumpTable implements the same method as documented on asm.AssemblerBase.
-func (a *GolangAsmBaseAssembler) BuildJumpTable(table []byte, labelInitialInstructions []asm.Node) {
-	a.AddOnGenerateCallBack(func(code []byte) error {
-		// Compile the offset table for each target.
-		base := labelInitialInstructions[0].OffsetInBinary()
-		for i, nop := range labelInitialInstructions {
-			if uint64(nop.OffsetInBinary())-uint64(base) >= asm.JumpTableMaximumOffset {
-				return fmt.Errorf("too large br_table")
-			}
-			// We store the offset from the beginning of the L0's initial instruction.
-			binary.LittleEndian.PutUint32(table[i*4:(i+1)*4], uint32(nop.OffsetInBinary())-uint32(base))
-		}
-		return nil
-	})
+func (a *GolangAsmBaseAssembler) BuildJumpTable(*asm.StaticConst, []asm.Node) {
+	panic("BuildJumpTable is not supported by golang-asm")
 }
 
 // AddInstruction is used in architecture specific assembler implementation for golang-asm.