Repository: tetratelabs/wazero
Pull request #98 (2021-12-30 07:00:56)

jit: refactor around builder.
This commit Introduces `compiler` interface as the architecture specific 
native code compiler (previously called amd64Builder as an implementation for it).
As a result, this also moves the `compilerWasmFunction` in engine.go by making it
architecture-independent via `compiler` interface.


Patch:
diff --git a/wasm/jit/compiler.go b/wasm/jit/compiler.go
new file mode 100644
index 00000000..3519b9f5
--- /dev/null
+++ b/wasm/jit/compiler.go
@@ -0,0 +1,43 @@
+package jit
+
+import "github.com/tetratelabs/wazero/wasm/wazeroir"
+
+// compiler is the interface of architecture-specific native code compiler,
+// and this is responsible for compiling native code for all wazeroir operations.
+type compiler interface {
+	// emitPreamble is called before compiling any wazeroir operation.
+	// This is used, for example, to initilize the reserved registers, etc.
+	emitPreamble()
+	// Finilizes the compilation, and returns the byte slice of native codes.
+	// maxStackPointer is the max stack pointer that the target function would reach.
+	compile() (code []byte, maxStackPointer uint64, err error)
+	// Followings are resinposible for compiling each wazeroir operation.
+	compileUnreachable()
+	compileSwap(o *wazeroir.OperationSwap) error
+	compileGlobalGet(o *wazeroir.OperationGlobalGet) error
+	compileGlobalSet(o *wazeroir.OperationGlobalSet) error
+	compileBr(o *wazeroir.OperationBr) error
+	compileBrIf(o *wazeroir.OperationBrIf) error
+	compileLabel(o *wazeroir.OperationLabel) error
+	compileCall(o *wazeroir.OperationCall) error
+	compileDrop(o *wazeroir.OperationDrop) error
+	compileSelect() error
+	compilePick(o *wazeroir.OperationPick) error
+	compileAdd(o *wazeroir.OperationAdd) error
+	compileSub(o *wazeroir.OperationSub) error
+	compileLe(o *wazeroir.OperationLe) error
+	compileLoad(o *wazeroir.OperationLoad) error
+	compileLoad8(o *wazeroir.OperationLoad8) error
+	compileLoad16(o *wazeroir.OperationLoad16) error
+	compileLoad32(o *wazeroir.OperationLoad32) error
+	compileStore(o *wazeroir.OperationStore) error
+	compileStore8(o *wazeroir.OperationStore8) error
+	compileStore16(o *wazeroir.OperationStore16) error
+	compileStore32(o *wazeroir.OperationStore32) error
+	compileMemoryGrow()
+	compileMemorySize()
+	compileConstI32(o *wazeroir.OperationConstI32) error
+	compileConstI64(o *wazeroir.OperationConstI64) error
+	compileConstF32(o *wazeroir.OperationConstF32) error
+	compileConstF64(o *wazeroir.OperationConstF64) error
+}
diff --git a/wasm/jit/engine.go b/wasm/jit/engine.go
index 1853af0c..691532da 100644
--- a/wasm/jit/engine.go
+++ b/wasm/jit/engine.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/tetratelabs/wazero/wasm"
 	"github.com/tetratelabs/wazero/wasm/buildoptions"
+	"github.com/tetratelabs/wazero/wasm/wazeroir"
 )
 
 type engine struct {
@@ -300,8 +301,8 @@ type compiledHostFunction = struct {
 
 type compiledWasmFunction struct {
 	// The source function instance from which this is compiled.
-	source          *wasm.FunctionInstance
-	params, results uint64
+	source                  *wasm.FunctionInstance
+	paramCount, resultCount uint64
 	// codeSegment is holding the compiled native code as a byte slice.
 	codeSegment []byte
 	// memory is the pointer to a memory instance which the original function instance refers to.
@@ -347,7 +348,7 @@ func (e *engine) exec(f *compiledWasmFunction) {
 		continuationAddress:      f.codeInitialAddress,
 		wasmFunction:             f,
 		caller:                   nil,
-		continuationStackPointer: f.params,
+		continuationStackPointer: f.paramCount,
 	}
 	e.globalSliceAddress = f.globalSliceAddress
 	// If the Go-allocated stack is running out, we grow it before calling into JITed code.
@@ -385,7 +386,7 @@ func (e *engine) exec(f *compiledWasmFunction) {
 			nextFunc := e.compiledWasmFunctions[e.functionCallIndex]
 			// Calculate the continuation address so we can resume this caller function frame.
 			currentFrame.continuationAddress = currentFrame.wasmFunction.codeInitialAddress + e.continuationAddressOffset
-			currentFrame.continuationStackPointer = e.stackPointer + nextFunc.results - nextFunc.params
+			currentFrame.continuationStackPointer = e.stackPointer + nextFunc.resultCount - nextFunc.paramCount
 			// Create the callee frame.
 			frame := &callFrame{
 				continuationAddress: nextFunc.codeInitialAddress,
@@ -393,7 +394,7 @@ func (e *engine) exec(f *compiledWasmFunction) {
 				// Set the caller frame so we can return back to the current frame!
 				caller: currentFrame,
 				// Set the base pointer to the beginning of the function params
-				stackBasePointer: e.stackBasePointer + e.stackPointer - nextFunc.params,
+				stackBasePointer: e.stackBasePointer + e.stackPointer - nextFunc.paramCount,
 			}
 			// If the Go-allocated stack is running out, we grow it before calling into JITed code.
 			e.maybeGrowStack(nextFunc.maxStackPointer)
@@ -401,7 +402,7 @@ func (e *engine) exec(f *compiledWasmFunction) {
 			e.callFrameStack = frame
 			e.stackBasePointer = frame.stackBasePointer
 			// Set the stack pointer so that base+sp would point to the top of function params.
-			e.stackPointer = nextFunc.params
+			e.stackPointer = nextFunc.paramCount
 			e.globalSliceAddress = nextFunc.globalSliceAddress
 		case jitCallStatusCodeCallBuiltInFunction:
 			switch e.functionCallIndex {
@@ -446,3 +447,182 @@ func (e *engine) builtinFunctionMemoryGrow(f *compiledWasmFunction) {
 func (e *engine) builtinFunctionMemorySize(f *compiledWasmFunction) {
 	e.push(uint64(len(f.memory.Buffer)) / wasm.PageSize)
 }
+
+func (e *engine) compileWasmFunction(f *wasm.FunctionInstance) (*compiledWasmFunction, error) {
+	ir, err := wazeroir.Compile(f)
+	if err != nil {
+		return nil, fmt.Errorf("failed to lower to wazeroir: %w", err)
+	}
+
+	compiler, err := newCompiler(e, f, ir)
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize assembly builder: %w", err)
+	}
+
+	compiler.emitPreamble()
+
+	for _, op := range ir.Operations {
+		var err error
+		switch o := op.(type) {
+		case *wazeroir.OperationUnreachable:
+			compiler.compileUnreachable()
+		case *wazeroir.OperationLabel:
+			err = compiler.compileLabel(o)
+		case *wazeroir.OperationBr:
+			err = compiler.compileBr(o)
+		case *wazeroir.OperationBrIf:
+			err = compiler.compileBrIf(o)
+		case *wazeroir.OperationBrTable:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationCall:
+			err = compiler.compileCall(o)
+		case *wazeroir.OperationCallIndirect:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationDrop:
+			err = compiler.compileDrop(o)
+		case *wazeroir.OperationSelect:
+			err = compiler.compileSelect()
+		case *wazeroir.OperationPick:
+			err = compiler.compilePick(o)
+		case *wazeroir.OperationSwap:
+			err = compiler.compileSwap(o)
+		case *wazeroir.OperationGlobalGet:
+			err = compiler.compileGlobalGet(o)
+		case *wazeroir.OperationGlobalSet:
+			err = compiler.compileGlobalSet(o)
+		case *wazeroir.OperationLoad:
+			err = compiler.compileLoad(o)
+		case *wazeroir.OperationLoad8:
+			err = compiler.compileLoad8(o)
+		case *wazeroir.OperationLoad16:
+			err = compiler.compileLoad16(o)
+		case *wazeroir.OperationLoad32:
+			err = compiler.compileLoad32(o)
+		case *wazeroir.OperationStore:
+			err = compiler.compileStore(o)
+		case *wazeroir.OperationStore8:
+			err = compiler.compileStore8(o)
+		case *wazeroir.OperationStore16:
+			err = compiler.compileStore16(o)
+		case *wazeroir.OperationStore32:
+			err = compiler.compileStore32(o)
+		case *wazeroir.OperationMemorySize:
+			compiler.compileMemorySize()
+		case *wazeroir.OperationMemoryGrow:
+			compiler.compileMemoryGrow()
+		case *wazeroir.OperationConstI32:
+			err = compiler.compileConstI32(o)
+		case *wazeroir.OperationConstI64:
+			err = compiler.compileConstI64(o)
+		case *wazeroir.OperationConstF32:
+			err = compiler.compileConstF32(o)
+		case *wazeroir.OperationConstF64:
+			err = compiler.compileConstF64(o)
+		case *wazeroir.OperationEq:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationNe:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationEqz:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationLt:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationGt:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationLe:
+			err = compiler.compileLe(o)
+		case *wazeroir.OperationGe:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationAdd:
+			err = compiler.compileAdd(o)
+		case *wazeroir.OperationSub:
+			err = compiler.compileSub(o)
+		case *wazeroir.OperationMul:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationClz:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationCtz:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationPopcnt:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationDiv:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationRem:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationAnd:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationOr:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationXor:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationShl:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationShr:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationRotl:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationRotr:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationAbs:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationNeg:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationCeil:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationFloor:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationTrunc:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationNearest:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationSqrt:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationMin:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationMax:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationCopysign:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationI32WrapFromI64:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationITruncFromF:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationFConvertFromI:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationF32DemoteFromF64:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationF64PromoteFromF32:
+		case *wazeroir.OperationI32ReinterpretFromF32,
+			*wazeroir.OperationI64ReinterpretFromF64,
+			*wazeroir.OperationF32ReinterpretFromI32,
+			*wazeroir.OperationF64ReinterpretFromI64:
+			err = fmt.Errorf("unsupported operation")
+		case *wazeroir.OperationExtend:
+			err = fmt.Errorf("unsupported operation")
+		}
+		if err != nil {
+			return nil, fmt.Errorf("failed to compile operation %s: %w", op.Kind().String(), err)
+		}
+	}
+
+	code, maxStackPointer, err := compiler.compile()
+	if err != nil {
+		return nil, fmt.Errorf("failed to compile: %w", err)
+	}
+
+	cf := &compiledWasmFunction{
+		source:          f,
+		codeSegment:     code,
+		paramCount:      uint64(len(f.Signature.ParamTypes)),
+		resultCount:     uint64(len(f.Signature.ResultTypes)),
+		memory:          f.ModuleInstance.Memory,
+		maxStackPointer: maxStackPointer,
+	}
+	if cf.memory != nil && len(cf.memory.Buffer) > 0 {
+		cf.memoryAddress = uintptr(unsafe.Pointer(&cf.memory.Buffer[0]))
+	}
+	if len(f.ModuleInstance.Globals) > 0 {
+		cf.globalSliceAddress = uintptr(unsafe.Pointer(&f.ModuleInstance.Globals[0]))
+	}
+	cf.codeInitialAddress = uintptr(unsafe.Pointer(&cf.codeSegment[0]))
+	return cf, nil
+}
diff --git a/wasm/jit/jit_amd64.go b/wasm/jit/jit_amd64.go
index c42a7e59..ba9a9b08 100644
--- a/wasm/jit/jit_amd64.go
+++ b/wasm/jit/jit_amd64.go
@@ -7,7 +7,6 @@ import (
 	"encoding/binary"
 	"fmt"
 	"math"
-	"unsafe"
 
 	asm "github.com/twitchyliquid64/golang-asm"
 	"github.com/twitchyliquid64/golang-asm/obj"
@@ -24,261 +23,22 @@ import (
 // memory is the pointer to the first byte of memoryInstance.Buffer slice to be used by the target function.
 func jitcall(codeSegment, engine, memory uintptr)
 
-func (e *engine) compileWasmFunction(f *wasm.FunctionInstance) (*compiledWasmFunction, error) {
-	ir, err := wazeroir.Compile(f)
-	if err != nil {
-		return nil, fmt.Errorf("failed to lower to wazeroir: %w", err)
-	}
-
-	// We can choose arbitrary number instead of 1024 which indicates the cache size in the builder.
+func newCompiler(eng *engine, f *wasm.FunctionInstance, ir *wazeroir.CompilationResult) (compiler, error) {
+	// We can choose arbitrary number instead of 1024 which indicates the cache size in the compiler.
 	// TODO: optimize the number.
 	b, err := asm.NewBuilder("amd64", 1024)
 	if err != nil {
 		return nil, fmt.Errorf("failed to create a new assembly builder: %w", err)
 	}
-	builder := &amd64Builder{
-		eng: e, f: f, builder: b, locationStack: newValueLocationStack(), ir: ir,
+
+	return &amd64Compiler{
+		eng: eng, f: f, builder: b, locationStack: newValueLocationStack(), ir: ir,
 		labelInitialInstructions: make(map[string]*obj.Prog),
 		onLabelStartCallbacks:    make(map[string][]func(*obj.Prog)),
-	}
-
-	// We assume all function parameters are already pushed onto the stack by
-	// the caller.
-	builder.pushFunctionParams()
-
-	// Initialize the reserved registers first of all.
-	builder.initializeReservedRegisters()
-	// Now move onto the function body to compile each wazeroir operation.
-	for _, op := range ir.Operations {
-		switch o := op.(type) {
-		case *wazeroir.OperationUnreachable:
-			builder.handleUnreachable()
-		case *wazeroir.OperationLabel:
-			if err := builder.handleLabel(o); err != nil {
-				return nil, fmt.Errorf("error handling label operation: %w", err)
-			}
-		case *wazeroir.OperationBr:
-			if err := builder.handleBr(o); err != nil {
-				return nil, fmt.Errorf("error handling br operation: %w", err)
-			}
-		case *wazeroir.OperationBrIf:
-			if err := builder.handleBrIf(o); err != nil {
-				return nil, fmt.Errorf("error handling br_if operation: %w", err)
-			}
-		case *wazeroir.OperationBrTable:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationCall:
-			if err := builder.handleCall(o); err != nil {
-				return nil, fmt.Errorf("error handling call operation: %w", err)
-			}
-		case *wazeroir.OperationCallIndirect:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationDrop:
-			if err := builder.handleDrop(o); err != nil {
-				return nil, fmt.Errorf("error handling drop operation: %w", err)
-			}
-		case *wazeroir.OperationSelect:
-			if err := builder.handleSelect(); err != nil {
-				return nil, fmt.Errorf("error handling select operation: %w", err)
-			}
-		case *wazeroir.OperationPick:
-			if err := builder.handlePick(o); err != nil {
-				return nil, fmt.Errorf("error handling pick operation: %w", err)
-			}
-		case *wazeroir.OperationSwap:
-			if err := builder.handleSwap(o); err != nil {
-				return nil, fmt.Errorf("error handling swap operation: %w", err)
-			}
-		case *wazeroir.OperationGlobalGet:
-			if err := builder.handleGlobalGet(o); err != nil {
-				return nil, fmt.Errorf("error handling global.get operation: %w", err)
-			}
-		case *wazeroir.OperationGlobalSet:
-			if err := builder.handleGlobalSet(o); err != nil {
-				return nil, fmt.Errorf("error handling global.set operation: %w", err)
-			}
-		case *wazeroir.OperationLoad:
-			if err := builder.handleLoad(o); err != nil {
-				return nil, fmt.Errorf("error handling load operation: %w", err)
-			}
-		case *wazeroir.OperationLoad8:
-			if err := builder.handleLoad8(o); err != nil {
-				return nil, fmt.Errorf("error handling load8 operation: %w", err)
-			}
-		case *wazeroir.OperationLoad16:
-			if err := builder.handleLoad16(o); err != nil {
-				return nil, fmt.Errorf("error handling load16 operation: %w", err)
-			}
-		case *wazeroir.OperationLoad32:
-			if err := builder.handleLoad32(o); err != nil {
-				return nil, fmt.Errorf("error handling load16 operation: %w", err)
-			}
-		case *wazeroir.OperationStore:
-			if err := builder.handleStore(o); err != nil {
-				return nil, fmt.Errorf("error handling store operation: %w", err)
-			}
-		case *wazeroir.OperationStore8:
-			if err := builder.handleStore8(o); err != nil {
-				return nil, fmt.Errorf("error handling store8 operation: %w", err)
-			}
-		case *wazeroir.OperationStore16:
-			if err := builder.handleStore16(o); err != nil {
-				return nil, fmt.Errorf("error handling store16 operation: %w", err)
-			}
-		case *wazeroir.OperationStore32:
-			if err := builder.handleStore32(o); err != nil {
-				return nil, fmt.Errorf("error handling store32 operation: %w", err)
-			}
-		case *wazeroir.OperationMemorySize:
-			builder.handleMemorySize()
-		case *wazeroir.OperationMemoryGrow:
-			builder.handleMemoryGrow()
-		case *wazeroir.OperationConstI32:
-			if err := builder.handleConstI32(o); err != nil {
-				return nil, fmt.Errorf("error handling i32.const operation: %w", err)
-			}
-		case *wazeroir.OperationConstI64:
-			if err := builder.handleConstI64(o); err != nil {
-				return nil, fmt.Errorf("error handling i64.const operation: %w", err)
-			}
-		case *wazeroir.OperationConstF32:
-			if err := builder.handleConstF32(o); err != nil {
-				return nil, fmt.Errorf("error handling f32.const operation: %w", err)
-			}
-		case *wazeroir.OperationConstF64:
-			if err := builder.handleConstF64(o); err != nil {
-				return nil, fmt.Errorf("error handling f64.const operation: %w", err)
-			}
-		case *wazeroir.OperationEq:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationNe:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationEqz:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationLt:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationGt:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationLe:
-			if err := builder.handleLe(o); err != nil {
-				return nil, fmt.Errorf("error handling le operation: %w", err)
-			}
-		case *wazeroir.OperationGe:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationAdd:
-			if err := builder.handleAdd(o); err != nil {
-				return nil, fmt.Errorf("error handling add operation: %w", err)
-			}
-		case *wazeroir.OperationSub:
-			if err := builder.handleSub(o); err != nil {
-				return nil, fmt.Errorf("error handling sub operation: %w", err)
-			}
-		case *wazeroir.OperationMul:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationClz:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationCtz:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationPopcnt:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationDiv:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationRem:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationAnd:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationOr:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationXor:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationShl:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationShr:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationRotl:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationRotr:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationAbs:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationNeg:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationCeil:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationFloor:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationTrunc:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationNearest:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationSqrt:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationMin:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationMax:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationCopysign:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationI32WrapFromI64:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationITruncFromF:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationFConvertFromI:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationF32DemoteFromF64:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationF64PromoteFromF32:
-		case *wazeroir.OperationI32ReinterpretFromF32,
-			*wazeroir.OperationI64ReinterpretFromF64,
-			*wazeroir.OperationF32ReinterpretFromI32,
-			*wazeroir.OperationF64ReinterpretFromI64:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		case *wazeroir.OperationExtend:
-			return nil, fmt.Errorf("unsupported operation in JIT compiler: %v", o)
-		default:
-			return nil, fmt.Errorf("unreachable: a bug in JIT compiler")
-		}
-	}
-
-	code, err := builder.compile()
-	if err != nil {
-		return nil, fmt.Errorf("failed to assemble: %w", err)
-	}
-	return builder.newCompiledWasmFunction(code), nil
-}
-
-func (b *amd64Builder) newCompiledWasmFunction(code []byte) *compiledWasmFunction {
-	cf := &compiledWasmFunction{
-		source:          b.f,
-		codeSegment:     code,
-		params:          uint64(len(b.f.Signature.ParamTypes)),
-		results:         uint64(len(b.f.Signature.ResultTypes)),
-		memory:          b.f.ModuleInstance.Memory,
-		maxStackPointer: b.locationStack.maxStackPointer,
-	}
-	if cf.memory != nil && len(cf.memory.Buffer) > 0 {
-		cf.memoryAddress = uintptr(unsafe.Pointer(&cf.memory.Buffer[0]))
-	}
-	if len(b.f.ModuleInstance.Globals) > 0 {
-		cf.globalSliceAddress = uintptr(unsafe.Pointer(&b.f.ModuleInstance.Globals[0]))
-	}
-	cf.codeInitialAddress = uintptr(unsafe.Pointer(&cf.codeSegment[0]))
-	return cf
-}
-
-func (b *amd64Builder) pushFunctionParams() {
-	for _, t := range b.f.Signature.ParamTypes {
-		loc := b.locationStack.pushValueOnStack()
-		switch t {
-		case wasm.ValueTypeI32, wasm.ValueTypeI64:
-			loc.setRegisterType(generalPurposeRegisterTypeInt)
-		case wasm.ValueTypeF32, wasm.ValueTypeF64:
-			loc.setRegisterType(generalPurposeRegisterTypeFloat)
-		}
-	}
+	}, nil
 }
 
-type amd64Builder struct {
+type amd64Compiler struct {
 	eng *engine
 	f   *wasm.FunctionInstance
 	ir  *wazeroir.CompilationResult
@@ -297,16 +57,24 @@ type amd64Builder struct {
 	requireFunctionCallReturnAddressOffsetResolution []*obj.Prog
 }
 
-func (b *amd64Builder) compile() ([]byte, error) {
-	code, err := mmapCodeSegment(b.builder.Assemble())
+func (c *amd64Compiler) emitPreamble() {
+	// We assume all function parameters are already pushed onto the stack by
+	// the caller.
+	c.pushFunctionParams()
+	// Initialize the reserved registers first of all.
+	c.initializeReservedRegisters()
+}
+
+func (c *amd64Compiler) compile() ([]byte, uint64, error) {
+	code, err := mmapCodeSegment(c.builder.Assemble())
 	if err != nil {
-		return nil, err
+		return nil, 0, err
 	}
 	// As we cannot read RIP register directly,
 	// we calculate now the offset to the next instruction
 	// relative to the beginning of this function body.
 	const operandSizeBytes = 8
-	for _, obj := range b.requireFunctionCallReturnAddressOffsetResolution {
+	for _, obj := range c.requireFunctionCallReturnAddressOffsetResolution {
 		// Skip MOV, and the register(rax): "0x49, 0xbd"
 		start := obj.Pc + 2
 		// obj.Link = setting offset to memory
@@ -316,40 +84,52 @@ func (b *amd64Builder) compile() ([]byte, error) {
 		afterReturnInst := obj.Link.Link.Link.Link
 		binary.LittleEndian.PutUint64(code[start:start+operandSizeBytes], uint64(afterReturnInst.Pc))
 	}
-	return code, nil
+	return code, c.locationStack.maxStackPointer, nil
+}
+
+func (c *amd64Compiler) pushFunctionParams() {
+	for _, t := range c.f.Signature.ParamTypes {
+		loc := c.locationStack.pushValueOnStack()
+		switch t {
+		case wasm.ValueTypeI32, wasm.ValueTypeI64:
+			loc.setRegisterType(generalPurposeRegisterTypeInt)
+		case wasm.ValueTypeF32, wasm.ValueTypeF64:
+			loc.setRegisterType(generalPurposeRegisterTypeFloat)
+		}
+	}
 }
 
-func (b *amd64Builder) addInstruction(prog *obj.Prog) {
-	b.builder.AddInstruction(prog)
-	if b.setJmpOrigin != nil {
-		b.setJmpOrigin.To.SetTarget(prog)
-		b.setJmpOrigin = nil
+func (c *amd64Compiler) addInstruction(prog *obj.Prog) {
+	c.builder.AddInstruction(prog)
+	if c.setJmpOrigin != nil {
+		c.setJmpOrigin.To.SetTarget(prog)
+		c.setJmpOrigin = nil
 	}
 }
 
-func (b *amd64Builder) newProg() (prog *obj.Prog) {
-	prog = b.builder.NewProg()
+func (c *amd64Compiler) newProg() (prog *obj.Prog) {
+	prog = c.builder.NewProg()
 	return
 }
 
-func (b *amd64Builder) handleUnreachable() {
-	b.releaseAllRegistersToStack()
-	b.setJITStatus(jitCallStatusCodeUnreachable)
-	b.returnFunction()
+func (c *amd64Compiler) compileUnreachable() {
+	c.releaseAllRegistersToStack()
+	c.setJITStatus(jitCallStatusCodeUnreachable)
+	c.returnFunction()
 }
 
-func (b *amd64Builder) handleSwap(o *wazeroir.OperationSwap) error {
-	index := len(b.locationStack.stack) - 1 - o.Depth
+func (c *amd64Compiler) compileSwap(o *wazeroir.OperationSwap) error {
+	index := len(c.locationStack.stack) - 1 - o.Depth
 	// Note that, in theory, the register types and value types
 	// are the same between these swap targets as swap operations
 	// are generated from local.set,tee instructions in Wasm.
-	x1 := b.locationStack.stack[len(b.locationStack.stack)-1]
-	x2 := b.locationStack.stack[index]
+	x1 := c.locationStack.stack[len(c.locationStack.stack)-1]
+	x2 := c.locationStack.stack[index]
 
 	// If x1 is on the conditional register, we must move it to a gp
 	// register before swap.
 	if x1.onConditionalRegister() {
-		if err := b.moveConditionalToGeneralPurposeRegister(x1); err != nil {
+		if err := c.moveConditionalToGeneralPurposeRegister(x1); err != nil {
 			return err
 		}
 	}
@@ -359,129 +139,129 @@ func (b *amd64Builder) handleSwap(o *wazeroir.OperationSwap) error {
 	} else if x1.onRegister() && x2.onStack() {
 		reg := x1.register
 		// Save x1's value to the temporary top of the stack.
-		tmpStackLocation := b.locationStack.pushValueOnRegister(reg)
-		b.releaseRegisterToStack(tmpStackLocation)
+		tmpStackLocation := c.locationStack.pushValueOnRegister(reg)
+		c.releaseRegisterToStack(tmpStackLocation)
 		// Then move the x2's value to the x1's register location.
 		x2.register = reg
-		b.moveStackToRegister(x2)
+		c.moveStackToRegister(x2)
 		// Now move the x1's value to the x1's stack location.
-		b.releaseRegisterToStack(x1)
+		c.releaseRegisterToStack(x1)
 		// Next we move the saved x1's value to the register.
 		tmpStackLocation.setRegister(reg)
-		b.moveStackToRegister(tmpStackLocation)
+		c.moveStackToRegister(tmpStackLocation)
 		// Finally move the x1's value in the register to the x2's stack location.
-		b.locationStack.releaseRegister(x1)
-		b.locationStack.releaseRegister(tmpStackLocation)
+		c.locationStack.releaseRegister(x1)
+		c.locationStack.releaseRegister(tmpStackLocation)
 		x2.setRegister(reg)
-		b.locationStack.markRegisterUsed(reg)
-		_ = b.locationStack.pop() // Delete tmpStackLocation.
+		c.locationStack.markRegisterUsed(reg)
+		_ = c.locationStack.pop() // Delete tmpStackLocation.
 	} else if x1.onStack() && x2.onRegister() {
 		reg := x2.register
 		// Save x2's value to the temporary top of the stack.
-		tmpStackLocation := b.locationStack.pushValueOnRegister(reg)
-		b.releaseRegisterToStack(tmpStackLocation)
+		tmpStackLocation := c.locationStack.pushValueOnRegister(reg)
+		c.releaseRegisterToStack(tmpStackLocation)
 		// Then move the x1's value to the x2's register location.
 		x1.register = reg
-		b.moveStackToRegister(x1)
+		c.moveStackToRegister(x1)
 		// Now move the x1's value to the x2's stack location.
-		b.releaseRegisterToStack(x2)
+		c.releaseRegisterToStack(x2)
 		// Next we move the saved x2's value to the register.
 		tmpStackLocation.setRegister(reg)
-		b.moveStackToRegister(tmpStackLocation)
+		c.moveStackToRegister(tmpStackLocation)
 		// Finally move the x2's value in the register to the x2's stack location.
-		b.locationStack.releaseRegister(x2)
-		b.locationStack.releaseRegister(tmpStackLocation)
+		c.locationStack.releaseRegister(x2)
+		c.locationStack.releaseRegister(tmpStackLocation)
 		x1.setRegister(reg)
-		b.locationStack.markRegisterUsed(reg)
-		_ = b.locationStack.pop() // Delete tmpStackLocation.
+		c.locationStack.markRegisterUsed(reg)
+		_ = c.locationStack.pop() // Delete tmpStackLocation.
 	} else if x1.onStack() && x2.onStack() {
-		reg, err := b.allocateRegister(x1.registerType())
+		reg, err := c.allocateRegister(x1.registerType())
 		if err != nil {
 			return err
 		}
 		// First we move the x2's value to the temp register.
 		x2.setRegister(reg)
-		b.moveStackToRegister(x2)
+		c.moveStackToRegister(x2)
 		// Save x2's value to the temporary top of the stack.
-		tmpStackLocation := b.locationStack.pushValueOnRegister(reg)
-		b.releaseRegisterToStack(tmpStackLocation)
+		tmpStackLocation := c.locationStack.pushValueOnRegister(reg)
+		c.releaseRegisterToStack(tmpStackLocation)
 		// Then move the x1's value to the x2's register location.
 		x1.register = reg
-		b.moveStackToRegister(x1)
+		c.moveStackToRegister(x1)
 		// Now move the x1's value to the x2's stack location.
-		b.releaseRegisterToStack(x2)
+		c.releaseRegisterToStack(x2)
 		// Next we move the saved x2's value to the register.
 		tmpStackLocation.setRegister(reg)
-		b.moveStackToRegister(tmpStackLocation)
+		c.moveStackToRegister(tmpStackLocation)
 		// Finally move the x2's value in the register to the x2's stack location.
-		b.locationStack.releaseRegister(x2)
-		b.locationStack.releaseRegister(tmpStackLocation)
+		c.locationStack.releaseRegister(x2)
+		c.locationStack.releaseRegister(tmpStackLocation)
 		x1.setRegister(reg)
-		b.locationStack.markRegisterUsed(reg)
-		_ = b.locationStack.pop() // Delete tmpStackLocation.
+		c.locationStack.markRegisterUsed(reg)
+		_ = c.locationStack.pop() // Delete tmpStackLocation.
 	}
 	return nil
 }
 
 const globalInstanceValueOffset = 8
 
-func (b *amd64Builder) handleGlobalGet(o *wazeroir.OperationGlobalGet) error {
-	intReg, err := b.allocateRegister(generalPurposeRegisterTypeInt)
+func (c *amd64Compiler) compileGlobalGet(o *wazeroir.OperationGlobalGet) error {
+	intReg, err := c.allocateRegister(generalPurposeRegisterTypeInt)
 	if err != nil {
 		return err
 	}
 
 	// First, move the pointer to the global slice into the allocated register.
-	moveGlobalSlicePointer := b.newProg()
+	moveGlobalSlicePointer := c.newProg()
 	moveGlobalSlicePointer.As = x86.AMOVQ
 	moveGlobalSlicePointer.To.Type = obj.TYPE_REG
 	moveGlobalSlicePointer.To.Reg = intReg
 	moveGlobalSlicePointer.From.Type = obj.TYPE_MEM
 	moveGlobalSlicePointer.From.Reg = reservedRegisterForEngine
 	moveGlobalSlicePointer.From.Offset = engineglobalSliceAddressOffset
-	b.addInstruction(moveGlobalSlicePointer)
+	c.addInstruction(moveGlobalSlicePointer)
 
 	// Then, get the memory location of the target global instance's pointer.
-	getGlobalInstanceLocation := b.newProg()
+	getGlobalInstanceLocation := c.newProg()
 	getGlobalInstanceLocation.As = x86.AADDQ
 	getGlobalInstanceLocation.To.Type = obj.TYPE_REG
 	getGlobalInstanceLocation.To.Reg = intReg
 	getGlobalInstanceLocation.From.Type = obj.TYPE_CONST
 	getGlobalInstanceLocation.From.Offset = 8 * int64(o.Index)
-	b.addInstruction(getGlobalInstanceLocation)
+	c.addInstruction(getGlobalInstanceLocation)
 
 	// Now, move the location of the global instance into the register.
-	getGlobalInstancePointer := b.newProg()
+	getGlobalInstancePointer := c.newProg()
 	getGlobalInstancePointer.As = x86.AMOVQ
 	getGlobalInstancePointer.To.Type = obj.TYPE_REG
 	getGlobalInstancePointer.To.Reg = intReg
 	getGlobalInstancePointer.From.Type = obj.TYPE_MEM
 	getGlobalInstancePointer.From.Reg = intReg
-	b.addInstruction(getGlobalInstancePointer)
+	c.addInstruction(getGlobalInstancePointer)
 
 	// When an integer, reuse the pointer register for the value. Otherwise, allocate a float register for it.
 	valueReg := intReg
-	wasmType := b.f.ModuleInstance.Globals[o.Index].Type.ValType
+	wasmType := c.f.ModuleInstance.Globals[o.Index].Type.ValType
 	switch wasmType {
 	case wasm.ValueTypeF32, wasm.ValueTypeF64:
-		valueReg, err = b.allocateRegister(generalPurposeRegisterTypeFloat)
+		valueReg, err = c.allocateRegister(generalPurposeRegisterTypeFloat)
 		if err != nil {
 			return err
 		}
 	}
 
 	// Using the register holding the pointer to the target instance, move its value into a register.
-	moveValue := b.newProg()
+	moveValue := c.newProg()
 	moveValue.As = x86.AMOVQ
 	moveValue.To.Type = obj.TYPE_REG
 	moveValue.To.Reg = valueReg
 	moveValue.From.Type = obj.TYPE_MEM
 	moveValue.From.Reg = intReg
 	moveValue.From.Offset = globalInstanceValueOffset
-	b.addInstruction(moveValue)
+	c.addInstruction(moveValue)
 
 	// Record that the retrieved global value on the top of the stack is now in a register.
-	loc := b.locationStack.pushValueOnRegister(valueReg)
+	loc := c.locationStack.pushValueOnRegister(valueReg)
 	switch wasmType {
 	case wasm.ValueTypeI32, wasm.ValueTypeI64:
 		loc.setRegisterType(generalPurposeRegisterTypeInt)
@@ -491,92 +271,92 @@ func (b *amd64Builder) handleGlobalGet(o *wazeroir.OperationGlobalGet) error {
 	return nil
 }
 
-func (b *amd64Builder) handleGlobalSet(o *wazeroir.OperationGlobalSet) error {
+func (c *amd64Compiler) compileGlobalSet(o *wazeroir.OperationGlobalSet) error {
 	// First, move the value to set into a temporary register.
-	val := b.locationStack.pop()
-	if err := b.ensureOnGeneralPurposeRegister(val); err != nil {
+	val := c.locationStack.pop()
+	if err := c.ensureOnGeneralPurposeRegister(val); err != nil {
 		return err
 	}
 
 	// Allocate a register to hold the memory location of the target global instance.
-	intReg, err := b.allocateRegister(generalPurposeRegisterTypeInt)
+	intReg, err := c.allocateRegister(generalPurposeRegisterTypeInt)
 	if err != nil {
 		return err
 	}
 
 	// First, move the pointer to the global slice into the allocated register.
-	moveGlobalSlicePointer := b.newProg()
+	moveGlobalSlicePointer := c.newProg()
 	moveGlobalSlicePointer.As = x86.AMOVQ
 	moveGlobalSlicePointer.To.Type = obj.TYPE_REG
 	moveGlobalSlicePointer.To.Reg = intReg
 	moveGlobalSlicePointer.From.Type = obj.TYPE_MEM
 	moveGlobalSlicePointer.From.Reg = reservedRegisterForEngine
 	moveGlobalSlicePointer.From.Offset = engineglobalSliceAddressOffset
-	b.addInstruction(moveGlobalSlicePointer)
+	c.addInstruction(moveGlobalSlicePointer)
 
 	// Then, get the memory location of the target global instance's pointer.
-	getGlobalInstanceLocation := b.newProg()
+	getGlobalInstanceLocation := c.newProg()
 	getGlobalInstanceLocation.As = x86.AADDQ
 	getGlobalInstanceLocation.To.Type = obj.TYPE_REG
 	getGlobalInstanceLocation.To.Reg = intReg
 	getGlobalInstanceLocation.From.Type = obj.TYPE_CONST
 	getGlobalInstanceLocation.From.Offset = 8 * int64(o.Index)
-	b.addInstruction(getGlobalInstanceLocation)
+	c.addInstruction(getGlobalInstanceLocation)
 
 	// Now, move the location of the global instance into the register.
-	getGlobalInstancePointer := b.newProg()
+	getGlobalInstancePointer := c.newProg()
 	getGlobalInstancePointer.As = x86.AMOVQ
 	getGlobalInstancePointer.To.Type = obj.TYPE_REG
 	getGlobalInstancePointer.To.Reg = intReg
 	getGlobalInstancePointer.From.Type = obj.TYPE_MEM
 	getGlobalInstancePointer.From.Reg = intReg
-	b.addInstruction(getGlobalInstancePointer)
+	c.addInstruction(getGlobalInstancePointer)
 
 	// Now ready to write the value to the global instance location.
-	moveValue := b.newProg()
+	moveValue := c.newProg()
 	moveValue.As = x86.AMOVQ
 	moveValue.From.Type = obj.TYPE_REG
 	moveValue.From.Reg = val.register
 	moveValue.To.Type = obj.TYPE_MEM
 	moveValue.To.Reg = intReg
 	moveValue.To.Offset = globalInstanceValueOffset
-	b.addInstruction(moveValue)
+	c.addInstruction(moveValue)
 
 	// Since the value is now written to memory, release the value register.
-	b.locationStack.releaseRegister(val)
+	c.locationStack.releaseRegister(val)
 	return nil
 }
 
-func (b *amd64Builder) handleBr(o *wazeroir.OperationBr) error {
+func (c *amd64Compiler) compileBr(o *wazeroir.OperationBr) error {
 	if o.Target.IsReturnTarget() {
 		// Release all the registers as our calling convention requires the callee-save.
-		b.releaseAllRegistersToStack()
-		b.setJITStatus(jitCallStatusCodeReturned)
+		c.releaseAllRegistersToStack()
+		c.setJITStatus(jitCallStatusCodeReturned)
 		// Then return from this function.
-		b.returnFunction()
+		c.returnFunction()
 	} else {
 		labelKey := o.Target.String()
-		targetNumCallers := b.ir.LabelCallers[labelKey]
+		targetNumCallers := c.ir.LabelCallers[labelKey]
 		if targetNumCallers > 1 {
 			// If the number of callers to the target label is larget than one,
 			// we have multiple origins to the target branch. In that case,
 			// we must have unique register state.
-			b.preJumpRegisterAdjustment()
+			c.preJumpRegisterAdjustment()
 		}
-		jmp := b.newProg()
+		jmp := c.newProg()
 		jmp.As = obj.AJMP
 		jmp.To.Type = obj.TYPE_BRANCH
-		b.addInstruction(jmp)
-		b.assignJumpTarget(labelKey, jmp)
+		c.addInstruction(jmp)
+		c.assignJumpTarget(labelKey, jmp)
 	}
 	return nil
 }
 
-func (b *amd64Builder) handleBrIf(o *wazeroir.OperationBrIf) error {
-	cond := b.locationStack.pop()
+func (c *amd64Compiler) compileBrIf(o *wazeroir.OperationBrIf) error {
+	cond := c.locationStack.pop()
 	var jmpWithCond *obj.Prog
 	if cond.onConditionalRegister() {
-		jmpWithCond = b.newProg()
+		jmpWithCond = c.newProg()
 		jmpWithCond.To.Type = obj.TYPE_BRANCH
 		switch cond.conditionalRegister {
 		case conditionalRegisterStateE:
@@ -618,26 +398,26 @@ func (b *amd64Builder) handleBrIf(o *wazeroir.OperationBrIf) error {
 			// This case even worse, the operand is not on a allocated register, but
 			// actually in the stack memory, so we have to assign a register to it
 			// before we judge if we should jump to the Then branch or Else.
-			if err := b.moveStackToRegisterWithAllocation(cond.registerType(), cond); err != nil {
+			if err := c.moveStackToRegisterWithAllocation(cond.registerType(), cond); err != nil {
 				return err
 			}
 		}
 		// Check if the value not equals zero.
-		prog := b.newProg()
+		prog := c.newProg()
 		prog.As = x86.ACMPQ
 		prog.From.Type = obj.TYPE_REG
 		prog.From.Reg = cond.register
 		prog.To.Type = obj.TYPE_CONST
 		prog.To.Offset = 0
-		b.addInstruction(prog)
+		c.addInstruction(prog)
 		// Emit jump instruction which jumps when the value does not equals zero.
-		jmpWithCond = b.newProg()
+		jmpWithCond = c.newProg()
 		jmpWithCond.As = x86.AJNE
 		jmpWithCond.To.Type = obj.TYPE_BRANCH
 	}
 
 	// Make sure that the next coming label is the else jump target.
-	b.addInstruction(jmpWithCond)
+	c.addInstruction(jmpWithCond)
 	thenTarget, elseTarget := o.Then, o.Else
 
 	// Here's the diagram of how we organize the instructions necessarly for brif operation.
@@ -649,48 +429,48 @@ func (b *amd64Builder) handleBrIf(o *wazeroir.OperationBrIf) error {
 	// corresponding to either If's Else block or Br_if's else block in Wasm.
 
 	// Emit for else branches
-	saved := b.locationStack
-	b.locationStack = saved.clone()
+	saved := c.locationStack
+	c.locationStack = saved.clone()
 	if elseTarget.Target.IsReturnTarget() {
 		// Release all the registers as our calling convention requires the callee-save.
-		b.releaseAllRegistersToStack()
-		b.setJITStatus(jitCallStatusCodeReturned)
+		c.releaseAllRegistersToStack()
+		c.setJITStatus(jitCallStatusCodeReturned)
 		// Then return from this function.
-		b.returnFunction()
+		c.returnFunction()
 	} else {
 		elseLabelKey := elseTarget.Target.Label.String()
-		if b.ir.LabelCallers[elseLabelKey] > 1 {
-			b.preJumpRegisterAdjustment()
+		if c.ir.LabelCallers[elseLabelKey] > 1 {
+			c.preJumpRegisterAdjustment()
 		}
-		elseJmp := b.newProg()
+		elseJmp := c.newProg()
 		elseJmp.As = obj.AJMP
 		elseJmp.To.Type = obj.TYPE_BRANCH
-		b.addInstruction(elseJmp)
-		b.assignJumpTarget(elseLabelKey, elseJmp)
+		c.addInstruction(elseJmp)
+		c.assignJumpTarget(elseLabelKey, elseJmp)
 	}
 
 	// Handle then branch.
-	b.setJmpOrigin = jmpWithCond
-	b.locationStack = saved
-	if err := b.emitDropRange(thenTarget.ToDrop); err != nil {
+	c.setJmpOrigin = jmpWithCond
+	c.locationStack = saved
+	if err := c.emitDropRange(thenTarget.ToDrop); err != nil {
 		return err
 	}
 	if thenTarget.Target.IsReturnTarget() {
 		// Release all the registers as our calling convention requires the callee-save.
-		b.releaseAllRegistersToStack()
-		b.setJITStatus(jitCallStatusCodeReturned)
+		c.releaseAllRegistersToStack()
+		c.setJITStatus(jitCallStatusCodeReturned)
 		// Then return from this function.
-		b.returnFunction()
+		c.returnFunction()
 	} else {
 		thenLabelKey := thenTarget.Target.Label.String()
-		if b.ir.LabelCallers[thenLabelKey] > 1 {
-			b.preJumpRegisterAdjustment()
+		if c.ir.LabelCallers[thenLabelKey] > 1 {
+			c.preJumpRegisterAdjustment()
 		}
-		thenJmp := b.newProg()
+		thenJmp := c.newProg()
 		thenJmp.As = obj.AJMP
 		thenJmp.To.Type = obj.TYPE_BRANCH
-		b.addInstruction(thenJmp)
-		b.assignJumpTarget(thenLabelKey, thenJmp)
+		c.addInstruction(thenJmp)
+		c.assignJumpTarget(thenLabelKey, thenJmp)
 	}
 	return nil
 }
@@ -698,67 +478,67 @@ func (b *amd64Builder) handleBrIf(o *wazeroir.OperationBrIf) error {
 // If a jump target has multiple callesr (origins),
 // we must have unique register states, so this function
 // must be called before such jump instruction.
-func (b *amd64Builder) preJumpRegisterAdjustment() {
+func (c *amd64Compiler) preJumpRegisterAdjustment() {
 	// For now, we just release all registers to memory.
 	// But this is obviously inefficient, so we come back here
 	// later once we finish the baseline implementation.
-	b.releaseAllRegistersToStack()
+	c.releaseAllRegistersToStack()
 }
 
-func (b *amd64Builder) assignJumpTarget(labelKey string, jmpInstruction *obj.Prog) {
-	jmpTarget, ok := b.labelInitialInstructions[labelKey]
+func (c *amd64Compiler) assignJumpTarget(labelKey string, jmpInstruction *obj.Prog) {
+	jmpTarget, ok := c.labelInitialInstructions[labelKey]
 	if ok {
 		jmpInstruction.To.SetTarget(jmpTarget)
 	} else {
-		b.onLabelStartCallbacks[labelKey] = append(b.onLabelStartCallbacks[labelKey], func(jmpTarget *obj.Prog) {
+		c.onLabelStartCallbacks[labelKey] = append(c.onLabelStartCallbacks[labelKey], func(jmpTarget *obj.Prog) {
 			jmpInstruction.To.SetTarget(jmpTarget)
 		})
 	}
 }
 
-func (b *amd64Builder) handleLabel(o *wazeroir.OperationLabel) error {
-	b.locationStack.sp = uint64(o.Label.OriginalStackLen)
+func (c *amd64Compiler) compileLabel(o *wazeroir.OperationLabel) error {
+	c.locationStack.sp = uint64(o.Label.OriginalStackLen)
 	// We use NOP as a beginning of instructions in a label.
 	// This should be eventually optimized out by assembler.
 	labelKey := o.Label.String()
-	labelBegin := b.newProg()
+	labelBegin := c.newProg()
 	labelBegin.As = obj.ANOP
-	b.addInstruction(labelBegin)
+	c.addInstruction(labelBegin)
 	// Save the instructions so that backward branching
 	// instructions can jump to this label.
-	b.labelInitialInstructions[labelKey] = labelBegin
+	c.labelInitialInstructions[labelKey] = labelBegin
 	// Invoke callbacks to notify the forward branching
 	// instructions can properly jump to this label.
-	for _, cb := range b.onLabelStartCallbacks[labelKey] {
+	for _, cb := range c.onLabelStartCallbacks[labelKey] {
 		cb(labelBegin)
 	}
 	// Now we don't need to call the callbacks.
-	delete(b.onLabelStartCallbacks, labelKey)
+	delete(c.onLabelStartCallbacks, labelKey)
 	return nil
 }
 
-func (b *amd64Builder) handleCall(o *wazeroir.OperationCall) error {
-	target := b.f.ModuleInstance.Functions[o.FunctionIndex]
+func (c *amd64Compiler) compileCall(o *wazeroir.OperationCall) error {
+	target := c.f.ModuleInstance.Functions[o.FunctionIndex]
 	if target.HostFunction != nil {
-		index := b.eng.compiledHostFunctionIndex[target]
-		b.callHostFunctionFromConstIndex(index)
+		index := c.eng.compiledHostFunctionIndex[target]
+		c.callHostFunctionFromConstIndex(index)
 	} else {
-		index := b.eng.compiledWasmFunctionIndex[target]
-		b.callFunctionFromConstIndex(index)
+		index := c.eng.compiledWasmFunctionIndex[target]
+		c.callFunctionFromConstIndex(index)
 	}
 	return nil
 }
-func (b *amd64Builder) handleDrop(o *wazeroir.OperationDrop) error {
-	return b.emitDropRange(o.Range)
+func (c *amd64Compiler) compileDrop(o *wazeroir.OperationDrop) error {
+	return c.emitDropRange(o.Range)
 }
 
-func (b *amd64Builder) emitDropRange(r *wazeroir.InclusiveRange) error {
+func (c *amd64Compiler) emitDropRange(r *wazeroir.InclusiveRange) error {
 	if r == nil {
 		return nil
 	} else if r.Start == 0 {
 		for i := 0; i < r.End; i++ {
-			if loc := b.locationStack.pop(); loc.onRegister() {
-				b.locationStack.releaseRegister(loc)
+			if loc := c.locationStack.pop(); loc.onRegister() {
+				c.locationStack.releaseRegister(loc)
 			}
 		}
 		return nil
@@ -770,7 +550,7 @@ func (b *amd64Builder) emitDropRange(r *wazeroir.InclusiveRange) error {
 		liveValues       []*valueLocation
 	)
 	for i := 0; i < r.Start; i++ {
-		live := b.locationStack.pop()
+		live := c.locationStack.pop()
 		if top == nil {
 			top = live
 			topIsConditional = top.onConditionalRegister()
@@ -778,8 +558,8 @@ func (b *amd64Builder) emitDropRange(r *wazeroir.InclusiveRange) error {
 		liveValues = append(liveValues, live)
 	}
 	for i := 0; i < r.End-r.Start+1; i++ {
-		if loc := b.locationStack.pop(); loc.onRegister() {
-			b.locationStack.releaseRegister(loc)
+		if loc := c.locationStack.pop(); loc.onRegister() {
+			c.locationStack.releaseRegister(loc)
 		}
 	}
 	for i := range liveValues {
@@ -788,19 +568,19 @@ func (b *amd64Builder) emitDropRange(r *wazeroir.InclusiveRange) error {
 			if topIsConditional {
 				// If the top is conditional, and it's not target of drop,
 				// we must assign it to the register before we emit any instructions here.
-				if err := b.moveConditionalToGeneralPurposeRegister(top); err != nil {
+				if err := c.moveConditionalToGeneralPurposeRegister(top); err != nil {
 					return err
 				}
 				topIsConditional = false
 			}
 			// Write the value in the old stack location to a register
-			if err := b.moveStackToRegisterWithAllocation(live.registerType(), live); err != nil {
+			if err := c.moveStackToRegisterWithAllocation(live.registerType(), live); err != nil {
 				return err
 			}
 			// Modify the location in the stack with new stack pointer.
-			b.locationStack.push(live)
+			c.locationStack.push(live)
 		} else if live.onRegister() {
-			b.locationStack.push(live)
+			c.locationStack.push(live)
 		}
 	}
 	return nil
@@ -812,42 +592,42 @@ func (b *amd64Builder) emitDropRange(r *wazeroir.InclusiveRange) error {
 // otherwise, [..., x2].
 // The emitted native code depends on whether the values are on
 // the physical registers or memory stack, or maybe conditional register.
-func (b *amd64Builder) handleSelect() error {
-	c := b.locationStack.pop()
-	if err := b.ensureOnGeneralPurposeRegister(c); err != nil {
+func (c *amd64Compiler) compileSelect() error {
+	cv := c.locationStack.pop()
+	if err := c.ensureOnGeneralPurposeRegister(cv); err != nil {
 		return err
 	}
 
-	x2 := b.locationStack.pop()
+	x2 := c.locationStack.pop()
 	// We do not consume x1 here, but modify the value according to
 	// the conditional value "c" above.
-	peekedX1 := b.locationStack.peek()
+	peekedX1 := c.locationStack.peek()
 
 	// Compare the conditional value with zero.
-	cmpZero := b.newProg()
+	cmpZero := c.newProg()
 	cmpZero.As = x86.ACMPQ
 	cmpZero.From.Type = obj.TYPE_REG
-	cmpZero.From.Reg = c.register
+	cmpZero.From.Reg = cv.register
 	cmpZero.To.Type = obj.TYPE_CONST
 	cmpZero.To.Offset = 0
-	b.addInstruction(cmpZero)
+	c.addInstruction(cmpZero)
 
 	// Now we can use c.register as temporary location.
 	// We alias it here for readability.
-	tmpRegister := c.register
+	tmpRegister := cv.register
 
 	// Set the jump if the top value is not zero.
-	jmpIfNotZero := b.newProg()
+	jmpIfNotZero := c.newProg()
 	jmpIfNotZero.As = x86.AJNE
 	jmpIfNotZero.To.Type = obj.TYPE_BRANCH
-	b.addInstruction(jmpIfNotZero)
+	c.addInstruction(jmpIfNotZero)
 
 	// If the value is zero, we must place the value of x2 onto the stack position of x1.
 
 	// First we copy the value of x2 to the temporary register if x2 is not currently on a register.
 	if x2.onStack() {
 		x2.register = tmpRegister
-		b.moveStackToRegister(x2)
+		c.moveStackToRegister(x2)
 	}
 
 	//
@@ -856,66 +636,66 @@ func (b *amd64Builder) handleSelect() error {
 
 	// Then release the value in the x2's register to the x1's stack position.
 	if peekedX1.onRegister() {
-		movX2ToX1 := b.newProg()
+		movX2ToX1 := c.newProg()
 		movX2ToX1.As = x86.AMOVQ
 		movX2ToX1.From.Type = obj.TYPE_REG
 		movX2ToX1.From.Reg = x2.register
 		movX2ToX1.To.Type = obj.TYPE_REG
 		movX2ToX1.To.Reg = peekedX1.register
-		b.addInstruction(movX2ToX1)
+		c.addInstruction(movX2ToX1)
 	} else {
 		peekedX1.register = x2.register
-		b.releaseRegisterToStack(peekedX1) // Note inside we mark the register unused!
+		c.releaseRegisterToStack(peekedX1) // Note inside we mark the register unused!
 	}
 
 	// Else, we don't need to adjust value, just need to jump to the next instruction.
-	b.setJmpOrigin = jmpIfNotZero
+	c.setJmpOrigin = jmpIfNotZero
 
 	// In any case, we don't need x2 and c anymore!
-	b.locationStack.releaseRegister(x2)
-	b.locationStack.releaseRegister(c)
+	c.locationStack.releaseRegister(x2)
+	c.locationStack.releaseRegister(cv)
 	return nil
 }
 
-func (b *amd64Builder) handlePick(o *wazeroir.OperationPick) error {
+func (c *amd64Compiler) compilePick(o *wazeroir.OperationPick) error {
 	// TODO: if we track the type of values on the stack,
 	// we could optimize the instruction according to the bit size of the value.
 	// For now, we just move the entire register i.e. as a quad word (8 bytes).
-	pickTarget := b.locationStack.stack[b.locationStack.sp-1-uint64(o.Depth)]
-	reg, err := b.allocateRegister(pickTarget.registerType())
+	pickTarget := c.locationStack.stack[c.locationStack.sp-1-uint64(o.Depth)]
+	reg, err := c.allocateRegister(pickTarget.registerType())
 	if err != nil {
 		return err
 	}
 
 	if pickTarget.onRegister() {
-		prog := b.newProg()
+		prog := c.newProg()
 		prog.As = x86.AMOVQ
 		prog.From.Type = obj.TYPE_REG
 		prog.From.Reg = pickTarget.register
 		prog.To.Type = obj.TYPE_REG
 		prog.To.Reg = reg
-		b.addInstruction(prog)
+		c.addInstruction(prog)
 	} else if pickTarget.onStack() {
 		// Copy the value from the stack.
-		prog := b.newProg()
+		prog := c.newProg()
 		prog.As = x86.AMOVQ
 		prog.From.Type = obj.TYPE_MEM
 		prog.From.Reg = reservedRegisterForStackBasePointer
 		prog.From.Offset = int64(pickTarget.stackPointer) * 8
 		prog.To.Type = obj.TYPE_REG
 		prog.To.Reg = reg
-		b.addInstruction(prog)
+		c.addInstruction(prog)
 	} else if pickTarget.onConditionalRegister() {
 		panic("TODO")
 	}
 	// Now we already placed the picked value on the register,
 	// so push the location onto the stack.
-	loc := b.locationStack.pushValueOnRegister(reg)
+	loc := c.locationStack.pushValueOnRegister(reg)
 	loc.setRegisterType(pickTarget.registerType())
 	return nil
 }
 
-func (b *amd64Builder) handleAdd(o *wazeroir.OperationAdd) error {
+func (c *amd64Compiler) compileAdd(o *wazeroir.OperationAdd) error {
 	// TODO: if the previous instruction is const, then
 	// this can be optimized. Same goes for other arithmetic instructions.
 
@@ -931,32 +711,32 @@ func (b *amd64Builder) handleAdd(o *wazeroir.OperationAdd) error {
 		instruction = x86.AADDSD
 	}
 
-	x2 := b.locationStack.pop()
-	if err := b.ensureOnGeneralPurposeRegister(x2); err != nil {
+	x2 := c.locationStack.pop()
+	if err := c.ensureOnGeneralPurposeRegister(x2); err != nil {
 		return err
 	}
 
-	x1 := b.locationStack.peek() // Note this is peek, pop!
-	if err := b.ensureOnGeneralPurposeRegister(x1); err != nil {
+	x1 := c.locationStack.peek() // Note this is peek, pop!
+	if err := c.ensureOnGeneralPurposeRegister(x1); err != nil {
 		return err
 	}
 
 	// x1 += x2.
-	prog := b.newProg()
+	prog := c.newProg()
 	prog.From.Type = obj.TYPE_REG
 	prog.From.Reg = x2.register
 	prog.To.Type = obj.TYPE_REG
 	prog.To.Reg = x1.register
 	prog.As = instruction
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 
 	// We no longer need x2 register after ADD operation here,
 	// so we release it.
-	b.locationStack.releaseRegister(x2)
+	c.locationStack.releaseRegister(x2)
 	return nil
 }
 
-func (b *amd64Builder) handleSub(o *wazeroir.OperationSub) error {
+func (c *amd64Compiler) compileSub(o *wazeroir.OperationSub) error {
 	// TODO: if the previous instruction is const, then
 	// this can be optimized. Same goes for other arithmetic instructions.
 
@@ -972,44 +752,44 @@ func (b *amd64Builder) handleSub(o *wazeroir.OperationSub) error {
 		instruction = x86.ASUBSD
 	}
 
-	x2 := b.locationStack.pop()
-	if err := b.ensureOnGeneralPurposeRegister(x2); err != nil {
+	x2 := c.locationStack.pop()
+	if err := c.ensureOnGeneralPurposeRegister(x2); err != nil {
 		return err
 	}
 
-	x1 := b.locationStack.peek() // Note this is peek, pop!
-	if err := b.ensureOnGeneralPurposeRegister(x1); err != nil {
+	x1 := c.locationStack.peek() // Note this is peek, pop!
+	if err := c.ensureOnGeneralPurposeRegister(x1); err != nil {
 		return err
 	}
 
 	// x1 += x2.
-	prog := b.newProg()
+	prog := c.newProg()
 	prog.From.Type = obj.TYPE_REG
 	prog.From.Reg = x2.register
 	prog.To.Type = obj.TYPE_REG
 	prog.To.Reg = x1.register
 	prog.As = instruction
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 
 	// We no longer need x2 register after ADD operation here,
 	// so we release it.
-	b.locationStack.releaseRegister(x2)
+	c.locationStack.releaseRegister(x2)
 	return nil
 }
 
-func (b *amd64Builder) handleLe(o *wazeroir.OperationLe) error {
-	x2 := b.locationStack.pop()
-	if err := b.ensureOnGeneralPurposeRegister(x2); err != nil {
+func (c *amd64Compiler) compileLe(o *wazeroir.OperationLe) error {
+	x2 := c.locationStack.pop()
+	if err := c.ensureOnGeneralPurposeRegister(x2); err != nil {
 		return err
 	}
 
-	x1 := b.locationStack.pop()
-	if err := b.ensureOnGeneralPurposeRegister(x1); err != nil {
+	x1 := c.locationStack.pop()
+	if err := c.ensureOnGeneralPurposeRegister(x1); err != nil {
 		return err
 	}
 
 	// Emit the compare instruction.
-	prog := b.newProg()
+	prog := c.newProg()
 	prog.From.Type = obj.TYPE_REG
 	prog.To.Type = obj.TYPE_REG
 	var resultConditionState conditionalRegisterState
@@ -1045,25 +825,25 @@ func (b *amd64Builder) handleLe(o *wazeroir.OperationLe) error {
 		prog.From.Reg = x2.register
 		prog.To.Reg = x1.register
 	}
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 
 	// TODO: emit NaN value handings for floats.
 
 	// We no longer need x1,x2 register after cmp operation here,
 	// so we release it.
-	b.locationStack.releaseRegister(x1)
-	b.locationStack.releaseRegister(x2)
+	c.locationStack.releaseRegister(x1)
+	c.locationStack.releaseRegister(x2)
 
 	// Finally we have the result on the conditional register,
 	// so record it.
-	loc := b.locationStack.pushValueOnConditionalRegister(resultConditionState)
+	loc := c.locationStack.pushValueOnConditionalRegister(resultConditionState)
 	loc.setRegisterType(generalPurposeRegisterTypeInt)
 	return nil
 }
 
-func (b *amd64Builder) handleLoad(o *wazeroir.OperationLoad) error {
-	base := b.locationStack.pop()
-	if err := b.ensureOnGeneralPurposeRegister(base); err != nil {
+func (c *amd64Compiler) compileLoad(o *wazeroir.OperationLoad) error {
+	base := c.locationStack.pop()
+	if err := c.ensureOnGeneralPurposeRegister(base); err != nil {
 		return err
 	}
 
@@ -1072,13 +852,13 @@ func (b *amd64Builder) handleLoad(o *wazeroir.OperationLoad) error {
 	reg := base.register
 
 	// Then we have to calculate the offset on the memory region.
-	addOffsetToBase := b.newProg()
+	addOffsetToBase := c.newProg()
 	addOffsetToBase.As = x86.AADDL // 32-bit!
 	addOffsetToBase.To.Type = obj.TYPE_REG
 	addOffsetToBase.To.Reg = reg
 	addOffsetToBase.From.Type = obj.TYPE_CONST
 	addOffsetToBase.From.Offset = int64(o.Arg.Offest)
-	b.addInstruction(addOffsetToBase)
+	c.addInstruction(addOffsetToBase)
 
 	// TODO: Emit instructions here to check memory out of bounds as
 	// potentially it would be an security risk.
@@ -1105,7 +885,7 @@ func (b *amd64Builder) handleLoad(o *wazeroir.OperationLoad) error {
 	if isIntType {
 		// For integer types, read the corresponding bytes from the offset to the memory
 		// and store the value to the int register.
-		moveFromMemory := b.newProg()
+		moveFromMemory := c.newProg()
 		moveFromMemory.As = movInst
 		moveFromMemory.To.Type = obj.TYPE_REG
 		moveFromMemory.To.Reg = reg
@@ -1113,16 +893,16 @@ func (b *amd64Builder) handleLoad(o *wazeroir.OperationLoad) error {
 		moveFromMemory.From.Reg = reservedRegisterForMemory
 		moveFromMemory.From.Index = reg
 		moveFromMemory.From.Scale = 1
-		b.addInstruction(moveFromMemory)
-		top := b.locationStack.pushValueOnRegister(reg)
+		c.addInstruction(moveFromMemory)
+		top := c.locationStack.pushValueOnRegister(reg)
 		top.setRegisterType(generalPurposeRegisterTypeInt)
 	} else {
 		// For float types, we read the value to the float register.
-		floatReg, err := b.allocateRegister(generalPurposeRegisterTypeFloat)
+		floatReg, err := c.allocateRegister(generalPurposeRegisterTypeFloat)
 		if err != nil {
 			return err
 		}
-		moveFromMemory := b.newProg()
+		moveFromMemory := c.newProg()
 		moveFromMemory.As = movInst
 		moveFromMemory.To.Type = obj.TYPE_REG
 		moveFromMemory.To.Reg = floatReg
@@ -1130,18 +910,18 @@ func (b *amd64Builder) handleLoad(o *wazeroir.OperationLoad) error {
 		moveFromMemory.From.Reg = reservedRegisterForMemory
 		moveFromMemory.From.Index = reg
 		moveFromMemory.From.Scale = 1
-		b.addInstruction(moveFromMemory)
-		top := b.locationStack.pushValueOnRegister(floatReg)
+		c.addInstruction(moveFromMemory)
+		top := c.locationStack.pushValueOnRegister(floatReg)
 		top.setRegisterType(generalPurposeRegisterTypeFloat)
 		// We no longer need the int register so mark it unused.
-		b.locationStack.markRegisterUnused(reg)
+		c.locationStack.markRegisterUnused(reg)
 	}
 	return nil
 }
 
-func (b *amd64Builder) handleLoad8(o *wazeroir.OperationLoad8) error {
-	base := b.locationStack.pop()
-	if err := b.ensureOnGeneralPurposeRegister(base); err != nil {
+func (c *amd64Compiler) compileLoad8(o *wazeroir.OperationLoad8) error {
+	base := c.locationStack.pop()
+	if err := c.ensureOnGeneralPurposeRegister(base); err != nil {
 		return err
 	}
 
@@ -1150,17 +930,17 @@ func (b *amd64Builder) handleLoad8(o *wazeroir.OperationLoad8) error {
 	reg := base.register
 
 	// We have to calculate the offset on the memory region.
-	addOffsetToBase := b.newProg()
+	addOffsetToBase := c.newProg()
 	addOffsetToBase.As = x86.AADDL // 32-bit!
 	addOffsetToBase.To.Type = obj.TYPE_REG
 	addOffsetToBase.To.Reg = reg
 	addOffsetToBase.From.Type = obj.TYPE_CONST
 	addOffsetToBase.From.Offset = int64(o.Arg.Offest)
-	b.addInstruction(addOffsetToBase)
+	c.addInstruction(addOffsetToBase)
 
 	// Then move a byte at the offset to the register.
 	// Note that Load8 is only for integer types.
-	moveFromMemory := b.newProg()
+	moveFromMemory := c.newProg()
 	moveFromMemory.As = x86.AMOVB
 	moveFromMemory.To.Type = obj.TYPE_REG
 	moveFromMemory.To.Reg = reg
@@ -1168,17 +948,17 @@ func (b *amd64Builder) handleLoad8(o *wazeroir.OperationLoad8) error {
 	moveFromMemory.From.Reg = reservedRegisterForMemory
 	moveFromMemory.From.Index = reg
 	moveFromMemory.From.Scale = 1
-	b.addInstruction(moveFromMemory)
-	top := b.locationStack.pushValueOnRegister(reg)
+	c.addInstruction(moveFromMemory)
+	top := c.locationStack.pushValueOnRegister(reg)
 
 	// The result of load8 is always int type.
 	top.setRegisterType(generalPurposeRegisterTypeInt)
 	return nil
 }
 
-func (b *amd64Builder) handleLoad16(o *wazeroir.OperationLoad16) error {
-	base := b.locationStack.pop()
-	if err := b.ensureOnGeneralPurposeRegister(base); err != nil {
+func (c *amd64Compiler) compileLoad16(o *wazeroir.OperationLoad16) error {
+	base := c.locationStack.pop()
+	if err := c.ensureOnGeneralPurposeRegister(base); err != nil {
 		return err
 	}
 
@@ -1187,17 +967,17 @@ func (b *amd64Builder) handleLoad16(o *wazeroir.OperationLoad16) error {
 	reg := base.register
 
 	// We have to calculate the offset on the memory region.
-	addOffsetToBase := b.newProg()
+	addOffsetToBase := c.newProg()
 	addOffsetToBase.As = x86.AADDL // 32-bit!
 	addOffsetToBase.To.Type = obj.TYPE_REG
 	addOffsetToBase.To.Reg = reg
 	addOffsetToBase.From.Type = obj.TYPE_CONST
 	addOffsetToBase.From.Offset = int64(o.Arg.Offest)
-	b.addInstruction(addOffsetToBase)
+	c.addInstruction(addOffsetToBase)
 
 	// Then move 2 bytes at the offset to the register.
 	// Note that Load16 is only for integer types.
-	moveFromMemory := b.newProg()
+	moveFromMemory := c.newProg()
 	moveFromMemory.As = x86.AMOVW
 	moveFromMemory.To.Type = obj.TYPE_REG
 	moveFromMemory.To.Reg = reg
@@ -1205,17 +985,17 @@ func (b *amd64Builder) handleLoad16(o *wazeroir.OperationLoad16) error {
 	moveFromMemory.From.Reg = reservedRegisterForMemory
 	moveFromMemory.From.Index = reg
 	moveFromMemory.From.Scale = 1
-	b.addInstruction(moveFromMemory)
-	top := b.locationStack.pushValueOnRegister(reg)
+	c.addInstruction(moveFromMemory)
+	top := c.locationStack.pushValueOnRegister(reg)
 
 	// The result of load16 is always int type.
 	top.setRegisterType(generalPurposeRegisterTypeInt)
 	return nil
 }
 
-func (b *amd64Builder) handleLoad32(o *wazeroir.OperationLoad32) error {
-	base := b.locationStack.pop()
-	if err := b.ensureOnGeneralPurposeRegister(base); err != nil {
+func (c *amd64Compiler) compileLoad32(o *wazeroir.OperationLoad32) error {
+	base := c.locationStack.pop()
+	if err := c.ensureOnGeneralPurposeRegister(base); err != nil {
 		return err
 	}
 
@@ -1224,16 +1004,16 @@ func (b *amd64Builder) handleLoad32(o *wazeroir.OperationLoad32) error {
 	reg := base.register
 
 	// We have to calculate the offset on the memory region.
-	addOffsetToBase := b.newProg()
+	addOffsetToBase := c.newProg()
 	addOffsetToBase.As = x86.AADDL // 32-bit!
 	addOffsetToBase.To.Type = obj.TYPE_REG
 	addOffsetToBase.To.Reg = reg
 	addOffsetToBase.From.Type = obj.TYPE_CONST
 	addOffsetToBase.From.Offset = int64(o.Arg.Offest)
-	b.addInstruction(addOffsetToBase)
+	c.addInstruction(addOffsetToBase)
 
 	// Then move 4 bytes at the offset to the register.
-	moveFromMemory := b.newProg()
+	moveFromMemory := c.newProg()
 	moveFromMemory.As = x86.AMOVL
 	moveFromMemory.To.Type = obj.TYPE_REG
 	moveFromMemory.To.Reg = reg
@@ -1241,15 +1021,15 @@ func (b *amd64Builder) handleLoad32(o *wazeroir.OperationLoad32) error {
 	moveFromMemory.From.Reg = reservedRegisterForMemory
 	moveFromMemory.From.Index = reg
 	moveFromMemory.From.Scale = 1
-	b.addInstruction(moveFromMemory)
-	top := b.locationStack.pushValueOnRegister(reg)
+	c.addInstruction(moveFromMemory)
+	top := c.locationStack.pushValueOnRegister(reg)
 
 	// The result of load32 is always int type.
 	top.setRegisterType(generalPurposeRegisterTypeInt)
 	return nil
 }
 
-func (b *amd64Builder) handleStore(o *wazeroir.OperationStore) error {
+func (c *amd64Compiler) compileStore(o *wazeroir.OperationStore) error {
 	var movInst obj.As
 	switch o.Type {
 	case wazeroir.UnsignedTypeI32, wazeroir.UnsignedTypeF32:
@@ -1257,45 +1037,45 @@ func (b *amd64Builder) handleStore(o *wazeroir.OperationStore) error {
 	case wazeroir.UnsignedTypeI64, wazeroir.UnsignedTypeF64:
 		movInst = x86.AMOVQ
 	}
-	return b.moveToMemory(o.Arg.Offest, movInst)
+	return c.moveToMemory(o.Arg.Offest, movInst)
 }
 
-func (b *amd64Builder) handleStore8(o *wazeroir.OperationStore8) error {
-	return b.moveToMemory(o.Arg.Offest, x86.AMOVB)
+func (c *amd64Compiler) compileStore8(o *wazeroir.OperationStore8) error {
+	return c.moveToMemory(o.Arg.Offest, x86.AMOVB)
 }
 
-func (b *amd64Builder) handleStore16(o *wazeroir.OperationStore16) error {
-	return b.moveToMemory(o.Arg.Offest, x86.AMOVW)
+func (c *amd64Compiler) compileStore16(o *wazeroir.OperationStore16) error {
+	return c.moveToMemory(o.Arg.Offest, x86.AMOVW)
 }
 
-func (b *amd64Builder) handleStore32(o *wazeroir.OperationStore32) error {
-	return b.moveToMemory(o.Arg.Offest, x86.AMOVL)
+func (c *amd64Compiler) compileStore32(o *wazeroir.OperationStore32) error {
+	return c.moveToMemory(o.Arg.Offest, x86.AMOVL)
 }
 
-func (b *amd64Builder) moveToMemory(offsetConst uint32, moveInstruction obj.As) error {
-	val := b.locationStack.pop()
-	if err := b.ensureOnGeneralPurposeRegister(val); err != nil {
+func (c *amd64Compiler) moveToMemory(offsetConst uint32, moveInstruction obj.As) error {
+	val := c.locationStack.pop()
+	if err := c.ensureOnGeneralPurposeRegister(val); err != nil {
 		return err
 	}
 
-	base := b.locationStack.pop()
-	if err := b.ensureOnGeneralPurposeRegister(base); err != nil {
+	base := c.locationStack.pop()
+	if err := c.ensureOnGeneralPurposeRegister(base); err != nil {
 		return err
 	}
 
 	// Then we have to calculate the offset on the memory region.
-	addOffsetToBase := b.newProg()
+	addOffsetToBase := c.newProg()
 	addOffsetToBase.As = x86.AADDL // 32-bit!
 	addOffsetToBase.To.Type = obj.TYPE_REG
 	addOffsetToBase.To.Reg = base.register
 	addOffsetToBase.From.Type = obj.TYPE_CONST
 	addOffsetToBase.From.Offset = int64(offsetConst)
-	b.addInstruction(addOffsetToBase)
+	c.addInstruction(addOffsetToBase)
 
 	// TODO: Emit instructions here to check memory out of bounds as
 	// potentially it would be an security risk.
 
-	moveToMemory := b.newProg()
+	moveToMemory := c.newProg()
 	moveToMemory.As = moveInstruction
 	moveToMemory.From.Type = obj.TYPE_REG
 	moveToMemory.From.Reg = val.register
@@ -1303,171 +1083,171 @@ func (b *amd64Builder) moveToMemory(offsetConst uint32, moveInstruction obj.As)
 	moveToMemory.To.Reg = reservedRegisterForMemory
 	moveToMemory.To.Index = base.register
 	moveToMemory.To.Scale = 1
-	b.addInstruction(moveToMemory)
+	c.addInstruction(moveToMemory)
 
 	// We no longer need both the value and base registers.
-	b.locationStack.releaseRegister(val)
-	b.locationStack.releaseRegister(base)
+	c.locationStack.releaseRegister(val)
+	c.locationStack.releaseRegister(base)
 	return nil
 }
 
-func (b *amd64Builder) handleMemoryGrow() {
-	b.callBuiltinFunctionFromConstIndex(builtinFunctionIndexMemoryGrow)
+func (c *amd64Compiler) compileMemoryGrow() {
+	c.callBuiltinFunctionFromConstIndex(builtinFunctionIndexMemoryGrow)
 }
 
-func (b *amd64Builder) handleMemorySize() {
-	b.callBuiltinFunctionFromConstIndex(builtinFunctionIndexMemorySize)
-	loc := b.locationStack.pushValueOnStack() // The size is pushed on the top.
+func (c *amd64Compiler) compileMemorySize() {
+	c.callBuiltinFunctionFromConstIndex(builtinFunctionIndexMemorySize)
+	loc := c.locationStack.pushValueOnStack() // The size is pushed on the top.
 	loc.setRegisterType(generalPurposeRegisterTypeInt)
 }
 
-func (b *amd64Builder) callBuiltinFunctionFromConstIndex(index int64) {
-	b.setJITStatus(jitCallStatusCodeCallBuiltInFunction)
-	b.setFunctionCallIndexFromConst(index)
+func (c *amd64Compiler) callBuiltinFunctionFromConstIndex(index int64) {
+	c.setJITStatus(jitCallStatusCodeCallBuiltInFunction)
+	c.setFunctionCallIndexFromConst(index)
 	// Release all the registers as our calling convention requires the callee-save.
-	b.releaseAllRegistersToStack()
-	b.setContinuationOffsetAtNextInstructionAndReturn()
+	c.releaseAllRegistersToStack()
+	c.setContinuationOffsetAtNextInstructionAndReturn()
 	// Once we return from the function call,
 	// we must setup the reserved registers again.
-	b.initializeReservedRegisters()
+	c.initializeReservedRegisters()
 }
 
-func (b *amd64Builder) handleConstI32(o *wazeroir.OperationConstI32) error {
-	reg, err := b.allocateRegister(generalPurposeRegisterTypeInt)
+func (c *amd64Compiler) compileConstI32(o *wazeroir.OperationConstI32) error {
+	reg, err := c.allocateRegister(generalPurposeRegisterTypeInt)
 	if err != nil {
 		return err
 	}
-	loc := b.locationStack.pushValueOnRegister(reg)
+	loc := c.locationStack.pushValueOnRegister(reg)
 	loc.setRegisterType(generalPurposeRegisterTypeInt)
 
-	prog := b.newProg()
+	prog := c.newProg()
 	prog.As = x86.AMOVL // Note 32-bit move!
 	prog.From.Type = obj.TYPE_CONST
 	prog.From.Offset = int64(o.Value)
 	prog.To.Type = obj.TYPE_REG
 	prog.To.Reg = reg
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 	return nil
 }
 
-func (b *amd64Builder) handleConstI64(o *wazeroir.OperationConstI64) error {
-	reg, err := b.allocateRegister(generalPurposeRegisterTypeInt)
+func (c *amd64Compiler) compileConstI64(o *wazeroir.OperationConstI64) error {
+	reg, err := c.allocateRegister(generalPurposeRegisterTypeInt)
 	if err != nil {
 		return err
 	}
-	loc := b.locationStack.pushValueOnRegister(reg)
+	loc := c.locationStack.pushValueOnRegister(reg)
 	loc.setRegisterType(generalPurposeRegisterTypeInt)
 
-	prog := b.newProg()
+	prog := c.newProg()
 	prog.As = x86.AMOVQ
 	prog.From.Type = obj.TYPE_CONST
 	prog.From.Offset = int64(o.Value)
 	prog.To.Type = obj.TYPE_REG
 	prog.To.Reg = reg
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 	return nil
 }
 
-func (b *amd64Builder) handleConstF32(o *wazeroir.OperationConstF32) error {
-	reg, err := b.allocateRegister(generalPurposeRegisterTypeFloat)
+func (c *amd64Compiler) compileConstF32(o *wazeroir.OperationConstF32) error {
+	reg, err := c.allocateRegister(generalPurposeRegisterTypeFloat)
 	if err != nil {
 		return err
 	}
-	loc := b.locationStack.pushValueOnRegister(reg)
+	loc := c.locationStack.pushValueOnRegister(reg)
 	loc.setRegisterType(generalPurposeRegisterTypeFloat)
 
 	// We cannot directly load the value from memory to float regs,
 	// so we move it to int reg temporarily.
-	tmpReg, err := b.allocateRegister(generalPurposeRegisterTypeInt)
+	tmpReg, err := c.allocateRegister(generalPurposeRegisterTypeInt)
 	if err != nil {
 		return err
 	}
-	moveToTmpReg := b.newProg()
+	moveToTmpReg := c.newProg()
 	moveToTmpReg.As = x86.AMOVL // Note 32-bit mov!
 	moveToTmpReg.From.Type = obj.TYPE_CONST
 	moveToTmpReg.From.Offset = int64(uint64(math.Float32bits(o.Value)))
 	moveToTmpReg.To.Type = obj.TYPE_REG
 	moveToTmpReg.To.Reg = tmpReg
-	b.addInstruction(moveToTmpReg)
+	c.addInstruction(moveToTmpReg)
 
-	prog := b.newProg()
+	prog := c.newProg()
 	prog.As = x86.AMOVL // Note 32-bit mov!
 	prog.From.Type = obj.TYPE_REG
 	prog.From.Reg = tmpReg
 	prog.To.Type = obj.TYPE_REG
 	prog.To.Reg = reg
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 	// We don't need to explicitly release tmpReg here
 	// as allocateRegister doesn't mark it used.
 	return nil
 }
 
-func (b *amd64Builder) handleConstF64(o *wazeroir.OperationConstF64) error {
-	reg, err := b.allocateRegister(generalPurposeRegisterTypeFloat)
+func (c *amd64Compiler) compileConstF64(o *wazeroir.OperationConstF64) error {
+	reg, err := c.allocateRegister(generalPurposeRegisterTypeFloat)
 	if err != nil {
 		return err
 	}
-	loc := b.locationStack.pushValueOnRegister(reg)
+	loc := c.locationStack.pushValueOnRegister(reg)
 	loc.setRegisterType(generalPurposeRegisterTypeFloat)
 
 	// We cannot directly load the value from memory to float regs,
 	// so we move it to int reg temporarily.
-	tmpReg, err := b.allocateRegister(generalPurposeRegisterTypeInt)
+	tmpReg, err := c.allocateRegister(generalPurposeRegisterTypeInt)
 	if err != nil {
 		return err
 	}
-	moveToTmpReg := b.newProg()
+	moveToTmpReg := c.newProg()
 	moveToTmpReg.As = x86.AMOVQ
 	moveToTmpReg.From.Type = obj.TYPE_CONST
 	moveToTmpReg.From.Offset = int64(math.Float64bits(o.Value))
 	moveToTmpReg.To.Type = obj.TYPE_REG
 	moveToTmpReg.To.Reg = tmpReg
-	b.addInstruction(moveToTmpReg)
+	c.addInstruction(moveToTmpReg)
 
-	prog := b.newProg()
+	prog := c.newProg()
 	prog.As = x86.AMOVQ
 	prog.From.Type = obj.TYPE_REG
 	prog.From.Reg = tmpReg
 	prog.To.Type = obj.TYPE_REG
 	prog.To.Reg = reg
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 	// We don't need to explicitly release tmpReg here
 	// as allocateRegister doesn't mark it used.
 	return nil
 }
 
 // TODO: maybe split this function as this is doing too much at once to say at once.
-func (b *amd64Builder) moveStackToRegisterWithAllocation(tp generalPurposeRegisterType, loc *valueLocation) error {
+func (c *amd64Compiler) moveStackToRegisterWithAllocation(tp generalPurposeRegisterType, loc *valueLocation) error {
 	// Allocate the register.
-	reg, err := b.allocateRegister(tp)
+	reg, err := c.allocateRegister(tp)
 	if err != nil {
 		return err
 	}
 
 	// Mark it uses the register.
 	loc.setRegister(reg)
-	b.locationStack.markRegisterUsed(reg)
+	c.locationStack.markRegisterUsed(reg)
 
 	// Now ready to move value.
-	b.moveStackToRegister(loc)
+	c.moveStackToRegister(loc)
 	return nil
 }
 
-func (b *amd64Builder) moveStackToRegister(loc *valueLocation) {
+func (c *amd64Compiler) moveStackToRegister(loc *valueLocation) {
 	// Copy the value from the stack.
-	prog := b.newProg()
+	prog := c.newProg()
 	prog.As = x86.AMOVQ
 	prog.From.Type = obj.TYPE_MEM
 	prog.From.Reg = reservedRegisterForStackBasePointer
 	prog.From.Offset = int64(loc.stackPointer) * 8
 	prog.To.Type = obj.TYPE_REG
 	prog.To.Reg = loc.register
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 }
 
-func (b *amd64Builder) moveConditionalToGeneralPurposeRegister(loc *valueLocation) error {
+func (c *amd64Compiler) moveConditionalToGeneralPurposeRegister(loc *valueLocation) error {
 	// Get the free register.
-	reg, ok := b.locationStack.takeFreeRegister(generalPurposeRegisterTypeInt)
+	reg, ok := c.locationStack.takeFreeRegister(generalPurposeRegisterTypeInt)
 	if !ok {
 		// This in theory should never be reached as moveConditionalToGeneralPurposeRegister
 		// is called right after comparison operations, meaning that
@@ -1476,7 +1256,7 @@ func (b *amd64Builder) moveConditionalToGeneralPurposeRegister(loc *valueLocatio
 	}
 
 	// Set the flag bit to the destination.
-	prog := b.newProg()
+	prog := c.newProg()
 	prog.To.Type = obj.TYPE_REG
 	prog.To.Reg = reg
 	// See
@@ -1509,20 +1289,20 @@ func (b *amd64Builder) moveConditionalToGeneralPurposeRegister(loc *valueLocatio
 	case conditionalRegisterStateBE:
 		prog.As = x86.ASETLS
 	}
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 
 	// Then we reset the unnecessary bit.
-	prog = b.newProg()
+	prog = c.newProg()
 	prog.As = x86.AANDQ
 	prog.To.Type = obj.TYPE_REG
 	prog.To.Reg = reg
 	prog.From.Type = obj.TYPE_CONST
 	prog.From.Offset = 0x1
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 
 	// Mark it uses the register.
 	loc.setRegister(reg)
-	b.locationStack.markRegisterUsed(reg)
+	c.locationStack.markRegisterUsed(reg)
 	return nil
 }
 
@@ -1530,16 +1310,16 @@ func (b *amd64Builder) moveConditionalToGeneralPurposeRegister(loc *valueLocatio
 // either from the free register pool or by stealing an used register.
 // Note that resulting registers are NOT marked as used so the call site should
 // mark it used if necessary.
-func (b *amd64Builder) allocateRegister(t generalPurposeRegisterType) (reg int16, err error) {
+func (c *amd64Compiler) allocateRegister(t generalPurposeRegisterType) (reg int16, err error) {
 	var ok bool
 	// Try to get the unused register.
-	reg, ok = b.locationStack.takeFreeRegister(t)
+	reg, ok = c.locationStack.takeFreeRegister(t)
 	if ok {
 		return
 	}
 
 	// If not found, we have to steal the register.
-	stealTarget, ok := b.locationStack.takeStealTargetFromUsedRegister(t)
+	stealTarget, ok := c.locationStack.takeStealTargetFromUsedRegister(t)
 	if !ok {
 		err = fmt.Errorf("cannot steal register")
 		return
@@ -1547,78 +1327,78 @@ func (b *amd64Builder) allocateRegister(t generalPurposeRegisterType) (reg int16
 
 	// Release the steal target register value onto stack location.
 	reg = stealTarget.register
-	b.releaseRegisterToStack(stealTarget)
+	c.releaseRegisterToStack(stealTarget)
 	return
 }
 
-func (b *amd64Builder) setJITStatus(status jitCallStatusCode) {
-	prog := b.newProg()
+func (c *amd64Compiler) setJITStatus(status jitCallStatusCode) {
+	prog := c.newProg()
 	prog.As = x86.AMOVL
 	prog.From.Type = obj.TYPE_CONST
 	prog.From.Offset = int64(status)
 	prog.To.Type = obj.TYPE_MEM
 	prog.To.Reg = reservedRegisterForEngine
 	prog.To.Offset = engineJITCallStatusCodeOffset
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 }
 
-func (b *amd64Builder) callHostFunctionFromConstIndex(index int64) {
+func (c *amd64Compiler) callHostFunctionFromConstIndex(index int64) {
 	// Set the jit status as jitCallStatusCodeCallHostFunction
-	b.setJITStatus(jitCallStatusCodeCallHostFunction)
+	c.setJITStatus(jitCallStatusCodeCallHostFunction)
 	// Set the function index.
-	b.setFunctionCallIndexFromConst(index)
+	c.setFunctionCallIndexFromConst(index)
 	// Release all the registers as our calling convention requires the callee-save.
-	b.releaseAllRegistersToStack()
+	c.releaseAllRegistersToStack()
 	// Set the continuation offset on the next instruction.
-	b.setContinuationOffsetAtNextInstructionAndReturn()
+	c.setContinuationOffsetAtNextInstructionAndReturn()
 	// Once the function call returns, we must re-initialize the reserved registers.
-	b.initializeReservedRegisters()
+	c.initializeReservedRegisters()
 }
 
-func (b *amd64Builder) callHostFunctionFromRegisterIndex(reg int16) {
+func (c *amd64Compiler) callHostFunctionFromRegisterIndex(reg int16) {
 	// Set the jit status as jitCallStatusCodeCallHostFunction
-	b.setJITStatus(jitCallStatusCodeCallHostFunction)
+	c.setJITStatus(jitCallStatusCodeCallHostFunction)
 	// Set the function index.
-	b.setFunctionCallIndexFromRegister(reg)
+	c.setFunctionCallIndexFromRegister(reg)
 	// Release all the registers as our calling convention requires the callee-save.
-	b.releaseAllRegistersToStack()
+	c.releaseAllRegistersToStack()
 	// Set the continuation offset on the next instruction.
-	b.setContinuationOffsetAtNextInstructionAndReturn()
+	c.setContinuationOffsetAtNextInstructionAndReturn()
 	// Once the function call returns, we must re-initialize the reserved registers..
-	b.initializeReservedRegisters()
+	c.initializeReservedRegisters()
 }
 
-func (b *amd64Builder) callFunctionFromConstIndex(index int64) {
+func (c *amd64Compiler) callFunctionFromConstIndex(index int64) {
 	// Set the jit status as jitCallStatusCodeCallWasmFunction
-	b.setJITStatus(jitCallStatusCodeCallWasmFunction)
+	c.setJITStatus(jitCallStatusCodeCallWasmFunction)
 	// Set the function index.
-	b.setFunctionCallIndexFromConst(index)
+	c.setFunctionCallIndexFromConst(index)
 	// Release all the registers as our calling convention requires the callee-save.
-	b.releaseAllRegistersToStack()
+	c.releaseAllRegistersToStack()
 	// Set the continuation offset on the next instruction.
-	b.setContinuationOffsetAtNextInstructionAndReturn()
+	c.setContinuationOffsetAtNextInstructionAndReturn()
 	// Once the function call returns, we must re-initialize the reserved registers.
-	b.initializeReservedRegisters()
+	c.initializeReservedRegisters()
 }
 
-func (b *amd64Builder) callFunctionFromRegisterIndex(reg int16) {
+func (c *amd64Compiler) callFunctionFromRegisterIndex(reg int16) {
 	// Set the jit status as jitCallStatusCodeCallWasmFunction
-	b.setJITStatus(jitCallStatusCodeCallWasmFunction)
+	c.setJITStatus(jitCallStatusCodeCallWasmFunction)
 	// Set the function index.
-	b.setFunctionCallIndexFromRegister(reg)
+	c.setFunctionCallIndexFromRegister(reg)
 	// Release all the registers as our calling convention requires the callee-save.
-	b.releaseAllRegistersToStack()
+	c.releaseAllRegistersToStack()
 	// Set the continuation offset on the next instruction.
-	b.setContinuationOffsetAtNextInstructionAndReturn()
+	c.setContinuationOffsetAtNextInstructionAndReturn()
 	// Once the function call returns, we must re-initialize the reserved registers.
-	b.initializeReservedRegisters()
+	c.initializeReservedRegisters()
 }
 
-func (b *amd64Builder) releaseAllRegistersToStack() {
-	used := len(b.locationStack.usedRegisters)
-	for i := len(b.locationStack.stack) - 1; i >= 0 && used > 0; i-- {
-		if loc := b.locationStack.stack[i]; loc.onRegister() {
-			b.releaseRegisterToStack(loc)
+func (c *amd64Compiler) releaseAllRegistersToStack() {
+	used := len(c.locationStack.usedRegisters)
+	for i := len(c.locationStack.stack) - 1; i >= 0 && used > 0; i-- {
+		if loc := c.locationStack.stack[i]; loc.onRegister() {
+			c.releaseRegisterToStack(loc)
 			used--
 		}
 	}
@@ -1629,15 +1409,15 @@ func (b *amd64Builder) releaseAllRegistersToStack() {
 // Maybe better have another status for that case
 // so that we don't call back again to this function
 // and instead just release the call frame.
-func (b *amd64Builder) setContinuationOffsetAtNextInstructionAndReturn() {
+func (c *amd64Compiler) setContinuationOffsetAtNextInstructionAndReturn() {
 	// setContinuationOffsetAtNextInstructionAndReturn is called after releasing
 	// all the registers, so at this point we always have free registers.
-	tmpReg, _ := b.locationStack.takeFreeRegister(generalPurposeRegisterTypeInt)
+	tmpReg, _ := c.locationStack.takeFreeRegister(generalPurposeRegisterTypeInt)
 	// Create the instruction for setting offset.
 	// We use tmp register to store the const, not directly movq to memory
 	// as it is not valid to move 64-bit const to memory directly.
 	// TODO: is it really illegal, though?
-	prog := b.newProg()
+	prog := c.newProg()
 	prog.As = x86.AMOVQ
 	prog.From.Type = obj.TYPE_CONST
 	prog.To.Type = obj.TYPE_REG
@@ -1648,153 +1428,153 @@ func (b *amd64Builder) setContinuationOffsetAtNextInstructionAndReturn() {
 	// 64-bit mov, instead of 32-bit mov.
 	prog.From.Offset = int64(1 << 33)
 	// Append this instruction so we can later resolve the actual offset of the next instruction after return below.
-	b.requireFunctionCallReturnAddressOffsetResolution = append(b.requireFunctionCallReturnAddressOffsetResolution, prog)
-	b.addInstruction(prog)
+	c.requireFunctionCallReturnAddressOffsetResolution = append(c.requireFunctionCallReturnAddressOffsetResolution, prog)
+	c.addInstruction(prog)
 
-	prog = b.newProg()
+	prog = c.newProg()
 	prog.As = x86.AMOVQ
 	prog.From.Type = obj.TYPE_REG
 	prog.From.Reg = tmpReg
 	prog.To.Type = obj.TYPE_MEM
 	prog.To.Reg = reservedRegisterForEngine
 	prog.To.Offset = engineContinuationAddressOffset
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 	// Then return temporarily -- giving control to normal Go code.
-	b.returnFunction()
+	c.returnFunction()
 }
 
-func (b *amd64Builder) setFunctionCallIndexFromRegister(reg int16) {
-	prog := b.newProg()
+func (c *amd64Compiler) setFunctionCallIndexFromRegister(reg int16) {
+	prog := c.newProg()
 	prog.As = x86.AMOVL
 	prog.From.Type = obj.TYPE_REG
 	prog.From.Reg = reg
 	prog.To.Type = obj.TYPE_MEM
 	prog.To.Reg = reservedRegisterForEngine
 	prog.To.Offset = engineFunctionCallIndexOffset
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 }
 
-func (b *amd64Builder) setFunctionCallIndexFromConst(index int64) {
-	prog := b.newProg()
+func (c *amd64Compiler) setFunctionCallIndexFromConst(index int64) {
+	prog := c.newProg()
 	prog.As = x86.AMOVL
 	prog.From.Type = obj.TYPE_CONST
 	prog.From.Offset = index
 	prog.To.Type = obj.TYPE_MEM
 	prog.To.Reg = reservedRegisterForEngine
 	prog.To.Offset = engineFunctionCallIndexOffset
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 }
 
-func (b *amd64Builder) releaseRegisterToStack(loc *valueLocation) {
+func (c *amd64Compiler) releaseRegisterToStack(loc *valueLocation) {
 	// Push value.
-	prog := b.newProg()
+	prog := c.newProg()
 	prog.As = x86.AMOVQ
 	prog.To.Type = obj.TYPE_MEM
 	prog.To.Reg = reservedRegisterForStackBasePointer
 	prog.To.Offset = int64(loc.stackPointer) * 8
 	prog.From.Type = obj.TYPE_REG
 	prog.From.Reg = loc.register
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 
 	// Mark the register is free.
-	b.locationStack.releaseRegister(loc)
+	c.locationStack.releaseRegister(loc)
 }
 
-func (b *amd64Builder) assignRegisterToValue(loc *valueLocation, reg int16) {
+func (c *amd64Compiler) assignRegisterToValue(loc *valueLocation, reg int16) {
 	// Pop value to the resgister.
-	prog := b.newProg()
+	prog := c.newProg()
 	prog.As = x86.AMOVQ
 	prog.From.Type = obj.TYPE_MEM
 	prog.From.Reg = reservedRegisterForStackBasePointer
 	prog.From.Offset = int64(loc.stackPointer) * 8
 	prog.To.Type = obj.TYPE_REG
 	prog.To.Reg = reg
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 
 	// Now the value is on register, so mark as such.
 	loc.setRegister(reg)
-	b.locationStack.markRegisterUsed(reg)
+	c.locationStack.markRegisterUsed(reg)
 }
 
-func (b *amd64Builder) returnFunction() {
+func (c *amd64Compiler) returnFunction() {
 	// Write back the cached SP to the actual eng.stackPointer.
-	prog := b.newProg()
+	prog := c.newProg()
 	prog.As = x86.AMOVQ
 	prog.From.Type = obj.TYPE_CONST
-	prog.From.Offset = int64(b.locationStack.sp)
+	prog.From.Offset = int64(c.locationStack.sp)
 	prog.To.Type = obj.TYPE_MEM
 	prog.To.Reg = reservedRegisterForEngine
 	prog.To.Offset = enginestackPointerOffset
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 
 	// Return.
-	ret := b.newProg()
+	ret := c.newProg()
 	ret.As = obj.ARET
-	b.addInstruction(ret)
+	c.addInstruction(ret)
 }
 
 // initializeReservedRegisters must be called at the very beginning and all the
 // after-call continuations of JITed functions.
 // This caches the actual stack base pointer (engine.stackBasePointer*8+[engine.engineStackSliceOffset])
 // to cachedStackBasePointerReg
-func (b *amd64Builder) initializeReservedRegisters() {
+func (c *amd64Compiler) initializeReservedRegisters() {
 	// At first, make cachedStackBasePointerReg point to the beginning of the slice backing array.
 	// movq [engineInstanceReg+engineStackSliceOffset] cachedStackBasePointerReg
-	prog := b.newProg()
+	prog := c.newProg()
 	prog.As = x86.AMOVQ
 	prog.From.Type = obj.TYPE_MEM
 	prog.From.Reg = reservedRegisterForEngine
 	prog.From.Offset = engineStackSliceOffset
 	prog.To.Type = obj.TYPE_REG
 	prog.To.Reg = reservedRegisterForStackBasePointer
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 
 	// Since initializeReservedRegisters is called at the beginning of function
 	// calls (or right after they return), we have free registers at this point.
-	reg, _ := b.locationStack.takeFreeRegister(generalPurposeRegisterTypeInt)
+	reg, _ := c.locationStack.takeFreeRegister(generalPurposeRegisterTypeInt)
 
 	// Next we move the base pointer (engine.stackBasePointer) to
 	// a temporary register.
 	// movq [engineInstanceReg+engineCurrentstackBasePointerOffset] reg
-	prog = b.newProg()
+	prog = c.newProg()
 	prog.As = x86.AMOVQ
 	prog.From.Type = obj.TYPE_MEM
 	prog.From.Reg = reservedRegisterForEngine
 	prog.From.Offset = enginestackBasePointerOffset
 	prog.To.Type = obj.TYPE_REG
 	prog.To.Reg = reg
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 
 	// Multiply reg with 8 via shift left with 3.
 	// shlq $3 reg
-	prog = b.newProg()
+	prog = c.newProg()
 	prog.As = x86.ASHLQ
 	prog.To.Type = obj.TYPE_REG
 	prog.To.Reg = reg
 	prog.From.Type = obj.TYPE_CONST
 	prog.From.Offset = 3
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 
 	// Finally we add the reg to cachedStackBasePointerReg.
 	// addq [reg] cachedStackBasePointerReg
-	prog = b.newProg()
+	prog = c.newProg()
 	prog.As = x86.AADDQ
 	prog.To.Type = obj.TYPE_REG
 	prog.To.Reg = reservedRegisterForStackBasePointer
 	prog.From.Type = obj.TYPE_REG
 	prog.From.Reg = reg
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 }
 
 // ensureOnGeneralPurposeRegister ensures that the given value is located on a
 // general purpose register of an appropriate type.
-func (b *amd64Builder) ensureOnGeneralPurposeRegister(loc *valueLocation) error {
+func (c *amd64Compiler) ensureOnGeneralPurposeRegister(loc *valueLocation) error {
 	if loc.onStack() {
-		if err := b.moveStackToRegisterWithAllocation(loc.registerType(), loc); err != nil {
+		if err := c.moveStackToRegisterWithAllocation(loc.registerType(), loc); err != nil {
 			return err
 		}
 	} else if loc.onConditionalRegister() {
-		if err := b.moveConditionalToGeneralPurposeRegister(loc); err != nil {
+		if err := c.moveConditionalToGeneralPurposeRegister(loc); err != nil {
 			return err
 		}
 	}
diff --git a/wasm/jit/jit_other.go b/wasm/jit/jit_other.go
index a06d5b7d..dba6d07f 100644
--- a/wasm/jit/jit_other.go
+++ b/wasm/jit/jit_other.go
@@ -3,12 +3,15 @@
 
 package jit
 
-import "github.com/tetratelabs/wazero/wasm"
+import (
+	"github.com/tetratelabs/wazero/wasm"
+	"github.com/tetratelabs/wazero/wasm/wazeroir"
+)
 
 func jitcall(codeSegment, engine, memory uintptr) {
 	panic("unsupported GOARCH")
 }
 
-func (e *engine) compileWasmFunction(f *wasm.FunctionInstance) (*compiledWasmFunction, error) {
+func newCompiler(eng *engine, f *wasm.FunctionInstance, ir *wazeroir.CompilationResult) (compiler, error) {
 	panic("unsupported GOARCH")
 }


Test patch:
diff --git a/wasm/jit/jit_amd64_test.go b/wasm/jit/jit_amd64_test.go
index 0a854096..7edbd2bb 100644
--- a/wasm/jit/jit_amd64_test.go
+++ b/wasm/jit/jit_amd64_test.go
@@ -26,24 +26,24 @@ func newMemoryInst() *wasm.MemoryInstance {
 	return &wasm.MemoryInstance{Buffer: make([]byte, 1024)}
 }
 
-func requireNewBuilder(t *testing.T) *amd64Builder {
+func requireNewCompiler(t *testing.T) *amd64Compiler {
 	b, err := asm.NewBuilder("amd64", 128)
 	require.NoError(t, err)
-	return &amd64Builder{eng: nil, builder: b,
+	return &amd64Compiler{eng: nil, builder: b,
 		locationStack:            newValueLocationStack(),
 		onLabelStartCallbacks:    map[string][]func(*obj.Prog){},
 		labelInitialInstructions: map[string]*obj.Prog{},
 	}
 }
 
-func (b *amd64Builder) movIntConstToRegister(val int64, targetRegister int16) *obj.Prog {
-	prog := b.newProg()
+func (c *amd64Compiler) movIntConstToRegister(val int64, targetRegister int16) *obj.Prog {
+	prog := c.newProg()
 	prog.As = x86.AMOVQ
 	prog.From.Type = obj.TYPE_CONST
 	prog.From.Offset = val
 	prog.To.Type = obj.TYPE_REG
 	prog.To.Reg = targetRegister
-	b.addInstruction(prog)
+	c.addInstruction(prog)
 	return prog
 }
 
@@ -51,12 +51,12 @@ func TestAmd64Builder_pushFunctionInputs(t *testing.T) {
 	f := &wasm.FunctionInstance{Signature: &wasm.FunctionType{
 		ParamTypes: []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeI32},
 	}}
-	builder := &amd64Builder{locationStack: newValueLocationStack(), f: f}
-	builder.pushFunctionParams()
-	require.Equal(t, uint64(len(f.Signature.ParamTypes)), builder.locationStack.sp)
-	loc := builder.locationStack.pop()
+	compiler := &amd64Compiler{locationStack: newValueLocationStack(), f: f}
+	compiler.pushFunctionParams()
+	require.Equal(t, uint64(len(f.Signature.ParamTypes)), compiler.locationStack.sp)
+	loc := compiler.locationStack.pop()
 	require.Equal(t, uint64(1), loc.stackPointer)
-	loc = builder.locationStack.pop()
+	loc = compiler.locationStack.pop()
 	require.Equal(t, uint64(0), loc.stackPointer)
 }
 
@@ -67,51 +67,51 @@ func TestRecursiveFunctionCalls(t *testing.T) {
 	// Build a function that decrements top of stack,
 	// and recursively call itself until the top value becomes zero,
 	// and if the value becomes zero, add 5 onto the top and return.
-	builder := requireNewBuilder(t)
-	builder.initializeReservedRegisters()
+	compiler := requireNewCompiler(t)
+	compiler.initializeReservedRegisters()
 	// Setup the initial value.
 	eng.stack[0] = 10 // We call recursively 10 times.
-	loc := builder.locationStack.pushValueOnStack()
-	builder.assignRegisterToValue(loc, tmpReg)
-	require.Contains(t, builder.locationStack.usedRegisters, loc.register)
+	loc := compiler.locationStack.pushValueOnStack()
+	compiler.assignRegisterToValue(loc, tmpReg)
+	require.Contains(t, compiler.locationStack.usedRegisters, loc.register)
 	// Decrement tha value.
-	prog := builder.builder.NewProg()
+	prog := compiler.builder.NewProg()
 	prog.As = x86.ADECQ
 	prog.To.Type = obj.TYPE_REG
 	prog.To.Reg = loc.register
-	builder.addInstruction(prog)
+	compiler.addInstruction(prog)
 	// Check if the value equals zero
-	prog = builder.newProg()
+	prog = compiler.newProg()
 	prog.As = x86.ACMPQ
 	prog.From.Type = obj.TYPE_REG
 	prog.From.Reg = loc.register
 	prog.To.Type = obj.TYPE_CONST
 	prog.To.Offset = 0
-	builder.addInstruction(prog)
+	compiler.addInstruction(prog)
 	// If zero, jump to ::End
-	jmp := builder.newProg()
+	jmp := compiler.newProg()
 	jmp.As = x86.AJEQ
 	jmp.To.Type = obj.TYPE_BRANCH
-	builder.addInstruction(jmp)
+	compiler.addInstruction(jmp)
 	// If not zero, we call push back the value to the stack
 	// and call itself recursively.
-	builder.releaseRegisterToStack(loc)
-	require.NotContains(t, builder.locationStack.usedRegisters, loc.register)
-	builder.callFunctionFromConstIndex(0)
+	compiler.releaseRegisterToStack(loc)
+	require.NotContains(t, compiler.locationStack.usedRegisters, loc.register)
+	compiler.callFunctionFromConstIndex(0)
 	// ::End
 	// If zero, we return from this function after pushing 5.
-	builder.assignRegisterToValue(loc, tmpReg)
-	prog = builder.movIntConstToRegister(5, loc.register)
+	compiler.assignRegisterToValue(loc, tmpReg)
+	prog = compiler.movIntConstToRegister(5, loc.register)
 	jmp.To.SetTarget(prog) // the above mov instruction is the jump target of the JEQ.
-	builder.releaseRegisterToStack(loc)
-	builder.setJITStatus(jitCallStatusCodeReturned)
-	builder.returnFunction()
+	compiler.releaseRegisterToStack(loc)
+	compiler.setJITStatus(jitCallStatusCodeReturned)
+	compiler.returnFunction()
 	// Compile.
-	code, err := builder.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 	// Setup engine.
 	mem := newMemoryInst()
-	compiledFunc := &compiledWasmFunction{codeSegment: code, memory: mem, params: 1, results: 1}
+	compiledFunc := &compiledWasmFunction{codeSegment: code, memory: mem, paramCount: 1, resultCount: 1}
 	compiledFunc.codeInitialAddress = uintptr(unsafe.Pointer(&compiledFunc.codeSegment[0]))
 	eng.compiledWasmFunctions = []*compiledWasmFunction{compiledFunc}
 	// Call into the function
@@ -136,7 +136,7 @@ func TestRecursiveFunctionCalls(t *testing.T) {
 			mem := newMemoryInst()
 			eng := newEngine()
 			eng.stack[0] = 10 // We call recursively 10 times.
-			compiledFunc := &compiledWasmFunction{codeSegment: code, memory: mem, params: 1, results: 1}
+			compiledFunc := &compiledWasmFunction{codeSegment: code, memory: mem, paramCount: 1, resultCount: 1}
 			compiledFunc.codeInitialAddress = uintptr(unsafe.Pointer(&compiledFunc.codeSegment[0]))
 			eng.compiledWasmFunctions = []*compiledWasmFunction{compiledFunc}
 			// Call into the function
@@ -160,19 +160,19 @@ func TestPushValueWithGoroutines(t *testing.T) {
 		go func() {
 			defer wg.Done()
 			// Build native codes.
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
+			compiler := requireNewCompiler(t)
+			compiler.initializeReservedRegisters()
 			// Push consts to pushTargetRegister.
-			_ = builder.locationStack.pushValueOnStack() // dummy value, not actually used!
-			loc := builder.locationStack.pushValueOnRegister(pushTargetRegister)
-			builder.movIntConstToRegister(int64(targetValue), pushTargetRegister)
+			_ = compiler.locationStack.pushValueOnStack() // dummy value, not actually used!
+			loc := compiler.locationStack.pushValueOnRegister(pushTargetRegister)
+			compiler.movIntConstToRegister(int64(targetValue), pushTargetRegister)
 			// Push pushTargetRegister into the engine.stack[engine.sp].
-			builder.releaseRegisterToStack(loc)
+			compiler.releaseRegisterToStack(loc)
 			// Finally increment the stack pointer and write it back to the eng.sp
-			builder.returnFunction()
+			compiler.returnFunction()
 
 			// Compile.
-			code, err := builder.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			eng := newEngine()
@@ -209,12 +209,12 @@ func Test_setJITStatus(t *testing.T) {
 		t.Run(s.String(), func(t *testing.T) {
 
 			// Build codes.
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
-			builder.setJITStatus(s)
-			builder.returnFunction()
+			compiler := requireNewCompiler(t)
+			compiler.initializeReservedRegisters()
+			compiler.setJITStatus(s)
+			compiler.returnFunction()
 			// Compile.
-			code, err := builder.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			// Run codes
 			eng := newEngine()
@@ -234,12 +234,12 @@ func Test_setFunctionCallIndexFromConst(t *testing.T) {
 	// Build codes.
 	for _, index := range []int64{1, 5, 20} {
 		// Build codes.
-		builder := requireNewBuilder(t)
-		builder.initializeReservedRegisters()
-		builder.setFunctionCallIndexFromConst(index)
-		builder.returnFunction()
+		compiler := requireNewCompiler(t)
+		compiler.initializeReservedRegisters()
+		compiler.setFunctionCallIndexFromConst(index)
+		compiler.returnFunction()
 		// Compile.
-		code, err := builder.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 		// Run codes
 		eng := newEngine()
@@ -258,13 +258,13 @@ func Test_setFunctionCallIndexFromRegister(t *testing.T) {
 	reg := int16(x86.REG_R10)
 	for _, index := range []int64{1, 5, 20} {
 		// Build codes.
-		builder := requireNewBuilder(t)
-		builder.initializeReservedRegisters()
-		builder.movIntConstToRegister(index, reg)
-		builder.setFunctionCallIndexFromRegister(reg)
-		builder.returnFunction()
+		compiler := requireNewCompiler(t)
+		compiler.initializeReservedRegisters()
+		compiler.movIntConstToRegister(index, reg)
+		compiler.setFunctionCallIndexFromRegister(reg)
+		compiler.returnFunction()
 		// Compile.
-		code, err := builder.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 		// Run codes
 		eng := newEngine()
@@ -282,22 +282,22 @@ func Test_setFunctionCallIndexFromRegister(t *testing.T) {
 func Test_setContinuationAtNextInstruction(t *testing.T) {
 	const tmpReg = x86.REG_AX
 	// Build codes.
-	builder := requireNewBuilder(t)
-	builder.initializeReservedRegisters()
-	builder.setContinuationOffsetAtNextInstructionAndReturn()
-	exp := uintptr(len(builder.builder.Assemble()))
+	compiler := requireNewCompiler(t)
+	compiler.initializeReservedRegisters()
+	compiler.setContinuationOffsetAtNextInstructionAndReturn()
+	exp := uintptr(len(compiler.builder.Assemble()))
 	// On the continuation, we have to setup the registers again.
-	builder.initializeReservedRegisters()
+	compiler.initializeReservedRegisters()
 	// The continuation after function calls.
-	_ = builder.locationStack.pushValueOnStack() // dummy value, not actually used!
-	loc := builder.locationStack.pushValueOnRegister(tmpReg)
-	builder.movIntConstToRegister(int64(50), tmpReg)
-	builder.releaseRegisterToStack(loc)
-	require.NotContains(t, builder.locationStack.usedRegisters, tmpReg)
-	builder.setJITStatus(jitCallStatusCodeCallWasmFunction)
-	builder.returnFunction()
+	_ = compiler.locationStack.pushValueOnStack() // dummy value, not actually used!
+	loc := compiler.locationStack.pushValueOnRegister(tmpReg)
+	compiler.movIntConstToRegister(int64(50), tmpReg)
+	compiler.releaseRegisterToStack(loc)
+	require.NotContains(t, compiler.locationStack.usedRegisters, tmpReg)
+	compiler.setJITStatus(jitCallStatusCodeCallWasmFunction)
+	compiler.returnFunction()
 	// Compile.
-	code, err := builder.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 
 	// Run codes
@@ -328,19 +328,19 @@ func Test_callFunction(t *testing.T) {
 	)
 	t.Run("from const", func(t *testing.T) {
 		// Build codes.
-		builder := requireNewBuilder(t)
-		builder.initializeReservedRegisters()
-		builder.callFunctionFromConstIndex(functionIndex)
+		compiler := requireNewCompiler(t)
+		compiler.initializeReservedRegisters()
+		compiler.callFunctionFromConstIndex(functionIndex)
 		// On the continuation after function call,
 		// We push the value onto stack
-		_ = builder.locationStack.pushValueOnStack() // dummy value, not actually used!
-		loc := builder.locationStack.pushValueOnRegister(tmpReg)
-		builder.movIntConstToRegister(int64(50), tmpReg)
-		builder.releaseRegisterToStack(loc)
-		require.NotContains(t, builder.locationStack.usedRegisters, tmpReg)
-		builder.returnFunction()
+		_ = compiler.locationStack.pushValueOnStack() // dummy value, not actually used!
+		loc := compiler.locationStack.pushValueOnRegister(tmpReg)
+		compiler.movIntConstToRegister(int64(50), tmpReg)
+		compiler.releaseRegisterToStack(loc)
+		require.NotContains(t, compiler.locationStack.usedRegisters, tmpReg)
+		compiler.returnFunction()
 		// Compile.
-		code, err := builder.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 
 		// Setup.
@@ -367,20 +367,20 @@ func Test_callFunction(t *testing.T) {
 	t.Run("from reg", func(t *testing.T) {
 		const functionIndex int64 = 10
 		// Build codes.
-		builder := requireNewBuilder(t)
-		builder.initializeReservedRegisters()
-		builder.movIntConstToRegister(functionIndex, tmpReg)
-		builder.callFunctionFromRegisterIndex(tmpReg)
+		compiler := requireNewCompiler(t)
+		compiler.initializeReservedRegisters()
+		compiler.movIntConstToRegister(functionIndex, tmpReg)
+		compiler.callFunctionFromRegisterIndex(tmpReg)
 		// On the continuation after function call,
 		// We push the value onto stack
-		_ = builder.locationStack.pushValueOnStack() // dummy value, not actually used!
-		loc := builder.locationStack.pushValueOnRegister(tmpReg)
-		builder.movIntConstToRegister(int64(50), tmpReg)
-		builder.releaseRegisterToStack(loc)
-		require.NotContains(t, builder.locationStack.usedRegisters, tmpReg)
-		builder.returnFunction()
+		_ = compiler.locationStack.pushValueOnStack() // dummy value, not actually used!
+		loc := compiler.locationStack.pushValueOnRegister(tmpReg)
+		compiler.movIntConstToRegister(int64(50), tmpReg)
+		compiler.releaseRegisterToStack(loc)
+		require.NotContains(t, compiler.locationStack.usedRegisters, tmpReg)
+		compiler.returnFunction()
 		// Compile.
-		code, err := builder.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 
 		// Setup.
@@ -413,21 +413,21 @@ func TestEngine_exec_callHostFunction(t *testing.T) {
 			tmpReg              = x86.REG_AX
 		)
 		// Build codes.
-		builder := requireNewBuilder(t)
-		builder.initializeReservedRegisters()
+		compiler := requireNewCompiler(t)
+		compiler.initializeReservedRegisters()
 		// Push the value onto stack.
-		_ = builder.locationStack.pushValueOnStack() // dummy value, not actually used!
-		loc := builder.locationStack.pushValueOnRegister(tmpReg)
-		builder.movIntConstToRegister(int64(50), tmpReg)
-		builder.releaseRegisterToStack(loc)
-		require.NotContains(t, builder.locationStack.usedRegisters, tmpReg)
-		builder.callHostFunctionFromConstIndex(functionIndex)
+		_ = compiler.locationStack.pushValueOnStack() // dummy value, not actually used!
+		loc := compiler.locationStack.pushValueOnRegister(tmpReg)
+		compiler.movIntConstToRegister(int64(50), tmpReg)
+		compiler.releaseRegisterToStack(loc)
+		require.NotContains(t, compiler.locationStack.usedRegisters, tmpReg)
+		compiler.callHostFunctionFromConstIndex(functionIndex)
 		// On the continuation after function call,
 		// We push the value onto stack
-		builder.setJITStatus(jitCallStatusCodeReturned)
-		builder.returnFunction()
+		compiler.setJITStatus(jitCallStatusCodeReturned)
+		compiler.returnFunction()
 		// Compile.
-		code, err := builder.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 
 		// Setup.
@@ -453,23 +453,23 @@ func TestEngine_exec_callHostFunction(t *testing.T) {
 			tmpReg               = x86.REG_AX
 		)
 		// Build codes.
-		builder := requireNewBuilder(t)
-		builder.initializeReservedRegisters()
+		compiler := requireNewCompiler(t)
+		compiler.initializeReservedRegisters()
 		// Push the value onto stack.
-		_ = builder.locationStack.pushValueOnStack() // dummy value, not actually used!
-		loc := builder.locationStack.pushValueOnRegister(x86.REG_AX)
-		builder.movIntConstToRegister(int64(50), tmpReg)
-		builder.releaseRegisterToStack(loc)
-		require.NotContains(t, builder.locationStack.usedRegisters, tmpReg)
+		_ = compiler.locationStack.pushValueOnStack() // dummy value, not actually used!
+		loc := compiler.locationStack.pushValueOnRegister(x86.REG_AX)
+		compiler.movIntConstToRegister(int64(50), tmpReg)
+		compiler.releaseRegisterToStack(loc)
+		require.NotContains(t, compiler.locationStack.usedRegisters, tmpReg)
 		// Set the function index
-		builder.movIntConstToRegister(int64(1), tmpReg)
-		builder.callHostFunctionFromRegisterIndex(tmpReg)
+		compiler.movIntConstToRegister(int64(1), tmpReg)
+		compiler.callHostFunctionFromRegisterIndex(tmpReg)
 		// On the continuation after function call,
 		// We push the value onto stack
-		builder.setJITStatus(jitCallStatusCodeReturned)
-		builder.returnFunction()
+		compiler.setJITStatus(jitCallStatusCodeReturned)
+		compiler.returnFunction()
 		// Compile.
-		code, err := builder.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 
 		// Setup.
@@ -506,32 +506,32 @@ func Test_popFromStackToRegister(t *testing.T) {
 	// Build function.
 	// Pop the value from the top twice, add two values,
 	// and push it back to the top.
-	builder := requireNewBuilder(t)
-	builder.initializeReservedRegisters()
+	compiler := requireNewCompiler(t)
+	compiler.initializeReservedRegisters()
 	// Create three values on the stack.
-	builder.locationStack.pushValueOnStack()
-	loc1 := builder.locationStack.pushValueOnRegister(targetRegister1)
-	loc2 := builder.locationStack.pushValueOnRegister(targetRegister2)
-	builder.assignRegisterToValue(loc1, targetRegister1)
-	builder.assignRegisterToValue(loc2, targetRegister2)
+	compiler.locationStack.pushValueOnStack()
+	loc1 := compiler.locationStack.pushValueOnRegister(targetRegister1)
+	loc2 := compiler.locationStack.pushValueOnRegister(targetRegister2)
+	compiler.assignRegisterToValue(loc1, targetRegister1)
+	compiler.assignRegisterToValue(loc2, targetRegister2)
 	// Increment the popped value on the register.
-	prog := builder.newProg()
+	prog := compiler.newProg()
 	prog.As = x86.AADDQ
 	prog.To.Type = obj.TYPE_REG
 	prog.To.Reg = targetRegister1
 	prog.From.Type = obj.TYPE_REG
 	prog.From.Reg = targetRegister2
 	// Now we used the two values so pop twice.
-	builder.locationStack.pop()
-	builder.locationStack.pop()
+	compiler.locationStack.pop()
+	compiler.locationStack.pop()
 	// Ready to push the result location.
-	result := builder.locationStack.pushValueOnRegister(targetRegister1)
-	builder.addInstruction(prog)
+	result := compiler.locationStack.pushValueOnRegister(targetRegister1)
+	compiler.addInstruction(prog)
 	// Push it back to the stack.
-	builder.releaseRegisterToStack(result)
-	builder.returnFunction()
+	compiler.releaseRegisterToStack(result)
+	compiler.returnFunction()
 	// Compile.
-	code, err := builder.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 
 	// Call in.
@@ -552,12 +552,12 @@ func Test_popFromStackToRegister(t *testing.T) {
 }
 
 func TestAmd64Builder_initializeReservedRegisters(t *testing.T) {
-	builder := requireNewBuilder(t)
-	builder.initializeReservedRegisters()
-	builder.returnFunction()
+	compiler := requireNewCompiler(t)
+	compiler.initializeReservedRegisters()
+	compiler.returnFunction()
 
 	// Assemble.
-	code, err := builder.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 
 	// Run code.
@@ -572,39 +572,39 @@ func TestAmd64Builder_initializeReservedRegisters(t *testing.T) {
 
 func TestAmd64Builder_allocateRegister(t *testing.T) {
 	t.Run("free", func(t *testing.T) {
-		builder := requireNewBuilder(t)
-		reg, err := builder.allocateRegister(generalPurposeRegisterTypeInt)
+		compiler := requireNewCompiler(t)
+		reg, err := compiler.allocateRegister(generalPurposeRegisterTypeInt)
 		require.NoError(t, err)
 		require.True(t, isIntRegister(reg))
-		reg, err = builder.allocateRegister(generalPurposeRegisterTypeFloat)
+		reg, err = compiler.allocateRegister(generalPurposeRegisterTypeFloat)
 		require.NoError(t, err)
 		require.True(t, isFloatRegister(reg))
 	})
 	t.Run("steal", func(t *testing.T) {
 		const stealTarget = x86.REG_AX
-		builder := requireNewBuilder(t)
-		builder.initializeReservedRegisters()
+		compiler := requireNewCompiler(t)
+		compiler.initializeReservedRegisters()
 		// Use up all the Int regs.
 		for _, r := range unreservedGeneralPurposeIntRegisters {
-			builder.locationStack.markRegisterUsed(r)
+			compiler.locationStack.markRegisterUsed(r)
 		}
-		stealTargetLocation := builder.locationStack.pushValueOnRegister(stealTarget)
-		builder.movIntConstToRegister(int64(50), stealTargetLocation.register)
+		stealTargetLocation := compiler.locationStack.pushValueOnRegister(stealTarget)
+		compiler.movIntConstToRegister(int64(50), stealTargetLocation.register)
 		require.Equal(t, int16(stealTarget), stealTargetLocation.register)
 		require.True(t, stealTargetLocation.onRegister())
-		reg, err := builder.allocateRegister(generalPurposeRegisterTypeInt)
+		reg, err := compiler.allocateRegister(generalPurposeRegisterTypeInt)
 		require.NoError(t, err)
 		require.True(t, isIntRegister(reg))
 		require.False(t, stealTargetLocation.onRegister())
 
 		// Create new value using the stolen register.
-		loc := builder.locationStack.pushValueOnRegister(reg)
-		builder.movIntConstToRegister(int64(2000), loc.register)
-		builder.releaseRegisterToStack(loc)
-		builder.returnFunction()
+		loc := compiler.locationStack.pushValueOnRegister(reg)
+		compiler.movIntConstToRegister(int64(2000), loc.register)
+		compiler.releaseRegisterToStack(loc)
+		compiler.returnFunction()
 
 		// Assemble.
-		code, err := builder.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 
 		// Run code.
@@ -624,23 +624,23 @@ func TestAmd64Builder_allocateRegister(t *testing.T) {
 }
 
 func TestAmd64Builder_handleLabel(t *testing.T) {
-	builder := requireNewBuilder(t)
-	builder.initializeReservedRegisters()
+	compiler := requireNewCompiler(t)
+	compiler.initializeReservedRegisters()
 	label := &wazeroir.Label{FrameID: 100, Kind: wazeroir.LabelKindContinuation}
 
 	var called bool
-	builder.onLabelStartCallbacks[label.String()] = append(builder.onLabelStartCallbacks[label.String()],
+	compiler.onLabelStartCallbacks[label.String()] = append(compiler.onLabelStartCallbacks[label.String()],
 		func(p *obj.Prog) { called = true },
 	)
-	err := builder.handleLabel(&wazeroir.OperationLabel{Label: label})
+	err := compiler.compileLabel(&wazeroir.OperationLabel{Label: label})
 	require.NoError(t, err)
-	require.Len(t, builder.onLabelStartCallbacks, 0)
-	require.Contains(t, builder.labelInitialInstructions, label.String())
+	require.Len(t, compiler.onLabelStartCallbacks, 0)
+	require.Contains(t, compiler.labelInitialInstructions, label.String())
 	require.True(t, called)
 
 	// Assemble.
-	builder.returnFunction()
-	code, err := builder.compile()
+	compiler.returnFunction()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 
 	// Run code.
@@ -655,36 +655,36 @@ func TestAmd64Builder_handleLabel(t *testing.T) {
 
 func TestAmd64Builder_handlePick(t *testing.T) {
 	o := &wazeroir.OperationPick{Depth: 1}
-	builder := requireNewBuilder(t)
-	builder.initializeReservedRegisters()
+	compiler := requireNewCompiler(t)
+	compiler.initializeReservedRegisters()
 	// The case when the original value is already in register.
 	t.Run("on reg", func(t *testing.T) {
 		// Set up the pick target original value.
-		pickTargetLocation := builder.locationStack.pushValueOnRegister(int16(x86.REG_R10))
+		pickTargetLocation := compiler.locationStack.pushValueOnRegister(int16(x86.REG_R10))
 		pickTargetLocation.setRegisterType(generalPurposeRegisterTypeInt)
-		builder.locationStack.pushValueOnStack() // Dummy value!
-		builder.movIntConstToRegister(100, pickTargetLocation.register)
+		compiler.locationStack.pushValueOnStack() // Dummy value!
+		compiler.movIntConstToRegister(100, pickTargetLocation.register)
 		// Now insert pick code.
-		err := builder.handlePick(o)
+		err := compiler.compilePick(o)
 		require.NoError(t, err)
 		// Increment the picked value.
-		pickedLocation := builder.locationStack.peek()
+		pickedLocation := compiler.locationStack.peek()
 		require.True(t, pickedLocation.onRegister())
 		require.NotEqual(t, pickedLocation.register, pickTargetLocation.register)
-		prog := builder.newProg()
+		prog := compiler.newProg()
 		prog.As = x86.AINCQ
 		prog.To.Type = obj.TYPE_REG
 		prog.To.Reg = pickedLocation.register
-		builder.addInstruction(prog)
+		compiler.addInstruction(prog)
 		// To verify the behavior, we push the incremented picked value
 		// to the stack.
-		builder.releaseRegisterToStack(pickedLocation)
+		compiler.releaseRegisterToStack(pickedLocation)
 		// Also write the original location back to the stack.
-		builder.releaseRegisterToStack(pickTargetLocation)
-		builder.returnFunction()
+		compiler.releaseRegisterToStack(pickTargetLocation)
+		compiler.returnFunction()
 
 		// Assemble.
-		code, err := builder.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 		// Run code.
 		eng := newEngine()
@@ -704,32 +704,32 @@ func TestAmd64Builder_handlePick(t *testing.T) {
 		eng := newEngine()
 
 		// Setup the original value.
-		builder.locationStack.pushValueOnStack() // Dummy value!
-		pickTargetLocation := builder.locationStack.pushValueOnStack()
-		builder.locationStack.pushValueOnStack() // Dummy value!
+		compiler.locationStack.pushValueOnStack() // Dummy value!
+		pickTargetLocation := compiler.locationStack.pushValueOnStack()
+		compiler.locationStack.pushValueOnStack() // Dummy value!
 		eng.stackPointer = 5
 		eng.stackBasePointer = 1
 		eng.stack[eng.stackBasePointer+pickTargetLocation.stackPointer] = 100
 
 		// Now insert pick code.
-		err := builder.handlePick(o)
+		err := compiler.compilePick(o)
 		require.NoError(t, err)
 
 		// Increment the picked value.
-		pickedLocation := builder.locationStack.peek()
-		prog := builder.newProg()
+		pickedLocation := compiler.locationStack.peek()
+		prog := compiler.newProg()
 		prog.As = x86.AINCQ
 		prog.To.Type = obj.TYPE_REG
 		prog.To.Reg = pickedLocation.register
-		builder.addInstruction(prog)
+		compiler.addInstruction(prog)
 
 		// To verify the behavior, we push the incremented picked value
 		// to the stack.
-		builder.releaseRegisterToStack(pickedLocation)
-		builder.returnFunction()
+		compiler.releaseRegisterToStack(pickedLocation)
+		compiler.returnFunction()
 
 		// Assemble.
-		code, err := builder.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 		// Run code.
 		mem := newMemoryInst()
@@ -748,28 +748,28 @@ func TestAmd64Builder_handlePick(t *testing.T) {
 func TestAmd64Builder_handleConstI32(t *testing.T) {
 	for _, v := range []uint32{1, 1 << 5, 1 << 31} {
 		t.Run(fmt.Sprintf("%d", v), func(t *testing.T) {
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
+			compiler := requireNewCompiler(t)
+			compiler.initializeReservedRegisters()
 
 			// Now emit the const instruction.
 			o := &wazeroir.OperationConstI32{Value: v}
-			err := builder.handleConstI32(o)
+			err := compiler.compileConstI32(o)
 			require.NoError(t, err)
 
 			// To verify the behavior, we increment and push the const value
 			// to the stack.
-			loc := builder.locationStack.peek()
+			loc := compiler.locationStack.peek()
 			require.Equal(t, generalPurposeRegisterTypeInt, loc.registerType())
-			prog := builder.newProg()
+			prog := compiler.newProg()
 			prog.As = x86.AINCQ
 			prog.To.Type = obj.TYPE_REG
 			prog.To.Reg = loc.register
-			builder.addInstruction(prog)
-			builder.releaseRegisterToStack(loc)
-			builder.returnFunction()
+			compiler.addInstruction(prog)
+			compiler.releaseRegisterToStack(loc)
+			compiler.returnFunction()
 
 			// Assemble.
-			code, err := builder.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			// Run code.
 			eng := newEngine()
@@ -790,28 +790,28 @@ func TestAmd64Builder_handleConstI32(t *testing.T) {
 func TestAmd64Builder_handleConstI64(t *testing.T) {
 	for _, v := range []uint64{1, 1 << 5, 1 << 35, 1 << 63} {
 		t.Run(fmt.Sprintf("%d", v), func(t *testing.T) {
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
+			compiler := requireNewCompiler(t)
+			compiler.initializeReservedRegisters()
 
 			// Now emit the const instruction.
 			o := &wazeroir.OperationConstI64{Value: v}
-			err := builder.handleConstI64(o)
+			err := compiler.compileConstI64(o)
 			require.NoError(t, err)
 
 			// To verify the behavior, we increment and push the const value
 			// to the stack.
-			loc := builder.locationStack.peek()
+			loc := compiler.locationStack.peek()
 			require.Equal(t, generalPurposeRegisterTypeInt, loc.registerType())
-			prog := builder.newProg()
+			prog := compiler.newProg()
 			prog.As = x86.AINCQ
 			prog.To.Type = obj.TYPE_REG
 			prog.To.Reg = loc.register
-			builder.addInstruction(prog)
-			builder.releaseRegisterToStack(loc)
-			builder.returnFunction()
+			compiler.addInstruction(prog)
+			compiler.releaseRegisterToStack(loc)
+			compiler.returnFunction()
 
 			// Assemble.
-			code, err := builder.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			// Run code.
 			eng := newEngine()
@@ -832,30 +832,30 @@ func TestAmd64Builder_handleConstI64(t *testing.T) {
 func TestAmd64Builder_handleConstF32(t *testing.T) {
 	for _, v := range []float32{1, -3.23, 100.123} {
 		t.Run(fmt.Sprintf("%f", v), func(t *testing.T) {
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
+			compiler := requireNewCompiler(t)
+			compiler.initializeReservedRegisters()
 
 			// Now emit the const instruction.
 			o := &wazeroir.OperationConstF32{Value: v}
-			err := builder.handleConstF32(o)
+			err := compiler.compileConstF32(o)
 			require.NoError(t, err)
 
 			// To verify the behavior, we double and push the const value
 			// to the stack.
-			loc := builder.locationStack.peek()
+			loc := compiler.locationStack.peek()
 			require.Equal(t, generalPurposeRegisterTypeFloat, loc.registerType())
-			prog := builder.newProg()
+			prog := compiler.newProg()
 			prog.As = x86.AADDSS
 			prog.To.Type = obj.TYPE_REG
 			prog.To.Reg = loc.register
 			prog.From.Type = obj.TYPE_REG
 			prog.From.Reg = loc.register
-			builder.addInstruction(prog)
-			builder.releaseRegisterToStack(loc)
-			builder.returnFunction()
+			compiler.addInstruction(prog)
+			compiler.releaseRegisterToStack(loc)
+			compiler.returnFunction()
 
 			// Assemble.
-			code, err := builder.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			// Run code.
 			eng := newEngine()
@@ -876,30 +876,30 @@ func TestAmd64Builder_handleConstF32(t *testing.T) {
 func TestAmd64Builder_handleConstF64(t *testing.T) {
 	for _, v := range []float64{1, -3.23, 100.123} {
 		t.Run(fmt.Sprintf("%f", v), func(t *testing.T) {
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
+			compiler := requireNewCompiler(t)
+			compiler.initializeReservedRegisters()
 
 			// Now emit the const instruction.
 			o := &wazeroir.OperationConstF64{Value: v}
-			err := builder.handleConstF64(o)
+			err := compiler.compileConstF64(o)
 			require.NoError(t, err)
 
 			// To verify the behavior, we double and push the const value
 			// to the stack.
-			loc := builder.locationStack.peek()
+			loc := compiler.locationStack.peek()
 			require.Equal(t, generalPurposeRegisterTypeFloat, loc.registerType())
-			prog := builder.newProg()
+			prog := compiler.newProg()
 			prog.As = x86.AADDSD
 			prog.To.Type = obj.TYPE_REG
 			prog.To.Reg = loc.register
 			prog.From.Type = obj.TYPE_REG
 			prog.From.Reg = loc.register
-			builder.addInstruction(prog)
-			builder.releaseRegisterToStack(loc)
-			builder.returnFunction()
+			compiler.addInstruction(prog)
+			compiler.releaseRegisterToStack(loc)
+			compiler.returnFunction()
 
 			// Assemble.
-			code, err := builder.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			// Run code.
 			eng := newEngine()
@@ -921,27 +921,27 @@ func TestAmd64Builder_handleAdd(t *testing.T) {
 	t.Run("int32", func(t *testing.T) {
 		const x1Value uint32 = 113
 		const x2Value uint32 = 41
-		builder := requireNewBuilder(t)
-		builder.initializeReservedRegisters()
-		err := builder.handleConstI32(&wazeroir.OperationConstI32{Value: x1Value})
+		compiler := requireNewCompiler(t)
+		compiler.initializeReservedRegisters()
+		err := compiler.compileConstI32(&wazeroir.OperationConstI32{Value: x1Value})
 		require.NoError(t, err)
-		x1 := builder.locationStack.peek()
-		err = builder.handleConstI32(&wazeroir.OperationConstI32{Value: x2Value})
+		x1 := compiler.locationStack.peek()
+		err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: x2Value})
 		require.NoError(t, err)
-		x2 := builder.locationStack.peek()
+		x2 := compiler.locationStack.peek()
 
-		err = builder.handleAdd(&wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeI32})
+		err = compiler.compileAdd(&wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeI32})
 		require.NoError(t, err)
-		require.Contains(t, builder.locationStack.usedRegisters, x1.register)
-		require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
+		require.Contains(t, compiler.locationStack.usedRegisters, x1.register)
+		require.NotContains(t, compiler.locationStack.usedRegisters, x2.register)
 
 		// To verify the behavior, we push the value
 		// to the stack.
-		builder.releaseRegisterToStack(x1)
-		builder.returnFunction()
+		compiler.releaseRegisterToStack(x1)
+		compiler.returnFunction()
 
 		// Assemble.
-		code, err := builder.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 		// Run code.
 		eng := newEngine()
@@ -958,27 +958,27 @@ func TestAmd64Builder_handleAdd(t *testing.T) {
 	t.Run("int64", func(t *testing.T) {
 		const x1Value uint64 = 1 << 35
 		const x2Value uint64 = 41
-		builder := requireNewBuilder(t)
-		builder.initializeReservedRegisters()
-		err := builder.handleConstI64(&wazeroir.OperationConstI64{Value: x1Value})
+		compiler := requireNewCompiler(t)
+		compiler.initializeReservedRegisters()
+		err := compiler.compileConstI64(&wazeroir.OperationConstI64{Value: x1Value})
 		require.NoError(t, err)
-		x1 := builder.locationStack.peek()
-		err = builder.handleConstI64(&wazeroir.OperationConstI64{Value: x2Value})
+		x1 := compiler.locationStack.peek()
+		err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: x2Value})
 		require.NoError(t, err)
-		x2 := builder.locationStack.peek()
+		x2 := compiler.locationStack.peek()
 
-		err = builder.handleAdd(&wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeI64})
+		err = compiler.compileAdd(&wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeI64})
 		require.NoError(t, err)
-		require.Contains(t, builder.locationStack.usedRegisters, x1.register)
-		require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
+		require.Contains(t, compiler.locationStack.usedRegisters, x1.register)
+		require.NotContains(t, compiler.locationStack.usedRegisters, x2.register)
 
 		// To verify the behavior, we push the value
 		// to the stack.
-		builder.releaseRegisterToStack(x1)
-		builder.returnFunction()
+		compiler.releaseRegisterToStack(x1)
+		compiler.returnFunction()
 
 		// Assemble.
-		code, err := builder.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 		// Run code.
 		eng := newEngine()
@@ -1005,27 +1005,27 @@ func TestAmd64Builder_handleAdd(t *testing.T) {
 		} {
 			tc := tc
 			t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
-				builder := requireNewBuilder(t)
-				builder.initializeReservedRegisters()
-				err := builder.handleConstF32(&wazeroir.OperationConstF32{Value: tc.v1})
+				compiler := requireNewCompiler(t)
+				compiler.initializeReservedRegisters()
+				err := compiler.compileConstF32(&wazeroir.OperationConstF32{Value: tc.v1})
 				require.NoError(t, err)
-				x1 := builder.locationStack.peek()
-				err = builder.handleConstF32(&wazeroir.OperationConstF32{Value: tc.v2})
+				x1 := compiler.locationStack.peek()
+				err = compiler.compileConstF32(&wazeroir.OperationConstF32{Value: tc.v2})
 				require.NoError(t, err)
-				x2 := builder.locationStack.peek()
+				x2 := compiler.locationStack.peek()
 
-				err = builder.handleAdd(&wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeF32})
+				err = compiler.compileAdd(&wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeF32})
 				require.NoError(t, err)
-				require.Contains(t, builder.locationStack.usedRegisters, x1.register)
-				require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
+				require.Contains(t, compiler.locationStack.usedRegisters, x1.register)
+				require.NotContains(t, compiler.locationStack.usedRegisters, x2.register)
 
 				// To verify the behavior, we push the value
 				// to the stack.
-				builder.releaseRegisterToStack(x1)
-				builder.returnFunction()
+				compiler.releaseRegisterToStack(x1)
+				compiler.returnFunction()
 
 				// Assemble.
-				code, err := builder.compile()
+				code, _, err := compiler.compile()
 				require.NoError(t, err)
 				// Run code.
 				eng := newEngine()
@@ -1054,27 +1054,27 @@ func TestAmd64Builder_handleAdd(t *testing.T) {
 		} {
 			tc := tc
 			t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
-				builder := requireNewBuilder(t)
-				builder.initializeReservedRegisters()
-				err := builder.handleConstF64(&wazeroir.OperationConstF64{Value: tc.v1})
+				compiler := requireNewCompiler(t)
+				compiler.initializeReservedRegisters()
+				err := compiler.compileConstF64(&wazeroir.OperationConstF64{Value: tc.v1})
 				require.NoError(t, err)
-				x1 := builder.locationStack.peek()
-				err = builder.handleConstF64(&wazeroir.OperationConstF64{Value: tc.v2})
+				x1 := compiler.locationStack.peek()
+				err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: tc.v2})
 				require.NoError(t, err)
-				x2 := builder.locationStack.peek()
+				x2 := compiler.locationStack.peek()
 
-				err = builder.handleAdd(&wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeF64})
+				err = compiler.compileAdd(&wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeF64})
 				require.NoError(t, err)
-				require.Contains(t, builder.locationStack.usedRegisters, x1.register)
-				require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
+				require.Contains(t, compiler.locationStack.usedRegisters, x1.register)
+				require.NotContains(t, compiler.locationStack.usedRegisters, x2.register)
 
 				// To verify the behavior, we push the value
 				// to the stack.
-				builder.releaseRegisterToStack(x1)
-				builder.returnFunction()
+				compiler.releaseRegisterToStack(x1)
+				compiler.returnFunction()
 
 				// Assemble.
-				code, err := builder.compile()
+				code, _, err := compiler.compile()
 				require.NoError(t, err)
 				// Run code.
 				eng := newEngine()
@@ -1111,31 +1111,31 @@ func TestAmd64Builder_handleLe(t *testing.T) {
 			} else {
 				o = &wazeroir.OperationLe{Type: wazeroir.SignedTypeUint32}
 			}
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
+			compiler := requireNewCompiler(t)
+			compiler.initializeReservedRegisters()
 			x1Reg := int16(x86.REG_R9)
 			x2Reg := int16(x86.REG_R10)
-			builder.locationStack.pushValueOnRegister(x1Reg)
-			builder.locationStack.pushValueOnRegister(x2Reg)
-			builder.movIntConstToRegister(int64(tc.x1), x1Reg)
-			builder.movIntConstToRegister(int64(tc.x2), x2Reg)
-			err := builder.handleLe(o)
+			compiler.locationStack.pushValueOnRegister(x1Reg)
+			compiler.locationStack.pushValueOnRegister(x2Reg)
+			compiler.movIntConstToRegister(int64(tc.x1), x1Reg)
+			compiler.movIntConstToRegister(int64(tc.x2), x2Reg)
+			err := compiler.compileLe(o)
 			require.NoError(t, err)
 
-			require.NotContains(t, builder.locationStack.usedRegisters, x1Reg)
-			require.NotContains(t, builder.locationStack.usedRegisters, x2Reg)
+			require.NotContains(t, compiler.locationStack.usedRegisters, x1Reg)
+			require.NotContains(t, compiler.locationStack.usedRegisters, x2Reg)
 			// To verify the behavior, we push the flag value
 			// to the stack.
-			top := builder.locationStack.peek()
+			top := compiler.locationStack.peek()
 			require.True(t, top.onConditionalRegister() && !top.onRegister())
-			err = builder.moveConditionalToGeneralPurposeRegister(top)
+			err = compiler.moveConditionalToGeneralPurposeRegister(top)
 			require.NoError(t, err)
 			require.True(t, !top.onConditionalRegister() && top.onRegister())
-			builder.releaseRegisterToStack(top)
-			builder.returnFunction()
+			compiler.releaseRegisterToStack(top)
+			compiler.returnFunction()
 
 			// Assemble.
-			code, err := builder.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			// Run code.
 			eng := newEngine()
@@ -1174,30 +1174,30 @@ func TestAmd64Builder_handleLe(t *testing.T) {
 			} else {
 				o = &wazeroir.OperationLe{Type: wazeroir.SignedTypeUint64}
 			}
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
+			compiler := requireNewCompiler(t)
+			compiler.initializeReservedRegisters()
 			x1Reg := int16(x86.REG_R9)
 			x2Reg := int16(x86.REG_R10)
-			builder.locationStack.pushValueOnRegister(x1Reg)
-			builder.locationStack.pushValueOnRegister(x2Reg)
-			builder.movIntConstToRegister(tc.x1, x1Reg)
-			builder.movIntConstToRegister(tc.x2, x2Reg)
-			err := builder.handleLe(o)
+			compiler.locationStack.pushValueOnRegister(x1Reg)
+			compiler.locationStack.pushValueOnRegister(x2Reg)
+			compiler.movIntConstToRegister(tc.x1, x1Reg)
+			compiler.movIntConstToRegister(tc.x2, x2Reg)
+			err := compiler.compileLe(o)
 			require.NoError(t, err)
-			require.NotContains(t, builder.locationStack.usedRegisters, x1Reg)
-			require.NotContains(t, builder.locationStack.usedRegisters, x2Reg)
+			require.NotContains(t, compiler.locationStack.usedRegisters, x1Reg)
+			require.NotContains(t, compiler.locationStack.usedRegisters, x2Reg)
 			// To verify the behavior, we push the flag value
 			// to the stack.
-			top := builder.locationStack.peek()
+			top := compiler.locationStack.peek()
 			require.True(t, top.onConditionalRegister() && !top.onRegister())
-			err = builder.moveConditionalToGeneralPurposeRegister(top)
+			err = compiler.moveConditionalToGeneralPurposeRegister(top)
 			require.NoError(t, err)
 			require.True(t, !top.onConditionalRegister() && top.onRegister())
-			builder.releaseRegisterToStack(top)
-			builder.returnFunction()
+			compiler.releaseRegisterToStack(top)
+			compiler.returnFunction()
 
 			// Assemble.
-			code, err := builder.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			// Run code.
 			eng := newEngine()
@@ -1232,37 +1232,37 @@ func TestAmd64Builder_handleLe(t *testing.T) {
 		} {
 			t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
 				// Prepare operands.
-				builder := requireNewBuilder(t)
-				builder.initializeReservedRegisters()
-				err := builder.handleConstF32(&wazeroir.OperationConstF32{Value: tc.x1})
+				compiler := requireNewCompiler(t)
+				compiler.initializeReservedRegisters()
+				err := compiler.compileConstF32(&wazeroir.OperationConstF32{Value: tc.x1})
 				require.NoError(t, err)
-				x1 := builder.locationStack.peek()
-				err = builder.handleConstF32(&wazeroir.OperationConstF32{Value: tc.x2})
+				x1 := compiler.locationStack.peek()
+				err = compiler.compileConstF32(&wazeroir.OperationConstF32{Value: tc.x2})
 				require.NoError(t, err)
-				x2 := builder.locationStack.peek()
+				x2 := compiler.locationStack.peek()
 
 				// Emit the Le instructions,
-				err = builder.handleLe(&wazeroir.OperationLe{Type: wazeroir.SignedTypeFloat32})
+				err = compiler.compileLe(&wazeroir.OperationLe{Type: wazeroir.SignedTypeFloat32})
 				require.NoError(t, err)
 
 				// At this point, these registers must be consumed.
-				require.NotContains(t, builder.locationStack.usedRegisters, x1.register)
-				require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
+				require.NotContains(t, compiler.locationStack.usedRegisters, x1.register)
+				require.NotContains(t, compiler.locationStack.usedRegisters, x2.register)
 				// Plus the result must be pushed.
-				require.Equal(t, uint64(1), builder.locationStack.sp)
+				require.Equal(t, uint64(1), compiler.locationStack.sp)
 
 				// To verify the behavior, we push the flag value
 				// to the stack.
-				flag := builder.locationStack.peek()
+				flag := compiler.locationStack.peek()
 				require.True(t, flag.onConditionalRegister() && !flag.onRegister())
-				err = builder.moveConditionalToGeneralPurposeRegister(flag)
+				err = compiler.moveConditionalToGeneralPurposeRegister(flag)
 				require.NoError(t, err)
 				require.True(t, !flag.onConditionalRegister() && flag.onRegister())
-				builder.releaseRegisterToStack(flag)
-				builder.returnFunction()
+				compiler.releaseRegisterToStack(flag)
+				compiler.returnFunction()
 
 				// Assemble.
-				code, err := builder.compile()
+				code, _, err := compiler.compile()
 				require.NoError(t, err)
 				// Run code.
 				eng := newEngine()
@@ -1300,37 +1300,37 @@ func TestAmd64Builder_handleLe(t *testing.T) {
 		} {
 			t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
 				// Prepare operands.
-				builder := requireNewBuilder(t)
-				builder.initializeReservedRegisters()
-				err := builder.handleConstF64(&wazeroir.OperationConstF64{Value: tc.x1})
+				compiler := requireNewCompiler(t)
+				compiler.initializeReservedRegisters()
+				err := compiler.compileConstF64(&wazeroir.OperationConstF64{Value: tc.x1})
 				require.NoError(t, err)
-				x1 := builder.locationStack.peek()
-				err = builder.handleConstF64(&wazeroir.OperationConstF64{Value: tc.x2})
+				x1 := compiler.locationStack.peek()
+				err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: tc.x2})
 				require.NoError(t, err)
-				x2 := builder.locationStack.peek()
+				x2 := compiler.locationStack.peek()
 
 				// Emit the Le instructions,
-				err = builder.handleLe(&wazeroir.OperationLe{Type: wazeroir.SignedTypeFloat64})
+				err = compiler.compileLe(&wazeroir.OperationLe{Type: wazeroir.SignedTypeFloat64})
 				require.NoError(t, err)
 
 				// At this point, these registers must be consumed.
-				require.NotContains(t, builder.locationStack.usedRegisters, x1.register)
-				require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
+				require.NotContains(t, compiler.locationStack.usedRegisters, x1.register)
+				require.NotContains(t, compiler.locationStack.usedRegisters, x2.register)
 				// Plus the result must be pushed.
-				require.Equal(t, uint64(1), builder.locationStack.sp)
+				require.Equal(t, uint64(1), compiler.locationStack.sp)
 
 				// To verify the behavior, we push the flag value
 				// to the stack.
-				flag := builder.locationStack.peek()
+				flag := compiler.locationStack.peek()
 				require.True(t, flag.onConditionalRegister() && !flag.onRegister())
-				err = builder.moveConditionalToGeneralPurposeRegister(flag)
+				err = compiler.moveConditionalToGeneralPurposeRegister(flag)
 				require.NoError(t, err)
 				require.True(t, !flag.onConditionalRegister() && flag.onRegister())
-				builder.releaseRegisterToStack(flag)
-				builder.returnFunction()
+				compiler.releaseRegisterToStack(flag)
+				compiler.returnFunction()
 
 				// Assemble.
-				code, err := builder.compile()
+				code, _, err := compiler.compile()
 				require.NoError(t, err)
 				// Run code.
 				eng := newEngine()
@@ -1356,27 +1356,27 @@ func TestAmd64Builder_handleSub(t *testing.T) {
 	t.Run("int32", func(t *testing.T) {
 		const x1Value uint32 = 1 << 31
 		const x2Value uint32 = 51
-		builder := requireNewBuilder(t)
-		builder.initializeReservedRegisters()
-		err := builder.handleConstI32(&wazeroir.OperationConstI32{Value: x1Value})
+		compiler := requireNewCompiler(t)
+		compiler.initializeReservedRegisters()
+		err := compiler.compileConstI32(&wazeroir.OperationConstI32{Value: x1Value})
 		require.NoError(t, err)
-		x1 := builder.locationStack.peek()
-		err = builder.handleConstI32(&wazeroir.OperationConstI32{Value: x2Value})
+		x1 := compiler.locationStack.peek()
+		err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: x2Value})
 		require.NoError(t, err)
-		x2 := builder.locationStack.peek()
+		x2 := compiler.locationStack.peek()
 
-		err = builder.handleSub(&wazeroir.OperationSub{Type: wazeroir.UnsignedTypeI32})
+		err = compiler.compileSub(&wazeroir.OperationSub{Type: wazeroir.UnsignedTypeI32})
 		require.NoError(t, err)
-		require.Contains(t, builder.locationStack.usedRegisters, x1.register)
-		require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
+		require.Contains(t, compiler.locationStack.usedRegisters, x1.register)
+		require.NotContains(t, compiler.locationStack.usedRegisters, x2.register)
 
 		// To verify the behavior, we push the value
 		// to the stack.
-		builder.releaseRegisterToStack(x1)
-		builder.returnFunction()
+		compiler.releaseRegisterToStack(x1)
+		compiler.returnFunction()
 
 		// Assemble.
-		code, err := builder.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 		// Run code.
 		eng := newEngine()
@@ -1393,27 +1393,27 @@ func TestAmd64Builder_handleSub(t *testing.T) {
 	t.Run("int64", func(t *testing.T) {
 		const x1Value uint64 = 1 << 35
 		const x2Value uint64 = 51
-		builder := requireNewBuilder(t)
-		builder.initializeReservedRegisters()
-		err := builder.handleConstI64(&wazeroir.OperationConstI64{Value: x1Value})
+		compiler := requireNewCompiler(t)
+		compiler.initializeReservedRegisters()
+		err := compiler.compileConstI64(&wazeroir.OperationConstI64{Value: x1Value})
 		require.NoError(t, err)
-		x1 := builder.locationStack.peek()
-		err = builder.handleConstI64(&wazeroir.OperationConstI64{Value: x2Value})
+		x1 := compiler.locationStack.peek()
+		err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: x2Value})
 		require.NoError(t, err)
-		x2 := builder.locationStack.peek()
+		x2 := compiler.locationStack.peek()
 
-		err = builder.handleSub(&wazeroir.OperationSub{Type: wazeroir.UnsignedTypeI64})
+		err = compiler.compileSub(&wazeroir.OperationSub{Type: wazeroir.UnsignedTypeI64})
 		require.NoError(t, err)
-		require.Contains(t, builder.locationStack.usedRegisters, x1.register)
-		require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
+		require.Contains(t, compiler.locationStack.usedRegisters, x1.register)
+		require.NotContains(t, compiler.locationStack.usedRegisters, x2.register)
 
 		// To verify the behavior, we push the value
 		// to the stack.
-		builder.releaseRegisterToStack(x1)
-		builder.returnFunction()
+		compiler.releaseRegisterToStack(x1)
+		compiler.returnFunction()
 
 		// Assemble.
-		code, err := builder.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 		// Run code.
 		eng := newEngine()
@@ -1440,27 +1440,27 @@ func TestAmd64Builder_handleSub(t *testing.T) {
 		} {
 			tc := tc
 			t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
-				builder := requireNewBuilder(t)
-				builder.initializeReservedRegisters()
-				err := builder.handleConstF32(&wazeroir.OperationConstF32{Value: tc.v1})
+				compiler := requireNewCompiler(t)
+				compiler.initializeReservedRegisters()
+				err := compiler.compileConstF32(&wazeroir.OperationConstF32{Value: tc.v1})
 				require.NoError(t, err)
-				x1 := builder.locationStack.peek()
-				err = builder.handleConstF32(&wazeroir.OperationConstF32{Value: tc.v2})
+				x1 := compiler.locationStack.peek()
+				err = compiler.compileConstF32(&wazeroir.OperationConstF32{Value: tc.v2})
 				require.NoError(t, err)
-				x2 := builder.locationStack.peek()
+				x2 := compiler.locationStack.peek()
 
-				err = builder.handleSub(&wazeroir.OperationSub{Type: wazeroir.UnsignedTypeF32})
+				err = compiler.compileSub(&wazeroir.OperationSub{Type: wazeroir.UnsignedTypeF32})
 				require.NoError(t, err)
-				require.Contains(t, builder.locationStack.usedRegisters, x1.register)
-				require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
+				require.Contains(t, compiler.locationStack.usedRegisters, x1.register)
+				require.NotContains(t, compiler.locationStack.usedRegisters, x2.register)
 
 				// To verify the behavior, we push the value
 				// to the stack.
-				builder.releaseRegisterToStack(x1)
-				builder.returnFunction()
+				compiler.releaseRegisterToStack(x1)
+				compiler.returnFunction()
 
 				// Assemble.
-				code, err := builder.compile()
+				code, _, err := compiler.compile()
 				require.NoError(t, err)
 				// Run code.
 				eng := newEngine()
@@ -1489,27 +1489,27 @@ func TestAmd64Builder_handleSub(t *testing.T) {
 		} {
 			tc := tc
 			t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
-				builder := requireNewBuilder(t)
-				builder.initializeReservedRegisters()
-				err := builder.handleConstF64(&wazeroir.OperationConstF64{Value: tc.v1})
+				compiler := requireNewCompiler(t)
+				compiler.initializeReservedRegisters()
+				err := compiler.compileConstF64(&wazeroir.OperationConstF64{Value: tc.v1})
 				require.NoError(t, err)
-				x1 := builder.locationStack.peek()
-				err = builder.handleConstF64(&wazeroir.OperationConstF64{Value: tc.v2})
+				x1 := compiler.locationStack.peek()
+				err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: tc.v2})
 				require.NoError(t, err)
-				x2 := builder.locationStack.peek()
+				x2 := compiler.locationStack.peek()
 
-				err = builder.handleSub(&wazeroir.OperationSub{Type: wazeroir.UnsignedTypeF64})
+				err = compiler.compileSub(&wazeroir.OperationSub{Type: wazeroir.UnsignedTypeF64})
 				require.NoError(t, err)
-				require.Contains(t, builder.locationStack.usedRegisters, x1.register)
-				require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
+				require.Contains(t, compiler.locationStack.usedRegisters, x1.register)
+				require.NotContains(t, compiler.locationStack.usedRegisters, x2.register)
 
 				// To verify the behavior, we push the value
 				// to the stack.
-				builder.releaseRegisterToStack(x1)
-				builder.returnFunction()
+				compiler.releaseRegisterToStack(x1)
+				compiler.returnFunction()
 
 				// Assemble.
-				code, err := builder.compile()
+				code, _, err := compiler.compile()
 				require.NoError(t, err)
 				// Run code.
 				eng := newEngine()
@@ -1530,29 +1530,29 @@ func TestAmd64Builder_handleSub(t *testing.T) {
 func TestAmd64Builder_handleCall(t *testing.T) {
 	t.Run("host function", func(t *testing.T) {
 		const functionIndex = 5
-		builder := requireNewBuilder(t)
-		builder.f = &wasm.FunctionInstance{ModuleInstance: &wasm.ModuleInstance{}}
+		compiler := requireNewCompiler(t)
+		compiler.f = &wasm.FunctionInstance{ModuleInstance: &wasm.ModuleInstance{}}
 
 		// Setup.
 		eng := newEngine()
-		builder.eng = eng
+		compiler.eng = eng
 		hostFuncRefValue := reflect.ValueOf(func() {})
 		hostFuncInstance := &wasm.FunctionInstance{HostFunction: &hostFuncRefValue}
-		builder.f.ModuleInstance.Functions = make([]*wasm.FunctionInstance, functionIndex+1)
-		builder.f.ModuleInstance.Functions[functionIndex] = hostFuncInstance
+		compiler.f.ModuleInstance.Functions = make([]*wasm.FunctionInstance, functionIndex+1)
+		compiler.f.ModuleInstance.Functions[functionIndex] = hostFuncInstance
 		eng.compiledHostFunctionIndex[hostFuncInstance] = functionIndex
 
 		// Build codes.
-		builder.initializeReservedRegisters()
+		compiler.initializeReservedRegisters()
 		// Push the value onto stack.
-		_ = builder.locationStack.pushValueOnStack() // dummy value, not actually used!
-		loc := builder.locationStack.pushValueOnRegister(x86.REG_AX)
-		builder.movIntConstToRegister(int64(50), loc.register)
-		err := builder.handleCall(&wazeroir.OperationCall{FunctionIndex: functionIndex})
+		_ = compiler.locationStack.pushValueOnStack() // dummy value, not actually used!
+		loc := compiler.locationStack.pushValueOnRegister(x86.REG_AX)
+		compiler.movIntConstToRegister(int64(50), loc.register)
+		err := compiler.compileCall(&wazeroir.OperationCall{FunctionIndex: functionIndex})
 		require.NoError(t, err)
 
 		// Compile.
-		code, err := builder.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 
 		// Run code.
@@ -1571,29 +1571,29 @@ func TestAmd64Builder_handleCall(t *testing.T) {
 	})
 	t.Run("wasm function", func(t *testing.T) {
 		const functionIndex = 20
-		builder := requireNewBuilder(t)
-		builder.f = &wasm.FunctionInstance{ModuleInstance: &wasm.ModuleInstance{}}
+		compiler := requireNewCompiler(t)
+		compiler.f = &wasm.FunctionInstance{ModuleInstance: &wasm.ModuleInstance{}}
 
 		// Setup.
 		eng := newEngine()
-		builder.eng = eng
+		compiler.eng = eng
 		wasmFuncInstance := &wasm.FunctionInstance{}
-		builder.f.ModuleInstance.Functions = make([]*wasm.FunctionInstance, functionIndex+1)
-		builder.f.ModuleInstance.Functions[functionIndex] = wasmFuncInstance
+		compiler.f.ModuleInstance.Functions = make([]*wasm.FunctionInstance, functionIndex+1)
+		compiler.f.ModuleInstance.Functions[functionIndex] = wasmFuncInstance
 		eng.compiledWasmFunctionIndex[wasmFuncInstance] = functionIndex
 
 		// Build codes.
-		builder.initializeReservedRegisters()
+		compiler.initializeReservedRegisters()
 		// Push the value onto stack.
-		_ = builder.locationStack.pushValueOnStack() // dummy value, not actually used!
-		_ = builder.locationStack.pushValueOnStack() // dummy value, not actually used!
-		loc := builder.locationStack.pushValueOnRegister(x86.REG_AX)
-		builder.movIntConstToRegister(int64(50), loc.register)
-		err := builder.handleCall(&wazeroir.OperationCall{FunctionIndex: functionIndex})
+		_ = compiler.locationStack.pushValueOnStack() // dummy value, not actually used!
+		_ = compiler.locationStack.pushValueOnStack() // dummy value, not actually used!
+		loc := compiler.locationStack.pushValueOnRegister(x86.REG_AX)
+		compiler.movIntConstToRegister(int64(50), loc.register)
+		err := compiler.compileCall(&wazeroir.OperationCall{FunctionIndex: functionIndex})
 		require.NoError(t, err)
 
 		// Compile.
-		code, err := builder.compile()
+		code, _, err := compiler.compile()
 		require.NoError(t, err)
 
 		// Run code.
@@ -1622,20 +1622,20 @@ func TestAmd64Builder_handleLoad(t *testing.T) {
 		tp := tp
 		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
 			eng := newEngine()
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
+			compiler := requireNewCompiler(t)
+			compiler.initializeReservedRegisters()
 			// Before load operations, we must push the base offset value.
 			const baseOffset = 100 // For testing. Arbitrary number is fine.
-			base := builder.locationStack.pushValueOnStack()
+			base := compiler.locationStack.pushValueOnStack()
 			eng.stack[base.stackPointer] = baseOffset
 
 			// Emit the memory load instructions.
 			o := &wazeroir.OperationLoad{Type: tp, Arg: &wazeroir.MemoryImmediate{Offest: 361}}
-			err := builder.handleLoad(o)
+			err := compiler.compileLoad(o)
 			require.NoError(t, err)
 
 			// At this point, the loaded value must be on top of the stack, and placed on a register.
-			loadedValue := builder.locationStack.peek()
+			loadedValue := compiler.locationStack.peek()
 			require.True(t, loadedValue.onRegister())
 
 			// Double the loaded value in order to verify the behavior.
@@ -1658,20 +1658,20 @@ func TestAmd64Builder_handleLoad(t *testing.T) {
 				require.True(t, isFloatRegister(loadedValue.register))
 				addInst = x86.AADDSD
 			}
-			doubleLoadedValue := builder.newProg()
+			doubleLoadedValue := compiler.newProg()
 			doubleLoadedValue.As = addInst
 			doubleLoadedValue.To.Type = obj.TYPE_REG
 			doubleLoadedValue.To.Reg = loadedValue.register
 			doubleLoadedValue.From.Type = obj.TYPE_REG
 			doubleLoadedValue.From.Reg = loadedValue.register
-			builder.addInstruction(doubleLoadedValue)
+			compiler.addInstruction(doubleLoadedValue)
 
 			// We need to write the result back to the memory stack.
-			builder.releaseRegisterToStack(loadedValue)
+			compiler.releaseRegisterToStack(loadedValue)
 
 			// Compile.
-			builder.returnFunction()
-			code, err := builder.compile()
+			compiler.returnFunction()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Place the load target value to the memory.
@@ -1722,36 +1722,36 @@ func TestAmd64Builder_handleLoad8(t *testing.T) {
 		tp := tp
 		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
 			eng := newEngine()
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
+			compiler := requireNewCompiler(t)
+			compiler.initializeReservedRegisters()
 			// Before load operations, we must push the base offset value.
 			const baseOffset = 100 // For testing. Arbitrary number is fine.
-			base := builder.locationStack.pushValueOnStack()
+			base := compiler.locationStack.pushValueOnStack()
 			eng.stack[base.stackPointer] = baseOffset
 
 			// Emit the memory load instructions.
 			o := &wazeroir.OperationLoad8{Type: tp, Arg: &wazeroir.MemoryImmediate{Offest: 361}}
-			err := builder.handleLoad8(o)
+			err := compiler.compileLoad8(o)
 			require.NoError(t, err)
 
 			// At this point, the loaded value must be on top of the stack, and placed on a register.
-			loadedValue := builder.locationStack.peek()
+			loadedValue := compiler.locationStack.peek()
 			require.Equal(t, generalPurposeRegisterTypeInt, loadedValue.registerType())
 			require.True(t, loadedValue.onRegister())
 
 			// Increment the loaded value in order to verify the behavior.
-			doubleLoadedValue := builder.newProg()
+			doubleLoadedValue := compiler.newProg()
 			doubleLoadedValue.As = x86.AINCB
 			doubleLoadedValue.To.Type = obj.TYPE_REG
 			doubleLoadedValue.To.Reg = loadedValue.register
-			builder.addInstruction(doubleLoadedValue)
+			compiler.addInstruction(doubleLoadedValue)
 
 			// We need to write the result back to the memory stack.
-			builder.releaseRegisterToStack(loadedValue)
+			compiler.releaseRegisterToStack(loadedValue)
 
 			// Compile.
-			builder.returnFunction()
-			code, err := builder.compile()
+			compiler.returnFunction()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Place the load target value to the memory.
@@ -1786,36 +1786,36 @@ func TestAmd64Builder_handleLoad16(t *testing.T) {
 		tp := tp
 		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
 			eng := newEngine()
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
+			compiler := requireNewCompiler(t)
+			compiler.initializeReservedRegisters()
 			// Before load operations, we must push the base offset value.
 			const baseOffset = 100 // For testing. Arbitrary number is fine.
-			base := builder.locationStack.pushValueOnStack()
+			base := compiler.locationStack.pushValueOnStack()
 			eng.stack[base.stackPointer] = baseOffset
 
 			// Emit the memory load instructions.
 			o := &wazeroir.OperationLoad16{Type: tp, Arg: &wazeroir.MemoryImmediate{Offest: 361}}
-			err := builder.handleLoad16(o)
+			err := compiler.compileLoad16(o)
 			require.NoError(t, err)
 
 			// At this point, the loaded value must be on top of the stack, and placed on a register.
-			loadedValue := builder.locationStack.peek()
+			loadedValue := compiler.locationStack.peek()
 			require.Equal(t, generalPurposeRegisterTypeInt, loadedValue.registerType())
 			require.True(t, loadedValue.onRegister())
 
 			// Increment the loaded value in order to verify the behavior.
-			doubleLoadedValue := builder.newProg()
+			doubleLoadedValue := compiler.newProg()
 			doubleLoadedValue.As = x86.AINCW
 			doubleLoadedValue.To.Type = obj.TYPE_REG
 			doubleLoadedValue.To.Reg = loadedValue.register
-			builder.addInstruction(doubleLoadedValue)
+			compiler.addInstruction(doubleLoadedValue)
 
 			// We need to write the result back to the memory stack.
-			builder.releaseRegisterToStack(loadedValue)
+			compiler.releaseRegisterToStack(loadedValue)
 
 			// Compile.
-			builder.returnFunction()
-			code, err := builder.compile()
+			compiler.returnFunction()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Place the load target value to the memory.
@@ -1842,36 +1842,36 @@ func TestAmd64Builder_handleLoad16(t *testing.T) {
 
 func TestAmd64Builder_handleLoad32(t *testing.T) {
 	eng := newEngine()
-	builder := requireNewBuilder(t)
-	builder.initializeReservedRegisters()
+	compiler := requireNewCompiler(t)
+	compiler.initializeReservedRegisters()
 	// Before load operations, we must push the base offset value.
 	const baseOffset = 100 // For testing. Arbitrary number is fine.
-	base := builder.locationStack.pushValueOnStack()
+	base := compiler.locationStack.pushValueOnStack()
 	eng.stack[base.stackPointer] = baseOffset
 
 	// Emit the memory load instructions.
 	o := &wazeroir.OperationLoad32{Arg: &wazeroir.MemoryImmediate{Offest: 361}}
-	err := builder.handleLoad32(o)
+	err := compiler.compileLoad32(o)
 	require.NoError(t, err)
 
 	// At this point, the loaded value must be on top of the stack, and placed on a register.
-	loadedValue := builder.locationStack.peek()
+	loadedValue := compiler.locationStack.peek()
 	require.Equal(t, generalPurposeRegisterTypeInt, loadedValue.registerType())
 	require.True(t, loadedValue.onRegister())
 
 	// Increment the loaded value in order to verify the behavior.
-	doubleLoadedValue := builder.newProg()
+	doubleLoadedValue := compiler.newProg()
 	doubleLoadedValue.As = x86.AINCL
 	doubleLoadedValue.To.Type = obj.TYPE_REG
 	doubleLoadedValue.To.Reg = loadedValue.register
-	builder.addInstruction(doubleLoadedValue)
+	compiler.addInstruction(doubleLoadedValue)
 
 	// We need to write the result back to the memory stack.
-	builder.releaseRegisterToStack(loadedValue)
+	compiler.releaseRegisterToStack(loadedValue)
 
 	// Compile.
-	builder.returnFunction()
-	code, err := builder.compile()
+	compiler.returnFunction()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 
 	// Place the load target value to the memory.
@@ -1904,15 +1904,15 @@ func TestAmd64Builder_handleStore(t *testing.T) {
 		tp := tp
 		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
 			eng := newEngine()
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
+			compiler := requireNewCompiler(t)
+			compiler.initializeReservedRegisters()
 
 			// Before store operations, we must push the base offset, and the store target values.
 			const baseOffset = 100 // For testing. Arbitrary number is fine.
-			base := builder.locationStack.pushValueOnStack()
+			base := compiler.locationStack.pushValueOnStack()
 			eng.stack[base.stackPointer] = baseOffset
 			storeTargetValue := uint64(math.MaxUint64)
-			storeTarget := builder.locationStack.pushValueOnStack()
+			storeTarget := compiler.locationStack.pushValueOnStack()
 			eng.stack[storeTarget.stackPointer] = storeTargetValue
 			switch tp {
 			case wazeroir.UnsignedTypeI32, wazeroir.UnsignedTypeF32:
@@ -1923,17 +1923,17 @@ func TestAmd64Builder_handleStore(t *testing.T) {
 
 			// Emit the memory load instructions.
 			o := &wazeroir.OperationStore{Type: tp, Arg: &wazeroir.MemoryImmediate{Offest: 361}}
-			err := builder.handleStore(o)
+			err := compiler.compileStore(o)
 			require.NoError(t, err)
 
 			// At this point, two values are popped so the stack pointer must be zero.
-			require.Equal(t, uint64(0), builder.locationStack.sp)
+			require.Equal(t, uint64(0), compiler.locationStack.sp)
 			// Plus there should be no used registers.
-			require.Len(t, builder.locationStack.usedRegisters, 0)
+			require.Len(t, compiler.locationStack.usedRegisters, 0)
 
 			// Compile.
-			builder.returnFunction()
-			code, err := builder.compile()
+			compiler.returnFunction()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Run code.
@@ -1965,31 +1965,31 @@ func TestAmd64Builder_handleStore(t *testing.T) {
 
 func TestAmd64Builder_handleStore8(t *testing.T) {
 	eng := newEngine()
-	builder := requireNewBuilder(t)
-	builder.initializeReservedRegisters()
+	compiler := requireNewCompiler(t)
+	compiler.initializeReservedRegisters()
 
 	// Before store operations, we must push the base offset, and the store target values.
 	const baseOffset = 100 // For testing. Arbitrary number is fine.
-	base := builder.locationStack.pushValueOnStack()
+	base := compiler.locationStack.pushValueOnStack()
 	eng.stack[base.stackPointer] = baseOffset
 	storeTargetValue := uint64(0x12_34_56_78_9a_bc_ef_01) // For testing. Arbitrary number is fine.
-	storeTarget := builder.locationStack.pushValueOnStack()
+	storeTarget := compiler.locationStack.pushValueOnStack()
 	eng.stack[storeTarget.stackPointer] = storeTargetValue
 	storeTarget.setRegisterType(generalPurposeRegisterTypeInt)
 
 	// Emit the memory load instructions.
 	o := &wazeroir.OperationStore8{Arg: &wazeroir.MemoryImmediate{Offest: 361}}
-	err := builder.handleStore8(o)
+	err := compiler.compileStore8(o)
 	require.NoError(t, err)
 
 	// At this point, two values are popped so the stack pointer must be zero.
-	require.Equal(t, uint64(0), builder.locationStack.sp)
+	require.Equal(t, uint64(0), compiler.locationStack.sp)
 	// Plus there should be no used registers.
-	require.Len(t, builder.locationStack.usedRegisters, 0)
+	require.Len(t, compiler.locationStack.usedRegisters, 0)
 
 	// Compile.
-	builder.returnFunction()
-	code, err := builder.compile()
+	compiler.returnFunction()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 
 	// Run code.
@@ -2011,31 +2011,31 @@ func TestAmd64Builder_handleStore8(t *testing.T) {
 
 func TestAmd64Builder_handleStore16(t *testing.T) {
 	eng := newEngine()
-	builder := requireNewBuilder(t)
-	builder.initializeReservedRegisters()
+	compiler := requireNewCompiler(t)
+	compiler.initializeReservedRegisters()
 
 	// Before store operations, we must push the base offset, and the store target values.
 	const baseOffset = 100 // For testing. Arbitrary number is fine.
-	base := builder.locationStack.pushValueOnStack()
+	base := compiler.locationStack.pushValueOnStack()
 	eng.stack[base.stackPointer] = baseOffset
 	storeTargetValue := uint64(0x12_34_56_78_9a_bc_ef_01) // For testing. Arbitrary number is fine.
-	storeTarget := builder.locationStack.pushValueOnStack()
+	storeTarget := compiler.locationStack.pushValueOnStack()
 	eng.stack[storeTarget.stackPointer] = storeTargetValue
 	storeTarget.setRegisterType(generalPurposeRegisterTypeInt)
 
 	// Emit the memory load instructions.
 	o := &wazeroir.OperationStore16{Arg: &wazeroir.MemoryImmediate{Offest: 361}}
-	err := builder.handleStore16(o)
+	err := compiler.compileStore16(o)
 	require.NoError(t, err)
 
 	// At this point, two values are popped so the stack pointer must be zero.
-	require.Equal(t, uint64(0), builder.locationStack.sp)
+	require.Equal(t, uint64(0), compiler.locationStack.sp)
 	// Plus there should be no used registers.
-	require.Len(t, builder.locationStack.usedRegisters, 0)
+	require.Len(t, compiler.locationStack.usedRegisters, 0)
 
 	// Compile.
-	builder.returnFunction()
-	code, err := builder.compile()
+	compiler.returnFunction()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 
 	// Run code.
@@ -2057,31 +2057,31 @@ func TestAmd64Builder_handleStore16(t *testing.T) {
 
 func TestAmd64Builder_handleStore32(t *testing.T) {
 	eng := newEngine()
-	builder := requireNewBuilder(t)
-	builder.initializeReservedRegisters()
+	compiler := requireNewCompiler(t)
+	compiler.initializeReservedRegisters()
 
 	// Before store operations, we must push the base offset, and the store target values.
 	const baseOffset = 100 // For testing. Arbitrary number is fine.
-	base := builder.locationStack.pushValueOnStack()
+	base := compiler.locationStack.pushValueOnStack()
 	eng.stack[base.stackPointer] = baseOffset
 	storeTargetValue := uint64(0x12_34_56_78_9a_bc_ef_01) // For testing. Arbitrary number is fine.
-	storeTarget := builder.locationStack.pushValueOnStack()
+	storeTarget := compiler.locationStack.pushValueOnStack()
 	eng.stack[storeTarget.stackPointer] = storeTargetValue
 	storeTarget.setRegisterType(generalPurposeRegisterTypeInt)
 
 	// Emit the memory load instructions.
 	o := &wazeroir.OperationStore32{Arg: &wazeroir.MemoryImmediate{Offest: 361}}
-	err := builder.handleStore32(o)
+	err := compiler.compileStore32(o)
 	require.NoError(t, err)
 
 	// At this point, two values are popped so the stack pointer must be zero.
-	require.Equal(t, uint64(0), builder.locationStack.sp)
+	require.Equal(t, uint64(0), compiler.locationStack.sp)
 	// Plus there should be no used registers.
-	require.Len(t, builder.locationStack.usedRegisters, 0)
+	require.Len(t, compiler.locationStack.usedRegisters, 0)
 
 	// Compile.
-	builder.returnFunction()
-	code, err := builder.compile()
+	compiler.returnFunction()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 
 	// Run code.
@@ -2102,14 +2102,14 @@ func TestAmd64Builder_handleStore32(t *testing.T) {
 }
 
 func TestAmd64Builder_handleMemoryGrow(t *testing.T) {
-	builder := requireNewBuilder(t)
+	compiler := requireNewCompiler(t)
 
-	builder.initializeReservedRegisters()
+	compiler.initializeReservedRegisters()
 	// Emit memory.grow instructions.
-	builder.handleMemoryGrow()
+	compiler.compileMemoryGrow()
 
 	// Compile.
-	code, err := builder.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 
 	// Run code.
@@ -2125,16 +2125,16 @@ func TestAmd64Builder_handleMemoryGrow(t *testing.T) {
 }
 
 func TestAmd64Builder_handleMemorySize(t *testing.T) {
-	builder := requireNewBuilder(t)
-	builder.initializeReservedRegisters()
+	compiler := requireNewCompiler(t)
+	compiler.initializeReservedRegisters()
 	// Emit memory.size instructions.
-	builder.handleMemorySize()
+	compiler.compileMemorySize()
 	// At this point, the size of memory should be pushed onto the stack.
-	require.Equal(t, uint64(1), builder.locationStack.sp)
-	require.Equal(t, generalPurposeRegisterTypeInt, builder.locationStack.peek().registerType())
+	require.Equal(t, uint64(1), compiler.locationStack.sp)
+	require.Equal(t, generalPurposeRegisterTypeInt, compiler.locationStack.peek().registerType())
 
 	// Compile.
-	code, err := builder.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 
 	// Run code.
@@ -2151,25 +2151,25 @@ func TestAmd64Builder_handleMemorySize(t *testing.T) {
 
 func TestAmd64Builder_handleDrop(t *testing.T) {
 	t.Run("nil", func(t *testing.T) {
-		builder := requireNewBuilder(t)
-		err := builder.handleDrop(&wazeroir.OperationDrop{})
+		compiler := requireNewCompiler(t)
+		err := compiler.compileDrop(&wazeroir.OperationDrop{})
 		require.NoError(t, err)
 	})
 	t.Run("zero start", func(t *testing.T) {
-		builder := requireNewBuilder(t)
-		shouldPeek := builder.locationStack.pushValueOnStack()
+		compiler := requireNewCompiler(t)
+		shouldPeek := compiler.locationStack.pushValueOnStack()
 		const numReg = 10
 		for i := int16(0); i < numReg; i++ {
-			builder.locationStack.pushValueOnRegister(i)
+			compiler.locationStack.pushValueOnRegister(i)
 		}
-		err := builder.handleDrop(&wazeroir.OperationDrop{
+		err := compiler.compileDrop(&wazeroir.OperationDrop{
 			Range: &wazeroir.InclusiveRange{Start: 0, End: numReg},
 		})
 		require.NoError(t, err)
 		for i := int16(0); i < numReg; i++ {
-			require.NotContains(t, builder.locationStack.usedRegisters, i)
+			require.NotContains(t, compiler.locationStack.usedRegisters, i)
 		}
-		actualPeek := builder.locationStack.peek()
+		actualPeek := compiler.locationStack.peek()
 		require.Equal(t, shouldPeek, actualPeek)
 	})
 	t.Run("live all on register", func(t *testing.T) {
@@ -2177,31 +2177,31 @@ func TestAmd64Builder_handleDrop(t *testing.T) {
 			numLive = 3
 			dropNum = 5
 		)
-		builder := requireNewBuilder(t)
-		shouldBottom := builder.locationStack.pushValueOnStack()
+		compiler := requireNewCompiler(t)
+		shouldBottom := compiler.locationStack.pushValueOnStack()
 		for i := int16(0); i < dropNum; i++ {
-			builder.locationStack.pushValueOnRegister(i)
+			compiler.locationStack.pushValueOnRegister(i)
 		}
 		for i := int16(dropNum); i < numLive+dropNum; i++ {
-			builder.locationStack.pushValueOnRegister(i)
+			compiler.locationStack.pushValueOnRegister(i)
 		}
-		err := builder.handleDrop(&wazeroir.OperationDrop{
+		err := compiler.compileDrop(&wazeroir.OperationDrop{
 			Range: &wazeroir.InclusiveRange{Start: numLive, End: numLive + dropNum - 1},
 		})
 		require.NoError(t, err)
 		for i := int16(0); i < dropNum; i++ {
-			require.NotContains(t, builder.locationStack.usedRegisters, i)
+			require.NotContains(t, compiler.locationStack.usedRegisters, i)
 		}
 		for i := int16(dropNum); i < numLive+dropNum; i++ {
-			require.Contains(t, builder.locationStack.usedRegisters, i)
+			require.Contains(t, compiler.locationStack.usedRegisters, i)
 		}
 		for i := int16(0); i < numLive; i++ {
-			actual := builder.locationStack.pop()
+			actual := compiler.locationStack.pop()
 			require.True(t, actual.onRegister())
 			require.Equal(t, numLive+dropNum-1-i, actual.register)
 		}
-		require.Equal(t, uint64(1), builder.locationStack.sp)
-		require.Equal(t, shouldBottom, builder.locationStack.pop())
+		require.Equal(t, uint64(1), compiler.locationStack.sp)
+		require.Equal(t, shouldBottom, compiler.locationStack.pop())
 	})
 	t.Run("live on stack", func(t *testing.T) {
 		// This is for testing all the edge cases with fake registers.
@@ -2211,60 +2211,60 @@ func TestAmd64Builder_handleDrop(t *testing.T) {
 				dropNum        = 5
 				liveRegisterID = 10
 			)
-			builder := requireNewBuilder(t)
-			bottom := builder.locationStack.pushValueOnStack()
+			compiler := requireNewCompiler(t)
+			bottom := compiler.locationStack.pushValueOnStack()
 			for i := int16(0); i < dropNum; i++ {
-				builder.locationStack.pushValueOnRegister(i)
+				compiler.locationStack.pushValueOnRegister(i)
 			}
 			// The bottom live value is on the stack.
-			bottomLive := builder.locationStack.pushValueOnStack()
+			bottomLive := compiler.locationStack.pushValueOnStack()
 			// The second live value is on the register.
-			LiveRegister := builder.locationStack.pushValueOnRegister(liveRegisterID)
+			LiveRegister := compiler.locationStack.pushValueOnRegister(liveRegisterID)
 			// The top live value is on the conditional.
-			topLive := builder.locationStack.pushValueOnConditionalRegister(conditionalRegisterStateAE)
+			topLive := compiler.locationStack.pushValueOnConditionalRegister(conditionalRegisterStateAE)
 			require.True(t, topLive.onConditionalRegister())
-			err := builder.handleDrop(&wazeroir.OperationDrop{
+			err := compiler.compileDrop(&wazeroir.OperationDrop{
 				Range: &wazeroir.InclusiveRange{Start: numLive, End: numLive + dropNum - 1},
 			})
 			require.NoError(t, err)
-			require.Equal(t, uint64(4), builder.locationStack.sp)
+			require.Equal(t, uint64(4), compiler.locationStack.sp)
 			for i := int16(0); i < dropNum; i++ {
-				require.NotContains(t, builder.locationStack.usedRegisters, i)
+				require.NotContains(t, compiler.locationStack.usedRegisters, i)
 			}
 			// Top value should be on the register.
-			actualTopLive := builder.locationStack.pop()
+			actualTopLive := compiler.locationStack.pop()
 			require.True(t, actualTopLive.onRegister() && !actualTopLive.onConditionalRegister())
 			require.Equal(t, topLive, actualTopLive)
 			// Second one should be on the same register.
-			actualLiveRegister := builder.locationStack.pop()
+			actualLiveRegister := compiler.locationStack.pop()
 			require.Equal(t, LiveRegister, actualLiveRegister)
 			// The bottom live value should be moved onto the stack.
-			actualBottomLive := builder.locationStack.pop()
+			actualBottomLive := compiler.locationStack.pop()
 			require.Equal(t, bottomLive, actualBottomLive)
 			require.True(t, actualBottomLive.onRegister() && !actualBottomLive.onStack())
 			// The bottom after drop should stay on stack.
-			actualBottom := builder.locationStack.pop()
+			actualBottom := compiler.locationStack.pop()
 			require.Equal(t, bottom, actualBottom)
 			require.True(t, bottom.onStack())
 		})
 		t.Run("real", func(t *testing.T) {
 			eng := newEngine()
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
-			bottom := builder.locationStack.pushValueOnRegister(x86.REG_R10)
-			builder.locationStack.pushValueOnRegister(x86.REG_R9)
-			top := builder.locationStack.pushValueOnStack()
+			compiler := requireNewCompiler(t)
+			compiler.initializeReservedRegisters()
+			bottom := compiler.locationStack.pushValueOnRegister(x86.REG_R10)
+			compiler.locationStack.pushValueOnRegister(x86.REG_R9)
+			top := compiler.locationStack.pushValueOnStack()
 			eng.stack[top.stackPointer] = 5000
-			builder.movIntConstToRegister(300, bottom.register)
-			err := builder.handleDrop(&wazeroir.OperationDrop{
+			compiler.movIntConstToRegister(300, bottom.register)
+			err := compiler.compileDrop(&wazeroir.OperationDrop{
 				Range: &wazeroir.InclusiveRange{Start: 1, End: 1},
 			})
 			require.NoError(t, err)
-			builder.releaseRegisterToStack(bottom)
-			builder.releaseRegisterToStack(top)
-			builder.returnFunction()
+			compiler.releaseRegisterToStack(bottom)
+			compiler.releaseRegisterToStack(top)
+			compiler.returnFunction()
 			// Assemble.
-			code, err := builder.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			// Run code.
 			require.Equal(t, []uint64{0, 0, 5000}, eng.stack[:3])
@@ -2286,27 +2286,27 @@ func TestAmd64Builder_handleDrop(t *testing.T) {
 
 func TestAmd64Builder_releaseAllRegistersToStack(t *testing.T) {
 	eng := newEngine()
-	builder := requireNewBuilder(t)
-	builder.initializeReservedRegisters()
+	compiler := requireNewCompiler(t)
+	compiler.initializeReservedRegisters()
 	x1Reg := int16(x86.REG_AX)
 	x2Reg := int16(x86.REG_R10)
-	_ = builder.locationStack.pushValueOnStack()
+	_ = compiler.locationStack.pushValueOnStack()
 	eng.stack[0] = 100
-	builder.locationStack.pushValueOnRegister(x1Reg)
-	builder.locationStack.pushValueOnRegister(x2Reg)
-	_ = builder.locationStack.pushValueOnStack()
+	compiler.locationStack.pushValueOnRegister(x1Reg)
+	compiler.locationStack.pushValueOnRegister(x2Reg)
+	_ = compiler.locationStack.pushValueOnStack()
 	eng.stack[3] = 123
-	require.Len(t, builder.locationStack.usedRegisters, 2)
+	require.Len(t, compiler.locationStack.usedRegisters, 2)
 
 	// Set the values supposed to be released to stack memory space.
-	builder.movIntConstToRegister(300, x1Reg)
-	builder.movIntConstToRegister(51, x2Reg)
-	builder.releaseAllRegistersToStack()
-	require.Len(t, builder.locationStack.usedRegisters, 0)
-	builder.returnFunction()
+	compiler.movIntConstToRegister(300, x1Reg)
+	compiler.movIntConstToRegister(51, x2Reg)
+	compiler.releaseAllRegistersToStack()
+	require.Len(t, compiler.locationStack.usedRegisters, 0)
+	compiler.returnFunction()
 
 	// Assemble.
-	code, err := builder.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 	// Run code.
 	mem := newMemoryInst()
@@ -2324,32 +2324,32 @@ func TestAmd64Builder_releaseAllRegistersToStack(t *testing.T) {
 }
 
 func TestAmd64Builder_assemble(t *testing.T) {
-	builder := requireNewBuilder(t)
-	builder.setContinuationOffsetAtNextInstructionAndReturn()
-	prog := builder.newProg()
+	compiler := requireNewCompiler(t)
+	compiler.setContinuationOffsetAtNextInstructionAndReturn()
+	prog := compiler.newProg()
 	prog.As = x86.AINCQ
 	prog.To.Type = obj.TYPE_REG
 	prog.To.Reg = x86.REG_R10
-	builder.addInstruction(prog)
-	code, err := builder.compile()
+	compiler.addInstruction(prog)
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 	actual := binary.LittleEndian.Uint64(code[2:10])
 	require.Equal(t, uint64(prog.Pc), actual)
 }
 
 func TestAmd64Builder_handleUnreachable(t *testing.T) {
-	builder := requireNewBuilder(t)
-	builder.initializeReservedRegisters()
+	compiler := requireNewCompiler(t)
+	compiler.initializeReservedRegisters()
 	x1Reg := int16(x86.REG_AX)
 	x2Reg := int16(x86.REG_R10)
-	builder.locationStack.pushValueOnRegister(x1Reg)
-	builder.locationStack.pushValueOnRegister(x2Reg)
-	builder.movIntConstToRegister(300, x1Reg)
-	builder.movIntConstToRegister(51, x2Reg)
-	builder.handleUnreachable()
+	compiler.locationStack.pushValueOnRegister(x1Reg)
+	compiler.locationStack.pushValueOnRegister(x2Reg)
+	compiler.movIntConstToRegister(300, x1Reg)
+	compiler.movIntConstToRegister(51, x2Reg)
+	compiler.compileUnreachable()
 
 	// Assemble.
-	code, err := builder.compile()
+	code, _, err := compiler.compile()
 	require.NoError(t, err)
 	// Run code.
 	eng := newEngine()
@@ -2415,41 +2415,41 @@ func TestAmd64Builder_handleSelect(t *testing.T) {
 		{x1OnRegister: false, x2OnRegister: false, selectX1: false, condValueOnCondRegister: true},
 	} {
 		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
-			builder := requireNewBuilder(t)
+			compiler := requireNewCompiler(t)
 			eng := newEngine()
-			builder.initializeReservedRegisters()
+			compiler.initializeReservedRegisters()
 			var x1, x2, c *valueLocation
 			if tc.x1OnRegister {
-				x1 = builder.locationStack.pushValueOnRegister(x86.REG_AX)
-				builder.movIntConstToRegister(x1Value, x1.register)
+				x1 = compiler.locationStack.pushValueOnRegister(x86.REG_AX)
+				compiler.movIntConstToRegister(x1Value, x1.register)
 			} else {
-				x1 = builder.locationStack.pushValueOnStack()
+				x1 = compiler.locationStack.pushValueOnStack()
 				eng.stack[x1.stackPointer] = x1Value
 			}
 			if tc.x2OnRegister {
-				x2 = builder.locationStack.pushValueOnRegister(x86.REG_R10)
-				builder.movIntConstToRegister(x2Value, x2.register)
+				x2 = compiler.locationStack.pushValueOnRegister(x86.REG_R10)
+				compiler.movIntConstToRegister(x2Value, x2.register)
 			} else {
-				x2 = builder.locationStack.pushValueOnStack()
+				x2 = compiler.locationStack.pushValueOnStack()
 				eng.stack[x2.stackPointer] = x2Value
 			}
 			if tc.condlValueOnStack {
-				c = builder.locationStack.pushValueOnStack()
+				c = compiler.locationStack.pushValueOnStack()
 				if tc.selectX1 {
 					eng.stack[c.stackPointer] = 1
 				} else {
 					eng.stack[c.stackPointer] = 0
 				}
 			} else if tc.condValueOnGPRegister {
-				c = builder.locationStack.pushValueOnRegister(x86.REG_R9)
+				c = compiler.locationStack.pushValueOnRegister(x86.REG_R9)
 				if tc.selectX1 {
-					builder.movIntConstToRegister(1, c.register)
+					compiler.movIntConstToRegister(1, c.register)
 				} else {
-					builder.movIntConstToRegister(0, c.register)
+					compiler.movIntConstToRegister(0, c.register)
 				}
 			} else if tc.condValueOnCondRegister {
-				builder.movIntConstToRegister(0, x86.REG_CX)
-				cmp := builder.newProg()
+				compiler.movIntConstToRegister(0, x86.REG_CX)
+				cmp := compiler.newProg()
 				cmp.As = x86.ACMPQ
 				cmp.From.Type = obj.TYPE_REG
 				cmp.From.Reg = x86.REG_CX
@@ -2459,26 +2459,26 @@ func TestAmd64Builder_handleSelect(t *testing.T) {
 				} else {
 					cmp.To.Offset = 1
 				}
-				builder.addInstruction(cmp)
-				builder.locationStack.pushValueOnConditionalRegister(conditionalRegisterStateE)
+				compiler.addInstruction(cmp)
+				compiler.locationStack.pushValueOnConditionalRegister(conditionalRegisterStateE)
 			}
 
 			// Now emit code for select.
-			err := builder.handleSelect()
+			err := compiler.compileSelect()
 			require.NoError(t, err)
 			// The code generation should not affect the x1's placement in any case.
 			require.Equal(t, tc.x1OnRegister, x1.onRegister())
 			// Plus x1 is top of the stack.
-			require.Equal(t, x1, builder.locationStack.peek())
+			require.Equal(t, x1, compiler.locationStack.peek())
 
 			// Now write back the x1 to the memory if it is on a register.
 			if tc.x1OnRegister {
-				builder.releaseRegisterToStack(x1)
+				compiler.releaseRegisterToStack(x1)
 			}
-			builder.returnFunction()
+			compiler.returnFunction()
 
 			// Run code.
-			code, err := builder.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			mem := newMemoryInst()
 			jitcall(
@@ -2513,45 +2513,45 @@ func TestAmd64Builder_handleSwap(t *testing.T) {
 	} {
 		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
 			eng := newEngine()
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
+			compiler := requireNewCompiler(t)
+			compiler.initializeReservedRegisters()
 			if tc.x2OnRegister {
-				x2 := builder.locationStack.pushValueOnRegister(x86.REG_R10)
-				builder.movIntConstToRegister(x2Value, x2.register)
+				x2 := compiler.locationStack.pushValueOnRegister(x86.REG_R10)
+				compiler.movIntConstToRegister(x2Value, x2.register)
 			} else {
-				x2 := builder.locationStack.pushValueOnStack()
+				x2 := compiler.locationStack.pushValueOnStack()
 				eng.stack[x2.stackPointer] = uint64(x2Value)
 			}
-			_ = builder.locationStack.pushValueOnStack() // Dummy value!
+			_ = compiler.locationStack.pushValueOnStack() // Dummy value!
 			if tc.x1OnRegister && !tc.x1OnConditionalRegister {
-				x1 := builder.locationStack.pushValueOnRegister(x86.REG_AX)
-				builder.movIntConstToRegister(x1Value, x1.register)
+				x1 := compiler.locationStack.pushValueOnRegister(x86.REG_AX)
+				compiler.movIntConstToRegister(x1Value, x1.register)
 			} else if !tc.x1OnConditionalRegister {
-				x1 := builder.locationStack.pushValueOnStack()
+				x1 := compiler.locationStack.pushValueOnStack()
 				eng.stack[x1.stackPointer] = uint64(x1Value)
 			} else {
-				builder.movIntConstToRegister(0, x86.REG_AX)
-				cmp := builder.newProg()
+				compiler.movIntConstToRegister(0, x86.REG_AX)
+				cmp := compiler.newProg()
 				cmp.As = x86.ACMPQ
 				cmp.From.Type = obj.TYPE_REG
 				cmp.From.Reg = x86.REG_AX
 				cmp.To.Type = obj.TYPE_CONST
 				cmp.To.Offset = 0
 				cmp.To.Offset = 0
-				builder.addInstruction(cmp)
-				builder.locationStack.pushValueOnConditionalRegister(conditionalRegisterStateE)
+				compiler.addInstruction(cmp)
+				compiler.locationStack.pushValueOnConditionalRegister(conditionalRegisterStateE)
 				x1Value = 1
 			}
 
 			// Swap x1 and x2.
-			err := builder.handleSwap(&wazeroir.OperationSwap{Depth: 2})
+			err := compiler.compileSwap(&wazeroir.OperationSwap{Depth: 2})
 			require.NoError(t, err)
 			// To verify the behavior, we release all the registers to stack locations.
-			builder.releaseAllRegistersToStack()
-			builder.returnFunction()
+			compiler.releaseAllRegistersToStack()
+			compiler.returnFunction()
 
 			// Assemble.
-			code, err := builder.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			// Run code.
 			mem := newMemoryInst()
@@ -2581,30 +2581,30 @@ func TestAmd64Builder_handleGlobalGet(t *testing.T) {
 		tp := tp
 		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
 			// Setup the globals.
-			builder := requireNewBuilder(t)
+			compiler := requireNewCompiler(t)
 			globals := []*wasm.GlobalInstance{nil, {Val: globalValue, Type: &wasm.GlobalType{ValType: tp}}, nil}
-			builder.f = &wasm.FunctionInstance{ModuleInstance: &wasm.ModuleInstance{Globals: globals}}
+			compiler.f = &wasm.FunctionInstance{ModuleInstance: &wasm.ModuleInstance{Globals: globals}}
 			// Emit the code.
-			builder.initializeReservedRegisters()
+			compiler.initializeReservedRegisters()
 			op := &wazeroir.OperationGlobalGet{Index: 1}
-			err := builder.handleGlobalGet(op)
+			err := compiler.compileGlobalGet(op)
 			require.NoError(t, err)
 
 			// At this point, the top of stack must be the retrieved global on a register.
-			global := builder.locationStack.peek()
+			global := compiler.locationStack.peek()
 			require.True(t, global.onRegister())
-			require.Len(t, builder.locationStack.usedRegisters, 1)
+			require.Len(t, compiler.locationStack.usedRegisters, 1)
 			switch tp {
 			case wasm.ValueTypeF32, wasm.ValueTypeF64:
 				require.True(t, isFloatRegister(global.register))
 			case wasm.ValueTypeI32, wasm.ValueTypeI64:
 				require.True(t, isIntRegister(global.register))
 			}
-			builder.releaseAllRegistersToStack()
-			builder.returnFunction()
+			compiler.releaseAllRegistersToStack()
+			compiler.returnFunction()
 
 			// Assemble.
-			code, err := builder.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 
 			// Run the code assembled above.
@@ -2632,19 +2632,19 @@ func TestAmd64Builder_handleGlobalSet(t *testing.T) {
 		tp := tp
 		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
 			// Setup the globals.
-			builder := requireNewBuilder(t)
+			compiler := requireNewCompiler(t)
 			globals := []*wasm.GlobalInstance{nil, {Val: 40, Type: &wasm.GlobalType{ValType: tp}}, nil}
-			builder.f = &wasm.FunctionInstance{ModuleInstance: &wasm.ModuleInstance{Globals: globals}}
-			_ = builder.locationStack.pushValueOnStack() // where we place the set target value below.
+			compiler.f = &wasm.FunctionInstance{ModuleInstance: &wasm.ModuleInstance{Globals: globals}}
+			_ = compiler.locationStack.pushValueOnStack() // where we place the set target value below.
 			// Now emit the code.
-			builder.initializeReservedRegisters()
+			compiler.initializeReservedRegisters()
 			op := &wazeroir.OperationGlobalSet{Index: 1}
-			err := builder.handleGlobalSet(op)
+			err := compiler.compileGlobalSet(op)
 			require.NoError(t, err)
-			builder.returnFunction()
+			compiler.returnFunction()
 
 			// Assemble.
-			code, err := builder.compile()
+			code, _, err := compiler.compile()
 			require.NoError(t, err)
 			// Run code.
 			eng := newEngine()