Repository: tetratelabs/wazero
Pull request #403 (2022-03-22 04:30:00)

asm: add skeleton amd64 assembler 
This adds the skeleton implementation of amd64 assembler.
The assembler is not able to produce binary at all, and the actual
encoding implementations follow in subsequent commits.

Part of #233 


Patch:
diff --git a/.github/workflows/commit.yaml b/.github/workflows/commit.yaml
index 534ef95f..fa9cec40 100644
--- a/.github/workflows/commit.yaml
+++ b/.github/workflows/commit.yaml
@@ -99,7 +99,7 @@ jobs:
           key: test-${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}-${{ matrix.arch }}
 
       - name: Build test binaries
-        run: go list ./... | xargs -Ipkg go test pkg -c
+        run: go list -f '{{.Dir}}' ./... | xargs -Ipkg go test pkg -c -o pkg.test
         env:
           GOARCH: ${{ matrix.arch }}
           CGO_ENABLED: 0
diff --git a/internal/wasm/jit/asm/amd64/assembler.go b/internal/wasm/jit/asm/amd64/assembler.go
index d711cb9e..a7be6f49 100644
--- a/internal/wasm/jit/asm/amd64/assembler.go
+++ b/internal/wasm/jit/asm/amd64/assembler.go
@@ -10,35 +10,38 @@ func NewAssembler() (Assembler, error) {
 
 type Assembler interface {
 	asm.AssemblerBase
-	// CompileConstModeRegisterToRegister adds an instruction where source and destination
+	// CompileRegisterToRegisterWithMode adds an instruction where source and destination
 	// are `from` and `to` registers and the instruction's "mode" is specified by `mode`.
-	// For example, ROUND** instructions can be modified "mode" constant.
-	// See https://www.felixcloutier.com/x86/roundss for ROUNDSS as an example.
-	CompileConstModeRegisterToRegister(inst asm.Instruction, from, to asm.Register, mode int64)
+	CompileRegisterToRegisterWithMode(instruction asm.Instruction, from, to asm.Register, mode Mode)
 	// CompileMemoryWithIndexToRegister adds an instruction where source operand is the memory address
 	// specified as `srcBaseReg + srcOffsetConst + srcIndex*srcScale` and destination is the register `dstReg`.
 	// Note: sourceScale must be one of 1, 2, 4, 8.
-	CompileMemoryWithIndexToRegister(inst asm.Instruction, srcBaseReg asm.Register, srcOffsetConst int64, srcIndex asm.Register, srcScale int16, dstReg asm.Register)
+	CompileMemoryWithIndexToRegister(instruction asm.Instruction, srcBaseReg asm.Register, srcOffsetConst int64, srcIndex asm.Register, srcScale int16, dstReg asm.Register)
 	// CompileRegisterToMemoryWithIndex adds an instruction where source operand is the register `srcReg`,
 	// and the destination is the memory address specified as `dstBaseReg + dstOffsetConst + dstIndex*dstScale`
 	// Note: dstScale must be one of 1, 2, 4, 8.
-	CompileRegisterToMemoryWithIndex(inst asm.Instruction, srcReg asm.Register, dstBaseReg asm.Register, dstOffsetConst int64, dstIndex asm.Register, dstScale int16)
+	CompileRegisterToMemoryWithIndex(instruction asm.Instruction, srcReg asm.Register, dstBaseReg asm.Register, dstOffsetConst int64, dstIndex asm.Register, dstScale int16)
 	// CompileRegisterToConst adds an instruction where source operand is the register `srcRegister`,
 	// and the destination is the const `value`.
-	CompileRegisterToConst(inst asm.Instruction, srcRegister asm.Register, value int64) asm.Node
+	CompileRegisterToConst(instruction asm.Instruction, srcRegister asm.Register, value int64) asm.Node
 	// CompileRegisterToNone adds an instruction where source operand is the register `register`,
 	// and there's no destination operand.
-	CompileRegisterToNone(inst asm.Instruction, register asm.Register)
+	CompileRegisterToNone(instruction asm.Instruction, register asm.Register)
 	// CompileRegisterToNone adds an instruction where destination operand is the register `register`,
 	// and there's no source operand.
-	CompileNoneToRegister(inst asm.Instruction, register asm.Register)
+	CompileNoneToRegister(instruction asm.Instruction, register asm.Register)
 	// CompileRegisterToNone adds an instruction where destination operand is the memory address specified
 	// as `baseReg+offset`. and there's no source operand.
-	CompileNoneToMemory(inst asm.Instruction, baseReg asm.Register, offset int64)
+	CompileNoneToMemory(instruction asm.Instruction, baseReg asm.Register, offset int64)
 	// CompileConstToMemory adds an instruction where source operand is the constant `value` and
 	// the destination is the memory address sppecified as `dstbaseReg+dstOffset`.
-	CompileConstToMemory(inst asm.Instruction, value int64, dstbaseReg asm.Register, dstOffset int64) asm.Node
+	CompileConstToMemory(instruction asm.Instruction, value int64, dstbaseReg asm.Register, dstOffset int64) asm.Node
 	// CompileMemoryToConst adds an instruction where source operand is the memory address, and
 	// the destination is the constant `value`.
-	CompileMemoryToConst(inst asm.Instruction, srcBaseReg asm.Register, srcOffset int64, value int64) asm.Node
+	CompileMemoryToConst(instruction asm.Instruction, srcBaseReg asm.Register, srcOffset int64, value int64) asm.Node
 }
+
+// Mode represents a mode for specific instruction.
+// For example, ROUND** instructions' behavior can be modified "mode" constant.
+// See https://www.felixcloutier.com/x86/roundss for ROUNDSS as an example.
+type Mode = byte
diff --git a/internal/wasm/jit/asm/amd64/consts.go b/internal/wasm/jit/asm/amd64/consts.go
index 04ff3589..d4fced36 100644
--- a/internal/wasm/jit/asm/amd64/consts.go
+++ b/internal/wasm/jit/asm/amd64/consts.go
@@ -2,7 +2,9 @@ package amd64
 
 import "github.com/tetratelabs/wazero/internal/wasm/jit/asm"
 
-// Arm64-specific register states.
+// AMD64-specific conditional register states.
+// https://www.lri.fr/~filliatr/ens/compil/x86-64.pdf
+// https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf
 const (
 	ConditionalRegisterStateE  = asm.ConditionalRegisterStateUnset + 1 + iota // ZF equal to zero
 	ConditionalRegisterStateNE                                                //˜ZF not equal to zero
@@ -18,9 +20,13 @@ const (
 	ConditionalRegisterStateBE                                                // CF | ZF below or equal (unsigned <=)
 )
 
-// Arm64-specific instructions.
+// AMD64-specific instructions.
+// https://www.felixcloutier.com/x86/index.html
+//
+// Note: here we do not define all of amd64 instructions, and we only define the ones used by wazero's JIT compiler.
+// Note: naming convension is exactly the same as Go assembler: https://go.dev/doc/asm
 const (
-	ADDL = iota
+	ADDL asm.Instruction = iota
 	ADDQ
 	ADDSD
 	ADDSS
@@ -112,7 +118,6 @@ const (
 	ROUNDSS
 	SARL
 	SARQ
-	SET
 	SETCC
 	SETCS
 	SETEQ
@@ -153,7 +158,277 @@ const (
 	UD2
 )
 
+func instructionName(instruction asm.Instruction) string {
+	switch instruction {
+	case ADDL:
+		return "ADDL"
+	case ADDQ:
+		return "ADDQ"
+	case ADDSD:
+		return "ADDSD"
+	case ADDSS:
+		return "ADDSS"
+	case ANDL:
+		return "ANDL"
+	case ANDPD:
+		return "ANDPD"
+	case ANDPS:
+		return "ANDPS"
+	case ANDQ:
+		return "ANDQ"
+	case BSRL:
+		return "BSRL"
+	case BSRQ:
+		return "BSRQ"
+	case CDQ:
+		return "CDQ"
+	case CMOVQCS:
+		return "CMOVQCS"
+	case CMPL:
+		return "CMPL"
+	case CMPQ:
+		return "CMPQ"
+	case COMISD:
+		return "COMISD"
+	case COMISS:
+		return "COMISS"
+	case CQO:
+		return "CQO"
+	case CVTSD2SS:
+		return "CVTSD2SS"
+	case CVTSL2SD:
+		return "CVTSL2SD"
+	case CVTSL2SS:
+		return "CVTSL2SS"
+	case CVTSQ2SD:
+		return "CVTSQ2SD"
+	case CVTSQ2SS:
+		return "CVTSQ2SS"
+	case CVTSS2SD:
+		return "CVTSS2SD"
+	case CVTTSD2SL:
+		return "CVTTSD2SL"
+	case CVTTSD2SQ:
+		return "CVTTSD2SQ"
+	case CVTTSS2SL:
+		return "CVTTSS2SL"
+	case CVTTSS2SQ:
+		return "CVTTSS2SQ"
+	case DECQ:
+		return "DECQ"
+	case DIVL:
+		return "DIVL"
+	case DIVQ:
+		return "DIVQ"
+	case DIVSD:
+		return "DIVSD"
+	case DIVSS:
+		return "DIVSS"
+	case IDIVL:
+		return "IDIVL"
+	case IDIVQ:
+		return "IDIVQ"
+	case INCQ:
+		return "INCQ"
+	case JCC:
+		return "JCC"
+	case JCS:
+		return "JCS"
+	case JEQ:
+		return "JEQ"
+	case JGE:
+		return "JGE"
+	case JGT:
+		return "JGT"
+	case JHI:
+		return "JHI"
+	case JLE:
+		return "JLE"
+	case JLS:
+		return "JLS"
+	case JLT:
+		return "JLT"
+	case JMI:
+		return "JMI"
+	case JNE:
+		return "JNE"
+	case JPC:
+		return "JPC"
+	case JPL:
+		return "JPL"
+	case JPS:
+		return "JPS"
+	case LEAQ:
+		return "LEAQ"
+	case LZCNTL:
+		return "LZCNTL"
+	case LZCNTQ:
+		return "LZCNTQ"
+	case MAXSD:
+		return "MAXSD"
+	case MAXSS:
+		return "MAXSS"
+	case MINSD:
+		return "MINSD"
+	case MINSS:
+		return "MINSS"
+	case MOVB:
+		return "MOVB"
+	case MOVBLSX:
+		return "MOVBLSX"
+	case MOVBLZX:
+		return "MOVBLZX"
+	case MOVBQSX:
+		return "MOVBQSX"
+	case MOVBQZX:
+		return "MOVBQZX"
+	case MOVL:
+		return "MOVL"
+	case MOVLQSX:
+		return "MOVLQSX"
+	case MOVLQZX:
+		return "MOVLQZX"
+	case MOVQ:
+		return "MOVQ"
+	case MOVW:
+		return "MOVW"
+	case MOVWLSX:
+		return "MOVWLSX"
+	case MOVWLZX:
+		return "MOVWLZX"
+	case MOVWQSX:
+		return "MOVWQSX"
+	case MOVWQZX:
+		return "MOVWQZX"
+	case MULL:
+		return "MULL"
+	case MULQ:
+		return "MULQ"
+	case MULSD:
+		return "MULSD"
+	case MULSS:
+		return "MULSS"
+	case ORL:
+		return "ORL"
+	case ORPD:
+		return "ORPD"
+	case ORPS:
+		return "ORPS"
+	case ORQ:
+		return "ORQ"
+	case POPCNTL:
+		return "POPCNTL"
+	case POPCNTQ:
+		return "POPCNTQ"
+	case PSLLL:
+		return "PSLLL"
+	case PSLLQ:
+		return "PSLLQ"
+	case PSRLL:
+		return "PSRLL"
+	case PSRLQ:
+		return "PSRLQ"
+	case ROLL:
+		return "ROLL"
+	case ROLQ:
+		return "ROLQ"
+	case RORL:
+		return "RORL"
+	case RORQ:
+		return "RORQ"
+	case ROUNDSD:
+		return "ROUNDSD"
+	case ROUNDSS:
+		return "ROUNDSS"
+	case SARL:
+		return "SARL"
+	case SARQ:
+		return "SARQ"
+	case SETCC:
+		return "SETCC"
+	case SETCS:
+		return "SETCS"
+	case SETEQ:
+		return "SETEQ"
+	case SETGE:
+		return "SETGE"
+	case SETGT:
+		return "SETGT"
+	case SETHI:
+		return "SETHI"
+	case SETLE:
+		return "SETLE"
+	case SETLS:
+		return "SETLS"
+	case SETLT:
+		return "SETLT"
+	case SETMI:
+		return "SETMI"
+	case SETNE:
+		return "SETNE"
+	case SETPC:
+		return "SETPC"
+	case SETPL:
+		return "SETPL"
+	case SETPS:
+		return "SETPS"
+	case SHLL:
+		return "SHLL"
+	case SHLQ:
+		return "SHLQ"
+	case SHRL:
+		return "SHRL"
+	case SHRQ:
+		return "SHRQ"
+	case SQRTSD:
+		return "SQRTSD"
+	case SQRTSS:
+		return "SQRTSS"
+	case SUBL:
+		return "SUBL"
+	case SUBQ:
+		return "SUBQ"
+	case SUBSD:
+		return "SUBSD"
+	case SUBSS:
+		return "SUBSS"
+	case TESTL:
+		return "TESTL"
+	case TESTQ:
+		return "TESTQ"
+	case TZCNTL:
+		return "TZCNTL"
+	case TZCNTQ:
+		return "TZCNTQ"
+	case UCOMISD:
+		return "UCOMISD"
+	case UCOMISS:
+		return "UCOMISS"
+	case XORL:
+		return "XORL"
+	case XORPD:
+		return "XORPD"
+	case XORPS:
+		return "XORPS"
+	case XORQ:
+		return "XORQ"
+	case RET:
+		return "RET"
+	case JMP:
+		return "JMP"
+	case NOP:
+		return "NOP"
+	case UD2:
+		return "UD2"
+	}
+	return "Unknown"
+}
+
 // Arm64-specific registers.
+// https://www.lri.fr/~filliatr/ens/compil/x86-64.pdf
+// https://cs.brown.edu/courses/cs033/docs/guides/x64_cheatsheet.pdf
+//
+// Note: naming convension is exactly the same as Go assembler: https://go.dev/doc/asm
 const (
 	REG_AX asm.Register = asm.NilRegister + 1 + iota
 	REG_CX
@@ -204,3 +479,105 @@ const (
 	REG_X30
 	REG_X31
 )
+
+func registerName(reg asm.Register) string {
+	switch reg {
+	case REG_AX:
+		return "AX"
+	case REG_CX:
+		return "CX"
+	case REG_DX:
+		return "DX"
+	case REG_BX:
+		return "BX"
+	case REG_SP:
+		return "SP"
+	case REG_BP:
+		return "BP"
+	case REG_SI:
+		return "SI"
+	case REG_DI:
+		return "DI"
+	case REG_R8:
+		return "R8"
+	case REG_R9:
+		return "R9"
+	case REG_R10:
+		return "R10"
+	case REG_R11:
+		return "R11"
+	case REG_R12:
+		return "R12"
+	case REG_R13:
+		return "R13"
+	case REG_R14:
+		return "R14"
+	case REG_R15:
+		return "R15"
+	case REG_X0:
+		return "X0"
+	case REG_X1:
+		return "X1"
+	case REG_X2:
+		return "X2"
+	case REG_X3:
+		return "X3"
+	case REG_X4:
+		return "X4"
+	case REG_X5:
+		return "X5"
+	case REG_X6:
+		return "X6"
+	case REG_X7:
+		return "X7"
+	case REG_X8:
+		return "X8"
+	case REG_X9:
+		return "X9"
+	case REG_X10:
+		return "X10"
+	case REG_X11:
+		return "X11"
+	case REG_X12:
+		return "X12"
+	case REG_X13:
+		return "X13"
+	case REG_X14:
+		return "X14"
+	case REG_X15:
+		return "X15"
+	case REG_X16:
+		return "X16"
+	case REG_X17:
+		return "X17"
+	case REG_X18:
+		return "X18"
+	case REG_X19:
+		return "X19"
+	case REG_X20:
+		return "X20"
+	case REG_X21:
+		return "X21"
+	case REG_X22:
+		return "X22"
+	case REG_X23:
+		return "X23"
+	case REG_X24:
+		return "X24"
+	case REG_X25:
+		return "X25"
+	case REG_X26:
+		return "X26"
+	case REG_X27:
+		return "X27"
+	case REG_X28:
+		return "X28"
+	case REG_X29:
+		return "X29"
+	case REG_X30:
+		return "X30"
+	case REG_X31:
+		return "X31"
+	}
+	return "Unknown"
+}
diff --git a/internal/wasm/jit/asm/amd64/golang_asm.go b/internal/wasm/jit/asm/amd64/golang_asm.go
index c4de3f48..678f2184 100644
--- a/internal/wasm/jit/asm/amd64/golang_asm.go
+++ b/internal/wasm/jit/asm/amd64/golang_asm.go
@@ -41,7 +41,7 @@ func (a *assemblerGoAsmImpl) CompileRegisterToRegister(inst asm.Instruction, fro
 
 // CompileMemoryWithIndexToRegister implements Assembler.CompileMemoryWithIndexToRegister.
 func (a *assemblerGoAsmImpl) CompileMemoryWithIndexToRegister(inst asm.Instruction,
-	sourceBaseReg asm.Register, sourceOffsetConst int64, sourceIndexReg asm.Register, sourceScale int16, destinationReg asm.Register) {
+	sourceBaseReg asm.Register, sourceOffsetConst asm.ConstantValue, sourceIndexReg asm.Register, sourceScale int16, destinationReg asm.Register) {
 	p := a.NewProg()
 	p.As = castAsGolangAsmInstruction[inst]
 	p.To.Type = obj.TYPE_REG
@@ -55,7 +55,7 @@ func (a *assemblerGoAsmImpl) CompileMemoryWithIndexToRegister(inst asm.Instructi
 }
 
 // CompileRegisterToMemoryWithIndex implements Assembler.CompileRegisterToMemoryWithIndex.
-func (a *assemblerGoAsmImpl) CompileRegisterToMemoryWithIndex(inst asm.Instruction, srcReg asm.Register, dstBaseReg asm.Register, dstOffsetConst int64, dstIndexReg asm.Register, dstScale int16) {
+func (a *assemblerGoAsmImpl) CompileRegisterToMemoryWithIndex(inst asm.Instruction, srcReg asm.Register, dstBaseReg asm.Register, dstOffsetConst asm.ConstantValue, dstIndexReg asm.Register, dstScale int16) {
 	p := a.NewProg()
 	p.As = castAsGolangAsmInstruction[inst]
 	p.From.Type = obj.TYPE_REG
@@ -69,7 +69,7 @@ func (a *assemblerGoAsmImpl) CompileRegisterToMemoryWithIndex(inst asm.Instructi
 }
 
 // CompileRegisterToMemory implements Assembler.CompileRegisterToMemory.
-func (a *assemblerGoAsmImpl) CompileRegisterToMemory(inst asm.Instruction, sourceRegister asm.Register, destinationBaseRegister asm.Register, destinationOffsetConst int64) {
+func (a *assemblerGoAsmImpl) CompileRegisterToMemory(inst asm.Instruction, sourceRegister asm.Register, destinationBaseRegister asm.Register, destinationOffsetConst asm.ConstantValue) {
 	p := a.NewProg()
 	p.As = castAsGolangAsmInstruction[inst]
 	p.To.Type = obj.TYPE_MEM
@@ -81,7 +81,7 @@ func (a *assemblerGoAsmImpl) CompileRegisterToMemory(inst asm.Instruction, sourc
 }
 
 // CompileConstToRegister implements Assembler.CompileConstToRegister.
-func (a *assemblerGoAsmImpl) CompileConstToRegister(inst asm.Instruction, constValue int64, destinationRegister asm.Register) asm.Node {
+func (a *assemblerGoAsmImpl) CompileConstToRegister(inst asm.Instruction, constValue asm.ConstantValue, destinationRegister asm.Register) asm.Node {
 	p := a.NewProg()
 	p.As = castAsGolangAsmInstruction[inst]
 	p.From.Type = obj.TYPE_CONST
@@ -93,7 +93,7 @@ func (a *assemblerGoAsmImpl) CompileConstToRegister(inst asm.Instruction, constV
 }
 
 // CompileRegisterToConst implements Assembler.CompileRegisterToConst.
-func (a *assemblerGoAsmImpl) CompileRegisterToConst(inst asm.Instruction, srcRegister asm.Register, constValue int64) asm.Node {
+func (a *assemblerGoAsmImpl) CompileRegisterToConst(inst asm.Instruction, srcRegister asm.Register, constValue asm.ConstantValue) asm.Node {
 	p := a.NewProg()
 	p.As = castAsGolangAsmInstruction[inst]
 	p.To.Type = obj.TYPE_CONST
@@ -125,7 +125,7 @@ func (a *assemblerGoAsmImpl) CompileNoneToRegister(inst asm.Instruction, registe
 }
 
 // CompileNoneToMemory implements Assembler.CompileNoneToMemory.
-func (a *assemblerGoAsmImpl) CompileNoneToMemory(inst asm.Instruction, baseReg asm.Register, offset int64) {
+func (a *assemblerGoAsmImpl) CompileNoneToMemory(inst asm.Instruction, baseReg asm.Register, offset asm.ConstantValue) {
 	p := a.NewProg()
 	p.As = castAsGolangAsmInstruction[inst]
 	p.To.Type = obj.TYPE_MEM
@@ -136,7 +136,7 @@ func (a *assemblerGoAsmImpl) CompileNoneToMemory(inst asm.Instruction, baseReg a
 }
 
 // CompileConstToMemory implements Assembler.CompileConstToMemory.
-func (a *assemblerGoAsmImpl) CompileConstToMemory(inst asm.Instruction, constValue int64, baseReg asm.Register, offset int64) asm.Node {
+func (a *assemblerGoAsmImpl) CompileConstToMemory(inst asm.Instruction, constValue asm.ConstantValue, baseReg asm.Register, offset asm.ConstantValue) asm.Node {
 	p := a.NewProg()
 	p.As = castAsGolangAsmInstruction[inst]
 	p.From.Type = obj.TYPE_CONST
@@ -149,7 +149,7 @@ func (a *assemblerGoAsmImpl) CompileConstToMemory(inst asm.Instruction, constVal
 }
 
 // CompileMemoryToRegister implements AssemblerBase.CompileMemoryToRegister.
-func (a *assemblerGoAsmImpl) CompileMemoryToRegister(inst asm.Instruction, sourceBaseReg asm.Register, sourceOffsetConst int64, destinationReg asm.Register) {
+func (a *assemblerGoAsmImpl) CompileMemoryToRegister(inst asm.Instruction, sourceBaseReg asm.Register, sourceOffsetConst asm.ConstantValue, destinationReg asm.Register) {
 	p := a.NewProg()
 	p.As = castAsGolangAsmInstruction[inst]
 	p.From.Type = obj.TYPE_MEM
@@ -161,7 +161,7 @@ func (a *assemblerGoAsmImpl) CompileMemoryToRegister(inst asm.Instruction, sourc
 }
 
 // CompileMemoryToConst implements Assembler.CompileMemoryToConst.
-func (a *assemblerGoAsmImpl) CompileMemoryToConst(inst asm.Instruction, baseReg asm.Register, offset int64, constValue int64) asm.Node {
+func (a *assemblerGoAsmImpl) CompileMemoryToConst(inst asm.Instruction, baseReg asm.Register, offset asm.ConstantValue, constValue asm.ConstantValue) asm.Node {
 	p := a.NewProg()
 	p.As = castAsGolangAsmInstruction[inst]
 	p.To.Type = obj.TYPE_CONST
@@ -173,11 +173,6 @@ func (a *assemblerGoAsmImpl) CompileMemoryToConst(inst asm.Instruction, baseReg
 	return asm.NewGolangAsmNode(p)
 }
 
-// CompileUnconditionalJump implements Assembler.CompileUnconditionalJump.
-func (a *assemblerGoAsmImpl) CompileUnconditionalJump() asm.Node {
-	return a.CompileJump(JMP)
-}
-
 // CompileJump implements Assembler.CompileJump.
 func (a *assemblerGoAsmImpl) CompileJump(jmpInstruction asm.Instruction) asm.Node {
 	p := a.NewProg()
@@ -197,7 +192,7 @@ func (a *assemblerGoAsmImpl) CompileJumpToRegister(jmpInstruction asm.Instructio
 }
 
 // CompileJumpToMemory implements Assembler.CompileJumpToMemory.
-func (a *assemblerGoAsmImpl) CompileJumpToMemory(jmpInstruction asm.Instruction, baseReg asm.Register, offset int64) {
+func (a *assemblerGoAsmImpl) CompileJumpToMemory(jmpInstruction asm.Instruction, baseReg asm.Register, offset asm.ConstantValue) {
 	p := a.NewProg()
 	p.As = castAsGolangAsmInstruction[jmpInstruction]
 	p.To.Type = obj.TYPE_MEM
@@ -206,12 +201,12 @@ func (a *assemblerGoAsmImpl) CompileJumpToMemory(jmpInstruction asm.Instruction,
 	a.AddInstruction(p)
 }
 
-// CompileConstModeRegisterToRegister implements Assembler.CompileConstModeRegisterToRegister.
-func (a *assemblerGoAsmImpl) CompileConstModeRegisterToRegister(inst asm.Instruction, from, to asm.Register, mode int64) {
+// CompileRegisterToRegisterWithMode implements Assembler.CompileRegisterToRegisterWithMode.
+func (a *assemblerGoAsmImpl) CompileRegisterToRegisterWithMode(inst asm.Instruction, from, to asm.Register, mode Mode) {
 	p := a.NewProg()
 	p.As = castAsGolangAsmInstruction[inst]
 	p.From.Type = obj.TYPE_CONST
-	p.From.Offset = mode
+	p.From.Offset = int64(mode)
 	p.To.Type = obj.TYPE_REG
 	p.To.Reg = castAsGolangAsmRegister[to]
 	p.RestArgs = append(p.RestArgs,
diff --git a/internal/wasm/jit/asm/amd64/impl.go b/internal/wasm/jit/asm/amd64/impl.go
new file mode 100644
index 00000000..3d742809
--- /dev/null
+++ b/internal/wasm/jit/asm/amd64/impl.go
@@ -0,0 +1,411 @@
+package amd64
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"io"
+
+	"github.com/tetratelabs/wazero/internal/wasm/jit/asm"
+)
+
+// nodeImpl implements asm.Node for amd64.
+type nodeImpl struct {
+	instruction asm.Instruction
+
+	offsetInBinary asm.NodeOffsetInBinary
+	// jumpTarget holds the target node in the linked for the jump-kind instruction.
+	jumpTarget *nodeImpl
+	// prev holds the previous node to this node in the assembled linked list.
+	prev *nodeImpl
+	// next holds the next node from this node in the assembled linked list.
+	next *nodeImpl
+
+	types                    operandTypes
+	srcReg, dstReg           asm.Register
+	srcConst, dstConst       asm.ConstantValue
+	srcMemIndex, dstMemIndex asm.Register
+	srcMemScale, dstMemScale byte
+
+	mode byte
+}
+
+// AssignJumpTarget implements asm.Node.AssignJumpTarget.
+func (n *nodeImpl) AssignJumpTarget(target asm.Node) {
+	n.jumpTarget = target.(*nodeImpl)
+}
+
+// AssignSourceConstant implements asm.Node.AssignSourceConstant.
+func (n *nodeImpl) AssignDestinationConstant(value asm.ConstantValue) {
+	n.dstConst = value
+}
+
+// AssignSourceConstant implements asm.Node.AssignSourceConstant.
+func (n *nodeImpl) AssignSourceConstant(value asm.ConstantValue) {
+	n.srcConst = value
+}
+
+// OffsetInBinary implements asm.Node.OffsetInBinary.
+func (n *nodeImpl) OffsetInBinary() asm.NodeOffsetInBinary {
+	return n.offsetInBinary
+}
+
+// String implements fmt.Stringer.
+//
+// This is for debugging purpose, and the format is almost same as the AT&T assembly syntax,
+// meaning that this should look like "INSTRUCTION ${from}, ${to}" where each operand
+// might be embraced by '[]' to represent the memory location.
+func (n *nodeImpl) String() (ret string) {
+	instName := instructionName(n.instruction)
+	switch n.types {
+	case operandTypesNoneToNone:
+		ret = instName
+	case operandTypesNoneToRegister:
+		ret = fmt.Sprintf("%s %s", instName, registerName(n.dstReg))
+	case operandTypesNoneToMemory:
+		if n.dstMemIndex != asm.NilRegister {
+			ret = fmt.Sprintf("%s [%s + 0x%x + %s*0x%x]", instName,
+				registerName(n.dstReg), n.dstConst, registerName(n.dstMemIndex), n.dstMemScale)
+		} else {
+			ret = fmt.Sprintf("%s [%s + 0x%x]", instName, registerName(n.dstReg), n.dstConst)
+		}
+	case operandTypesNoneToBranch:
+		ret = fmt.Sprintf("%s {%v}", instName, n.jumpTarget)
+	case operandTypesRegisterToNone:
+		ret = fmt.Sprintf("%s %s", instName, registerName(n.srcReg))
+	case operandTypesRegisterToRegister:
+		ret = fmt.Sprintf("%s %s, %s", instName, registerName(n.srcReg), registerName(n.dstReg))
+	case operandTypesRegisterToMemory:
+		if n.dstMemIndex != asm.NilRegister {
+			ret = fmt.Sprintf("%s %s, [%s + 0x%x + %s*0x%x]", instName, registerName(n.srcReg),
+				registerName(n.dstReg), n.dstConst, registerName(n.dstMemIndex), n.dstMemScale)
+		} else {
+			ret = fmt.Sprintf("%s %s, [%s + 0x%x]", instName, registerName(n.srcReg), registerName(n.dstReg), n.dstConst)
+		}
+	case operandTypesRegisterToConst:
+		ret = fmt.Sprintf("%s %s, 0x%x", instName, registerName(n.srcReg), n.dstConst)
+	case operandTypesMemoryToRegister:
+		if n.srcMemIndex != asm.NilRegister {
+			ret = fmt.Sprintf("%s [%s + %d + %s*0x%x], %s", instName,
+				registerName(n.srcReg), n.srcConst, registerName(n.srcMemIndex), n.srcMemScale, registerName(n.dstReg))
+		} else {
+			ret = fmt.Sprintf("%s [%s + 0x%x], %s", instName, registerName(n.srcReg), n.srcConst, registerName(n.dstReg))
+		}
+	case operandTypesMemoryToConst:
+		if n.srcMemIndex != asm.NilRegister {
+			ret = fmt.Sprintf("%s [%s + %d + %s*0x%x], 0x%x", instName,
+				registerName(n.srcReg), n.srcConst, registerName(n.srcMemIndex), n.srcMemScale, n.dstConst)
+		} else {
+			ret = fmt.Sprintf("%s [%s + 0x%x], 0x%x", instName, registerName(n.srcReg), n.srcConst, n.dstConst)
+		}
+	case operandTypesConstToMemory:
+		if n.dstMemIndex != asm.NilRegister {
+			ret = fmt.Sprintf("%s 0x%x, [%s + 0x%x + %s*0x%x]", instName, n.srcConst,
+				registerName(n.dstReg), n.dstConst, registerName(n.dstMemIndex), n.dstMemScale)
+		} else {
+			ret = fmt.Sprintf("%s 0x%x, [%s + 0x%x]", instName, n.srcConst, registerName(n.dstReg), n.dstConst)
+		}
+	case operandTypesConstToRegister:
+		ret = fmt.Sprintf("%s 0x%x, %s", instName, n.srcConst, registerName(n.dstReg))
+	}
+	return
+}
+
+// operandType represents where an operand is placed for an instruction.
+// Note: this is almost the same as obj.AddrType in GO assembler.
+type operandType byte
+
+const (
+	operandTypeNone operandType = iota
+	operandTypeRegister
+	operandTypeMemory
+	operandTypeConst
+	operandTypeBranch
+)
+
+func (o operandType) String() (ret string) {
+	switch o {
+	case operandTypeNone:
+		ret = "none"
+	case operandTypeRegister:
+		ret = "register"
+	case operandTypeMemory:
+		ret = "memory"
+	case operandTypeConst:
+		ret = "const"
+	case operandTypeBranch:
+		ret = "branch"
+	}
+	return
+}
+
+// operandTypes represents the only combinations of two operandTypes used by wazero
+type operandTypes struct{ src, dst operandType }
+
+var (
+	operandTypesNoneToNone         = operandTypes{operandTypeNone, operandTypeNone}
+	operandTypesNoneToRegister     = operandTypes{operandTypeNone, operandTypeRegister}
+	operandTypesNoneToMemory       = operandTypes{operandTypeNone, operandTypeMemory}
+	operandTypesNoneToBranch       = operandTypes{operandTypeNone, operandTypeBranch}
+	operandTypesRegisterToNone     = operandTypes{operandTypeRegister, operandTypeNone}
+	operandTypesRegisterToRegister = operandTypes{operandTypeRegister, operandTypeRegister}
+	operandTypesRegisterToMemory   = operandTypes{operandTypeRegister, operandTypeMemory}
+	operandTypesRegisterToConst    = operandTypes{operandTypeRegister, operandTypeConst}
+	operandTypesMemoryToRegister   = operandTypes{operandTypeMemory, operandTypeRegister}
+	operandTypesMemoryToConst      = operandTypes{operandTypeMemory, operandTypeConst}
+	operandTypesConstToRegister    = operandTypes{operandTypeConst, operandTypeRegister}
+	operandTypesConstToMemory      = operandTypes{operandTypeConst, operandTypeMemory}
+)
+
+// String implements fmt.Stringer
+func (o operandTypes) String() string {
+	return fmt.Sprintf("from:%s,to:%s", o.src, o.dst)
+}
+
+// assemblerImpl implements Assembler.
+type assemblerImpl struct {
+	asm.BaseAssemblerImpl
+
+	root, current *nodeImpl
+}
+
+var _ Assembler = &assemblerImpl{}
+
+// newNode creates a new Node and appends it into the linked list.
+func (a *assemblerImpl) newNode(instruction asm.Instruction, types operandTypes) *nodeImpl {
+	n := &nodeImpl{
+		instruction: instruction,
+		prev:        nil,
+		next:        nil,
+		types:       types,
+	}
+	a.addNode(n)
+	return n
+}
+
+// addNode appends the new node into the linked list.
+func (a *assemblerImpl) addNode(node *nodeImpl) {
+	if a.root == nil {
+		a.root = node
+		a.current = node
+	} else {
+		parent := a.current
+		parent.next = node
+		a.current = node
+	}
+}
+
+// encodeNode encodes the given node into writer.
+func (a *assemblerImpl) encodeNode(w io.Writer, n *nodeImpl) (err error) {
+	switch n.types {
+	case operandTypesNoneToNone:
+		err = a.encodeNoneToNone(w, n)
+	case operandTypesNoneToRegister:
+		err = a.encodeNoneToRegister(w, n)
+	case operandTypesNoneToMemory:
+		err = a.encodeNoneToMemory(w, n)
+	case operandTypesNoneToBranch:
+		err = a.encodeNoneToBranch(w, n)
+	case operandTypesRegisterToNone:
+		err = a.encodeRegisterToNone(w, n)
+	case operandTypesRegisterToRegister:
+		err = a.encodeRegisterToRegister(w, n)
+	case operandTypesRegisterToMemory:
+		err = a.encodeRegisterToMemory(w, n)
+	case operandTypesRegisterToConst:
+		err = a.encodeRegisterToConst(w, n)
+	case operandTypesMemoryToRegister:
+		err = a.encodeMemoryToRegister(w, n)
+	default:
+		err = fmt.Errorf("encoder undefined for [%s] operand type", n.types)
+	}
+	return
+}
+
+// Assemble implements asm.AssemblerBase
+func (a *assemblerImpl) Assemble() ([]byte, error) {
+	w := bytes.NewBuffer(nil)
+	// TODO: NOP padding for jumps before encodes: https://github.com/golang/go/issues/35881
+
+	for n := a.root; n != nil; n = n.next {
+		if err := a.encodeNode(w, n); err != nil {
+			return nil, err
+		}
+	}
+
+	code := w.Bytes()
+	for _, cb := range a.OnGenerateCallbacks {
+		if err := cb(code); err != nil {
+			return nil, err
+		}
+	}
+	return code, nil
+}
+
+// CompileReadInstructionAddress implements asm.AssemblerBase.CompileReadInstructionAddress
+func (a *assemblerImpl) CompileStandAlone(instruction asm.Instruction) asm.Node {
+	return a.newNode(instruction, operandTypesNoneToNone)
+}
+
+// CompileConstToRegister implements asm.AssemblerBase.CompileConstToRegister
+func (a *assemblerImpl) CompileConstToRegister(instruction asm.Instruction, value asm.ConstantValue, destinationReg asm.Register) (inst asm.Node) {
+	n := a.newNode(instruction, operandTypesConstToRegister)
+	n.srcConst = value
+	n.dstReg = destinationReg
+	return n
+}
+
+// CompileRegisterToRegister implements asm.AssemblerBase.CompileRegisterToRegister
+func (a *assemblerImpl) CompileRegisterToRegister(instruction asm.Instruction, from, to asm.Register) {
+	n := a.newNode(instruction, operandTypesRegisterToRegister)
+	n.srcReg = from
+	n.dstReg = to
+}
+
+// CompileMemoryToRegister implements asm.AssemblerBase.CompileMemoryToRegister
+func (a *assemblerImpl) CompileMemoryToRegister(instruction asm.Instruction, sourceBaseReg asm.Register, sourceOffsetConst asm.ConstantValue, destinationReg asm.Register) {
+	n := a.newNode(instruction, operandTypesMemoryToRegister)
+	n.srcReg = sourceBaseReg
+	n.srcConst = sourceOffsetConst
+	n.dstReg = destinationReg
+}
+
+// CompileRegisterToMemory implements asm.AssemblerBase.CompileRegisterToMemory
+func (a *assemblerImpl) CompileRegisterToMemory(instruction asm.Instruction, sourceRegister asm.Register, destinationBaseRegister asm.Register, destinationOffsetConst asm.ConstantValue) {
+	n := a.newNode(instruction, operandTypesRegisterToMemory)
+	n.srcReg = sourceRegister
+	n.dstReg = destinationBaseRegister
+	n.dstConst = destinationOffsetConst
+}
+
+// CompileJump implements asm.AssemblerBase.CompileJump
+func (a *assemblerImpl) CompileJump(jmpInstruction asm.Instruction) asm.Node {
+	return a.newNode(jmpInstruction, operandTypesNoneToBranch)
+}
+
+// CompileJumpToMemory implements asm.AssemblerBase.CompileJumpToMemory
+func (a *assemblerImpl) CompileJumpToMemory(jmpInstruction asm.Instruction, baseReg asm.Register, offset asm.ConstantValue) {
+	n := a.newNode(jmpInstruction, operandTypesNoneToMemory)
+	n.dstReg = baseReg
+	n.dstConst = offset
+}
+
+// CompileJumpToRegister implements asm.AssemblerBase.CompileJumpToRegister
+func (a *assemblerImpl) CompileJumpToRegister(jmpInstruction asm.Instruction, reg asm.Register) {
+	n := a.newNode(jmpInstruction, operandTypesNoneToRegister)
+	n.dstReg = reg
+}
+
+// CompileReadInstructionAddress implements asm.AssemblerBase.CompileReadInstructionAddress
+func (a *assemblerImpl) CompileReadInstructionAddress(destinationRegister asm.Register, beforeAcquisitionTargetInstruction asm.Instruction) {
+	// TODO
+}
+
+// CompileRegisterToRegisterWithMode implements assembler.CompileRegisterToRegisterWithMode
+func (a *assemblerImpl) CompileRegisterToRegisterWithMode(instruction asm.Instruction, from, to asm.Register, mode Mode) {
+	n := a.newNode(instruction, operandTypesRegisterToRegister)
+	n.srcReg = from
+	n.dstReg = to
+	n.mode = mode
+}
+
+// CompileMemoryWithIndexToRegister implements assembler.CompileMemoryWithIndexToRegister
+func (a *assemblerImpl) CompileMemoryWithIndexToRegister(instruction asm.Instruction, srcBaseReg asm.Register, srcOffsetConst asm.ConstantValue, srcIndex asm.Register, srcScale int16, dstReg asm.Register) {
+	n := a.newNode(instruction, operandTypesMemoryToRegister)
+	n.srcReg = srcBaseReg
+	n.srcConst = srcOffsetConst
+	n.srcMemIndex = srcIndex
+	n.srcMemScale = byte(srcScale)
+	n.dstReg = dstReg
+}
+
+// CompileRegisterToMemoryWithIndex implements assembler.CompileRegisterToMemoryWithIndex
+func (a *assemblerImpl) CompileRegisterToMemoryWithIndex(instruction asm.Instruction, srcReg asm.Register, dstBaseReg asm.Register, dstOffsetConst asm.ConstantValue, dstIndex asm.Register, dstScale int16) {
+	n := a.newNode(instruction, operandTypesRegisterToMemory)
+	n.srcReg = srcReg
+	n.dstReg = dstBaseReg
+	n.dstConst = dstOffsetConst
+	n.dstMemIndex = dstIndex
+	n.dstMemScale = byte(dstScale)
+}
+
+// CompileRegisterToConst implements assembler.CompileRegisterToConst
+func (a *assemblerImpl) CompileRegisterToConst(instruction asm.Instruction, srcRegister asm.Register, value asm.ConstantValue) asm.Node {
+	n := a.newNode(instruction, operandTypesRegisterToConst)
+	n.srcReg = srcRegister
+	n.dstConst = value
+	return n
+}
+
+// CompileRegisterToNone implements assembler.CompileRegisterToNone
+func (a *assemblerImpl) CompileRegisterToNone(instruction asm.Instruction, register asm.Register) {
+	n := a.newNode(instruction, operandTypesRegisterToNone)
+	n.srcReg = register
+}
+
+// CompileNoneToRegister implements assembler.CompileNoneToRegister
+func (a *assemblerImpl) CompileNoneToRegister(instruction asm.Instruction, register asm.Register) {
+	n := a.newNode(instruction, operandTypesNoneToRegister)
+	n.dstReg = register
+}
+
+// CompileNoneToMemory implements assembler.CompileNoneToMemory
+func (a *assemblerImpl) CompileNoneToMemory(instruction asm.Instruction, baseReg asm.Register, offset asm.ConstantValue) {
+	n := a.newNode(instruction, operandTypesNoneToMemory)
+	n.dstReg = baseReg
+	n.dstConst = offset
+}
+
+// CompileConstToMemory implements assembler.CompileConstToMemory
+func (a *assemblerImpl) CompileConstToMemory(instruction asm.Instruction, value asm.ConstantValue, dstbaseReg asm.Register, dstOffset asm.ConstantValue) asm.Node {
+	n := a.newNode(instruction, operandTypesConstToMemory)
+	n.srcConst = value
+	n.dstReg = dstbaseReg
+	n.dstConst = dstOffset
+	return n
+}
+
+// CompileMemoryToConst implements assembler.CompileMemoryToConst
+func (a *assemblerImpl) CompileMemoryToConst(instruction asm.Instruction, srcBaseReg asm.Register, srcOffset asm.ConstantValue, value asm.ConstantValue) asm.Node {
+	n := a.newNode(instruction, operandTypesMemoryToConst)
+	n.srcReg = srcBaseReg
+	n.srcConst = srcOffset
+	n.dstConst = value
+	return n
+}
+
+func (a *assemblerImpl) encodeNoneToNone(w io.Writer, n *nodeImpl) error {
+	return errors.New("TODO")
+}
+
+func (a *assemblerImpl) encodeNoneToRegister(w io.Writer, n *nodeImpl) error {
+	return errors.New("TODO")
+}
+
+func (a *assemblerImpl) encodeNoneToMemory(w io.Writer, n *nodeImpl) error {
+	return errors.New("TODO")
+}
+
+func (a *assemblerImpl) encodeNoneToBranch(w io.Writer, n *nodeImpl) error {
+	return errors.New("TODO")
+}
+
+func (a *assemblerImpl) encodeRegisterToNone(w io.Writer, n *nodeImpl) error {
+	return errors.New("TODO")
+}
+
+func (a *assemblerImpl) encodeRegisterToRegister(w io.Writer, n *nodeImpl) error {
+	return errors.New("TODO")
+}
+
+func (a *assemblerImpl) encodeRegisterToMemory(w io.Writer, n *nodeImpl) error {
+	return errors.New("TODO")
+}
+
+func (a *assemblerImpl) encodeRegisterToConst(w io.Writer, n *nodeImpl) error {
+	return errors.New("TODO")
+}
+
+func (a *assemblerImpl) encodeMemoryToRegister(w io.Writer, n *nodeImpl) error {
+	return errors.New("TODO")
+}
diff --git a/internal/wasm/jit/asm/arm64/assembler.go b/internal/wasm/jit/asm/arm64/assembler.go
index 6e8922b8..27cb9615 100644
--- a/internal/wasm/jit/asm/arm64/assembler.go
+++ b/internal/wasm/jit/asm/arm64/assembler.go
@@ -28,10 +28,10 @@ type Assembler interface {
 	CompileTwoRegistersToNone(instruction asm.Instruction, src1, src2 asm.Register)
 	// CompileTwoRegistersToRegister adds an instruction where source operands consist of one register `src` and
 	// constant `srcConst`, and destination operand is unspecified.
-	CompileRegisterAndConstSourceToNone(instruction asm.Instruction, src asm.Register, srcConst int64)
+	CompileRegisterAndConstSourceToNone(instruction asm.Instruction, src asm.Register, srcConst asm.ConstantValue)
 	// CompileLeftShiftedRegisterToRegister adds an instruction where source operand is the "left shifted register"
 	// represented as `srcReg << shiftNum` and the destaintion is the register `dstReg`.
-	CompileLeftShiftedRegisterToRegister(shiftedSourceReg asm.Register, shiftNum int64, srcReg, dstReg asm.Register)
+	CompileLeftShiftedRegisterToRegister(shiftedSourceReg asm.Register, shiftNum asm.ConstantValue, srcReg, dstReg asm.Register)
 	// CompileSIMDByteToSIMDByte adds an instruction where source and destination operand is the SIMD register
 	// specified as `srcReg.B8` and `dstReg.B8` where `.B8` part of register is called "arrangement".
 	// See https://stackoverflow.com/questions/57294672/what-is-arrangement-specifier-16b-8b-in-arm-assembly-language-instructions
diff --git a/internal/wasm/jit/asm/arm64/consts.go b/internal/wasm/jit/asm/arm64/consts.go
index 9604ebe9..f2d98a33 100644
--- a/internal/wasm/jit/asm/arm64/consts.go
+++ b/internal/wasm/jit/asm/arm64/consts.go
@@ -5,6 +5,8 @@ import (
 )
 
 // Arm64-specific register states.
+// https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/condition-codes-1-condition-flags-and-codes
+// Note: naming convension is exactly the same as Go assembler: https://go.dev/doc/asm
 const (
 	COND_EQ asm.ConditionalRegisterState = asm.ConditionalRegisterStateUnset + 1 + iota
 	COND_NE
@@ -25,6 +27,8 @@ const (
 )
 
 // Arm64-specific registers.
+// https://developer.arm.com/documentation/dui0801/a/Overview-of-AArch64-state/Predeclared-core-register-names-in-AArch64-state
+// Note: naming convension is exactly the same as Go assembler: https://go.dev/doc/asm
 const (
 	// Integer registers.
 	REG_R0 asm.Register = asm.NilRegister + 1 + iota
@@ -99,6 +103,8 @@ const (
 )
 
 // Arm64-specific instructions.
+//
+// Note: naming convension is exactly the same as Go assembler: https://go.dev/doc/asm
 const (
 	NOP asm.Instruction = iota
 	RET
diff --git a/internal/wasm/jit/asm/arm64/golang_asm.go b/internal/wasm/jit/asm/arm64/golang_asm.go
index 2aeefd60..625f3fc2 100644
--- a/internal/wasm/jit/asm/arm64/golang_asm.go
+++ b/internal/wasm/jit/asm/arm64/golang_asm.go
@@ -22,7 +22,7 @@ func newGolangAsmAssembler(temporaryRegister asm.Register) (*assemblerGoAsmImpl,
 }
 
 // CompileConstToRegisterInstruction implements Assembler.CompileConstToRegisterInstruction.
-func (a *assemblerGoAsmImpl) CompileConstToRegister(instruction asm.Instruction, constValue int64, destinationReg asm.Register) asm.Node {
+func (a *assemblerGoAsmImpl) CompileConstToRegister(instruction asm.Instruction, constValue asm.ConstantValue, destinationReg asm.Register) asm.Node {
 	inst := a.NewProg()
 	inst.As = castAsGolangAsmInstruction[instruction]
 	if constValue == 0 {
@@ -43,7 +43,7 @@ func (a *assemblerGoAsmImpl) CompileConstToRegister(instruction asm.Instruction,
 }
 
 // CompileMemoryToRegister implements AssemblerBase.CompileMemoryToRegister.
-func (a *assemblerGoAsmImpl) CompileMemoryToRegister(instruction asm.Instruction, sourceBaseReg asm.Register, sourceOffsetConst int64, destinationReg asm.Register) {
+func (a *assemblerGoAsmImpl) CompileMemoryToRegister(instruction asm.Instruction, sourceBaseReg asm.Register, sourceOffsetConst asm.ConstantValue, destinationReg asm.Register) {
 	if sourceOffsetConst > math.MaxInt16 {
 		// The assembler can take care of offsets larger than 2^15-1 by emitting additional instructions to load such large offset,
 		// but it uses "its" temporary register which we cannot track. Therefore, we avoid directly emitting memory load with large offsets,
@@ -76,7 +76,7 @@ func (a *assemblerGoAsmImpl) CompileMemoryWithRegisterOffsetToRegister(instructi
 }
 
 // CompileRegisterToMemory implements Assembler.CompileRegisterToMemory.
-func (a *assemblerGoAsmImpl) CompileRegisterToMemory(instruction asm.Instruction, sourceReg asm.Register, destinationBaseReg asm.Register, destinationOffsetConst int64) {
+func (a *assemblerGoAsmImpl) CompileRegisterToMemory(instruction asm.Instruction, sourceReg asm.Register, destinationBaseReg asm.Register, destinationOffsetConst asm.ConstantValue) {
 	if destinationOffsetConst > math.MaxInt16 {
 		// The assembler can take care of offsets larger than 2^15-1 by emitting additional instructions to load such large offset,
 		// but we cannot track its temporary register. Therefore, we avoid directly emitting memory load with large offsets:
@@ -158,7 +158,7 @@ func (a *assemblerGoAsmImpl) CompileTwoRegistersToNone(instruction asm.Instructi
 }
 
 // CompileRegisterAndConstSourceToNone implements Assembler.CompileRegisterAndConstSourceToNone.
-func (a *assemblerGoAsmImpl) CompileRegisterAndConstSourceToNone(instruction asm.Instruction, src asm.Register, srcConst int64) {
+func (a *assemblerGoAsmImpl) CompileRegisterAndConstSourceToNone(instruction asm.Instruction, src asm.Register, srcConst asm.ConstantValue) {
 	inst := a.NewProg()
 	inst.As = castAsGolangAsmInstruction[instruction]
 	// TYPE_NONE indicates that this instruction doesn't have a destination.
@@ -180,7 +180,7 @@ func (a *assemblerGoAsmImpl) CompileJump(jmpInstruction asm.Instruction) asm.Nod
 }
 
 // CompileJumpToMemory implements AssemblerBase.CompileJumpToMemory.
-func (a *assemblerGoAsmImpl) CompileJumpToMemory(jmpInstruction asm.Instruction, baseReg asm.Register, offset int64) {
+func (a *assemblerGoAsmImpl) CompileJumpToMemory(jmpInstruction asm.Instruction, baseReg asm.Register, offset asm.ConstantValue) {
 	br := a.NewProg()
 	br.As = castAsGolangAsmInstruction[jmpInstruction]
 	br.To.Type = obj.TYPE_MEM
@@ -207,7 +207,7 @@ func (a *assemblerGoAsmImpl) CompileStandAlone(instruction asm.Instruction) asm.
 }
 
 // CompileLeftShiftedRegisterToRegister implements Assembler.CompileLeftShiftedRegisterToRegister.
-func (a *assemblerGoAsmImpl) CompileLeftShiftedRegisterToRegister(shiftedSourceReg asm.Register, shiftNum int64, srcReg, destinationReg asm.Register) {
+func (a *assemblerGoAsmImpl) CompileLeftShiftedRegisterToRegister(shiftedSourceReg asm.Register, shiftNum asm.ConstantValue, srcReg, destinationReg asm.Register) {
 	inst := a.NewProg()
 	inst.As = arm64.AADD
 	inst.To.Type = obj.TYPE_REG
diff --git a/internal/wasm/jit/asm/assembler.go b/internal/wasm/jit/asm/assembler.go
index e754ebf4..b178cf0f 100644
--- a/internal/wasm/jit/asm/assembler.go
+++ b/internal/wasm/jit/asm/assembler.go
@@ -1,5 +1,10 @@
 package asm
 
+import (
+	"fmt"
+	"math"
+)
+
 // Register represents architecture-specific registers.
 type Register byte
 
@@ -20,19 +25,26 @@ const ConditionalRegisterStateUnset ConditionalRegisterState = 0
 
 // Node represents a node in the linked list of assembled operations.
 type Node interface {
+	fmt.Stringer
 	// AssignJumpTarget assigns the given target node as the destination of
 	// jump instruction for this Node.
 	AssignJumpTarget(target Node)
 	// AssignDestinationConstant assigns the given constnat as the destination
 	// of the instruction for this node.
-	AssignDestinationConstant(value int64)
+	AssignDestinationConstant(value ConstantValue)
 	// AssignSourceConstant assigns the given constnat as the source
 	// of the instruction for this node.
-	AssignSourceConstant(value int64)
-	// offsetInBinary returns the offset of this node in the assembled binary.
-	offsetInBinary() int64
+	AssignSourceConstant(value ConstantValue)
+	// OffsetInBinary returns the offset of this node in the assembled binary.
+	OffsetInBinary() NodeOffsetInBinary
 }
 
+// NodeOffsetInBinary represents an offset of this node in the final binary.
+type NodeOffsetInBinary = uint64
+
+// ConstantValue represents a constant value used in an instruction.
+type ConstantValue = int64
+
 // AssemblerBase is the common interface for assemblers among multiple architectures.
 //
 // Note: some of them can be implemented in a arch-independent way, but not all can be
@@ -53,20 +65,20 @@ type AssemblerBase interface {
 	// CompileStandAlone adds an instruction to take no arguments.
 	CompileStandAlone(instruction Instruction) Node
 	// CompileConstToRegister adds an instruction where source operand is `value` as constant and destination is `destinationReg` register.
-	CompileConstToRegister(instruction Instruction, value int64, destinationReg Register) (inst Node)
+	CompileConstToRegister(instruction Instruction, value ConstantValue, destinationReg Register) Node
 	// CompileConstToRegister adds an instruction where source and destination operands are registers.
 	CompileRegisterToRegister(instruction Instruction, from, to Register)
 	// CompileMemoryToRegister adds an instruction where source operands is the memory address specified by `sourceBaseReg+sourceOffsetConst`
 	// and the destination is `destinationReg` register.
-	CompileMemoryToRegister(instruction Instruction, sourceBaseReg Register, sourceOffsetConst int64, destinationReg Register)
+	CompileMemoryToRegister(instruction Instruction, sourceBaseReg Register, sourceOffsetConst ConstantValue, destinationReg Register)
 	// CompileRegisterToMemory adds an instruction where source operand is `sourceRegister` register and the destination is the
 	// memory address specified by `destinationBaseRegister+destinationOffsetConst`.
-	CompileRegisterToMemory(inst Instruction, sourceRegister Register, destinationBaseRegister Register, destinationOffsetConst int64)
+	CompileRegisterToMemory(instruction Instruction, sourceRegister Register, destinationBaseRegister Register, destinationOffsetConst ConstantValue)
 	// CompileJump adds jump-type instruction and returns the corresponding Node in the assembled linked list.
 	CompileJump(jmpInstruction Instruction) Node
 	// CompileJumpToMemory adds jump-type instruction whose destination is stored in the memory address specified by `baseReg+offset`,
 	// and returns the corresponding Node in the assembled linked list.
-	CompileJumpToMemory(jmpInstruction Instruction, baseReg Register, offset int64)
+	CompileJumpToMemory(jmpInstruction Instruction, baseReg Register, offset ConstantValue)
 	// CompileJumpToMemory adds jump-type instruction whose destination is the memory address specified by `reg` register.
 	CompileJumpToRegister(jmpInstruction Instruction, reg Register)
 	// CompileReadInstructionAddress adds an ADR instruction to set the absolute address of "target instruction"
@@ -78,3 +90,11 @@ type AssemblerBase interface {
 	// address of MOV instruction into the destination register.
 	CompileReadInstructionAddress(destinationRegister Register, beforeAcquisitionTargetInstruction Instruction)
 }
+
+// binaryOffsetMaximum represents the limit on the size of jump table in bytes.
+// When users try loading an extremely large webassembly binary which contains a br_table
+// statement with approximately 4294967296 (2^32) targets. Realistically speaking, that kind of binary
+// could result in more than ten giga bytes of native JITed code where we have to care about
+// huge stacks whose height might exceed 32-bit range, and such huge stack doesn't work with the
+// current implementation.
+const jumpTableMaximumOffset = math.MaxUint32
diff --git a/internal/wasm/jit/asm/golang_asm.go b/internal/wasm/jit/asm/golang_asm.go
index 447a6bfc..feb9f948 100644
--- a/internal/wasm/jit/asm/golang_asm.go
+++ b/internal/wasm/jit/asm/golang_asm.go
@@ -3,7 +3,6 @@ package asm
 import (
 	"encoding/binary"
 	"fmt"
-	"math"
 	"runtime"
 
 	goasm "github.com/twitchyliquid64/golang-asm"
@@ -19,9 +18,14 @@ func NewGolangAsmNode(p *obj.Prog) Node {
 	return &golangAsmNode{prog: p}
 }
 
-// offsetInBinary implements Node.offsetInBinary.
-func (n *golangAsmNode) offsetInBinary() int64 {
-	return n.prog.Pc
+// String implements fmt.Stringer.
+func (n *golangAsmNode) String() string {
+	return n.prog.String()
+}
+
+// OffsetInBinary implements Node.OffsetInBinary.
+func (n *golangAsmNode) OffsetInBinary() NodeOffsetInBinary {
+	return NodeOffsetInBinary(n.prog.Pc)
 }
 
 // AssignJumpTarget implements Node.AssignJumpTarget.
@@ -31,12 +35,12 @@ func (n *golangAsmNode) AssignJumpTarget(target Node) {
 }
 
 // AssignDestinationConstant implements Node.AssignDestinationConstant.
-func (n *golangAsmNode) AssignDestinationConstant(value int64) {
+func (n *golangAsmNode) AssignDestinationConstant(value ConstantValue) {
 	n.prog.To.Offset = value
 }
 
 // AssignSourceConstant implements Node.AssignSourceConstant.
-func (n *golangAsmNode) AssignSourceConstant(value int64) {
+func (n *golangAsmNode) AssignSourceConstant(value ConstantValue) {
 	n.prog.From.Offset = value
 }
 
@@ -82,20 +86,14 @@ func (a *GolangAsmBaseAssembler) AddOnGenerateCallBack(cb func([]byte) error) {
 // BuildJumpTable implements AssemblerBase.BuildJumpTable
 func (a *GolangAsmBaseAssembler) BuildJumpTable(table []byte, labelInitialInstructions []Node) {
 	a.AddOnGenerateCallBack(func(code []byte) error {
-		// Build the offset table for each target including default one.
-		base := labelInitialInstructions[0].offsetInBinary() // This corresponds to the L0's address in the example.
+		// Build the offset table for each target.
+		base := labelInitialInstructions[0].OffsetInBinary()
 		for i, nop := range labelInitialInstructions {
-			if uint64(nop.offsetInBinary())-uint64(base) >= math.MaxUint32 {
-				// TODO: this happens when users try loading an extremely large webassembly binary
-				// which contains a br_table statement with approximately 4294967296 (2^32) targets.
-				// We would like to support that binary, but realistically speaking, that kind of binary
-				// could result in more than ten giga bytes of native JITed code where we have to care about
-				// huge stacks whose height might exceed 32-bit range, and such huge stack doesn't work with the
-				// current implementation.
+			if uint64(nop.OffsetInBinary())-uint64(base) >= jumpTableMaximumOffset {
 				return fmt.Errorf("too large br_table")
 			}
 			// We store the offset from the beginning of the L0's initial instruction.
-			binary.LittleEndian.PutUint32(table[i*4:(i+1)*4], uint32(nop.offsetInBinary())-uint32(base))
+			binary.LittleEndian.PutUint32(table[i*4:(i+1)*4], uint32(nop.OffsetInBinary())-uint32(base))
 		}
 		return nil
 	})
diff --git a/internal/wasm/jit/asm/impl.go b/internal/wasm/jit/asm/impl.go
new file mode 100644
index 00000000..c9353efa
--- /dev/null
+++ b/internal/wasm/jit/asm/impl.go
@@ -0,0 +1,44 @@
+package asm
+
+import (
+	"encoding/binary"
+	"fmt"
+)
+
+// BaseAssemblerImpl includes code common to all architectures.
+//
+// Note: When possible, add code here instead of in architecture-specific files to reduce drift:
+// As this is internal, exporting symbols only to reduce duplication is ok.
+type BaseAssemblerImpl struct {
+	// setBranchTargetOnNextInstructions holds branch kind instructions (BR, conditional BR, etc)
+	// where we want to set the next coming instruction as the destination of these BR instructions.
+	setBranchTargetOnNextNodes []Node
+	// onGenerateCallbacks holds the callbacks which are called after generating native code.
+	OnGenerateCallbacks []func(code []byte) error
+}
+
+// SetJumpTargetOnNext implements AssemblerBase.SetJumpTargetOnNext
+func (a *BaseAssemblerImpl) SetJumpTargetOnNext(nodes ...Node) {
+	a.setBranchTargetOnNextNodes = append(a.setBranchTargetOnNextNodes, nodes...)
+}
+
+// AddOnGenerateCallBack implements AssemblerBase.AddOnGenerateCallBack
+func (a *BaseAssemblerImpl) AddOnGenerateCallBack(cb func([]byte) error) {
+	a.OnGenerateCallbacks = append(a.OnGenerateCallbacks, cb)
+}
+
+// BuildJumpTable implements AssemblerBase.BuildJumpTable
+func (a *BaseAssemblerImpl) BuildJumpTable(table []byte, labelInitialInstructions []Node) {
+	a.AddOnGenerateCallBack(func(code []byte) error {
+		// Build the offset table for each target.
+		base := labelInitialInstructions[0].OffsetInBinary()
+		for i, nop := range labelInitialInstructions {
+			if uint64(nop.OffsetInBinary())-uint64(base) >= jumpTableMaximumOffset {
+				return fmt.Errorf("too large br_table")
+			}
+			// We store the offset from the beginning of the L0's initial instruction.
+			binary.LittleEndian.PutUint32(table[i*4:(i+1)*4], uint32(nop.OffsetInBinary())-uint32(base))
+		}
+		return nil
+	})
+}
diff --git a/internal/wasm/jit/jit_amd64_impl.go b/internal/wasm/jit/jit_amd64_impl.go
index d31e3fb0..b58e620b 100644
--- a/internal/wasm/jit/jit_amd64_impl.go
+++ b/internal/wasm/jit/jit_amd64_impl.go
@@ -1801,9 +1801,9 @@ func (c *amd64Compiler) compileRoundInstruction(is32Bit bool, mode int64) error
 	}
 
 	if is32Bit {
-		c.assembler.CompileConstModeRegisterToRegister(amd64.ROUNDSS, target.register, target.register, mode)
+		c.assembler.CompileRegisterToRegisterWithMode(amd64.ROUNDSS, target.register, target.register, byte(mode))
 	} else {
-		c.assembler.CompileConstModeRegisterToRegister(amd64.ROUNDSD, target.register, target.register, mode)
+		c.assembler.CompileRegisterToRegisterWithMode(amd64.ROUNDSD, target.register, target.register, byte(mode))
 	}
 	return nil
 }


Test patch:
diff --git a/internal/wasm/jit/asm/amd64/impl_test.go b/internal/wasm/jit/asm/amd64/impl_test.go
new file mode 100644
index 00000000..04cc9d8b
--- /dev/null
+++ b/internal/wasm/jit/asm/amd64/impl_test.go
@@ -0,0 +1,339 @@
+package amd64
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestNodeImpl_AssignJumpTarget(t *testing.T) {
+	n := &nodeImpl{}
+	target := &nodeImpl{}
+	n.AssignJumpTarget(target)
+	require.Equal(t, n.jumpTarget, target)
+}
+
+func TestNodeImpl_AssignDestinationConstant(t *testing.T) {
+	n := &nodeImpl{}
+	n.AssignDestinationConstant(12345)
+	require.Equal(t, int64(12345), n.dstConst)
+}
+
+func TestNodeImpl_AssignSourceConstant(t *testing.T) {
+	n := &nodeImpl{}
+	n.AssignSourceConstant(12345)
+	require.Equal(t, int64(12345), n.srcConst)
+}
+
+func TestNodeImpl_String(t *testing.T) {
+	for _, tc := range []struct {
+		in  *nodeImpl
+		exp string
+	}{
+		{
+			in:  &nodeImpl{instruction: NOP},
+			exp: "NOP",
+		},
+		{
+			in:  &nodeImpl{instruction: SETCC, types: operandTypesNoneToRegister, dstReg: REG_AX},
+			exp: "SETCC AX",
+		},
+		{
+			in:  &nodeImpl{instruction: JMP, types: operandTypesNoneToMemory, dstReg: REG_AX, dstConst: 100},
+			exp: "JMP [AX + 0x64]",
+		},
+		{
+			in:  &nodeImpl{instruction: JMP, types: operandTypesNoneToMemory, dstReg: REG_AX, dstConst: 100, dstMemScale: 8, dstMemIndex: REG_R11},
+			exp: "JMP [AX + 0x64 + R11*0x8]",
+		},
+		{
+			in:  &nodeImpl{instruction: JMP, types: operandTypesNoneToBranch, jumpTarget: &nodeImpl{instruction: JMP, types: operandTypesNoneToMemory, dstReg: REG_AX, dstConst: 100}},
+			exp: "JMP {JMP [AX + 0x64]}",
+		},
+		{
+			in:  &nodeImpl{instruction: IDIVQ, types: operandTypesRegisterToNone, srcReg: REG_DX},
+			exp: "IDIVQ DX",
+		},
+		{
+			in:  &nodeImpl{instruction: ADDL, types: operandTypesRegisterToRegister, srcReg: REG_DX, dstReg: REG_R14},
+			exp: "ADDL DX, R14",
+		},
+		{
+			in: &nodeImpl{instruction: MOVQ, types: operandTypesRegisterToMemory,
+				srcReg: REG_DX, dstReg: REG_R14, dstConst: 100},
+			exp: "MOVQ DX, [R14 + 0x64]",
+		},
+		{
+			in: &nodeImpl{instruction: MOVQ, types: operandTypesRegisterToMemory,
+				srcReg: REG_DX, dstReg: REG_R14, dstConst: 100, dstMemIndex: REG_CX, dstMemScale: 4},
+			exp: "MOVQ DX, [R14 + 0x64 + CX*0x4]",
+		},
+		{
+			in: &nodeImpl{instruction: CMPL, types: operandTypesRegisterToConst,
+				srcReg: REG_DX, dstConst: 100},
+			exp: "CMPL DX, 0x64",
+		},
+		{
+			in: &nodeImpl{instruction: MOVL, types: operandTypesMemoryToRegister,
+				srcReg: REG_DX, srcConst: 1, dstReg: REG_AX},
+			exp: "MOVL [DX + 0x1], AX",
+		},
+		{
+			in: &nodeImpl{instruction: MOVL, types: operandTypesMemoryToRegister,
+				srcReg: REG_DX, srcConst: 1, srcMemIndex: REG_R12, srcMemScale: 2,
+				dstReg: REG_AX},
+			exp: "MOVL [DX + 1 + R12*0x2], AX",
+		},
+		{
+			in: &nodeImpl{instruction: CMPQ, types: operandTypesMemoryToConst,
+				srcReg: REG_DX, srcConst: 1, srcMemIndex: REG_R12, srcMemScale: 2,
+				dstConst: 123},
+			exp: "CMPQ [DX + 1 + R12*0x2], 0x7b",
+		},
+		{
+			in:  &nodeImpl{instruction: MOVQ, types: operandTypesConstToMemory, srcConst: 123, dstReg: REG_AX, dstConst: 100, dstMemScale: 8, dstMemIndex: REG_R11},
+			exp: "MOVQ 0x7b, [AX + 0x64 + R11*0x8]",
+		},
+		{
+			in:  &nodeImpl{instruction: MOVQ, types: operandTypesConstToRegister, srcConst: 123, dstReg: REG_AX},
+			exp: "MOVQ 0x7b, AX",
+		},
+	} {
+		require.Equal(t, tc.exp, tc.in.String())
+	}
+}
+
+func TestAssemblerImpl_addNode(t *testing.T) {
+	a := &assemblerImpl{}
+
+	root := &nodeImpl{}
+	a.addNode(root)
+	require.Equal(t, a.root, root)
+	require.Equal(t, a.current, root)
+	require.Nil(t, root.next)
+
+	next := &nodeImpl{}
+	a.addNode(next)
+	require.Equal(t, a.root, root)
+	require.Equal(t, a.current, next)
+	require.Equal(t, next, root.next)
+	require.Nil(t, next.next)
+}
+
+func TestAssemblerImpl_newNode(t *testing.T) {
+	a := &assemblerImpl{}
+	actual := a.newNode(ADDL, operandTypesConstToMemory)
+	require.Equal(t, ADDL, actual.instruction)
+	require.Equal(t, operandTypeConst, actual.types.src)
+	require.Equal(t, operandTypeMemory, actual.types.dst)
+	require.Equal(t, actual, a.root)
+	require.Equal(t, actual, a.current)
+}
+
+func TestAssemblerImpl_encodeNode(t *testing.T) {
+	t.Run("encoder undefined", func(t *testing.T) {
+		a := &assemblerImpl{}
+		err := a.encodeNode(bytes.NewBuffer(nil), &nodeImpl{
+			types: operandTypes{operandTypeBranch, operandTypeRegister},
+		})
+		require.EqualError(t, err, "encoder undefined for [from:branch,to:register] operand type")
+	})
+	// TODO: adds ok cases.
+}
+
+func TestAssemblerImpl_Assemble(t *testing.T) {
+	t.Run("callback", func(t *testing.T) {
+		t.Run("ok", func(t *testing.T) {
+			a := &assemblerImpl{}
+			callbacked := false
+			a.AddOnGenerateCallBack(func(b []byte) error { callbacked = true; return nil })
+			_, err := a.Assemble()
+			require.NoError(t, err)
+			require.True(t, callbacked)
+		})
+		t.Run("error", func(t *testing.T) {
+			a := &assemblerImpl{}
+			a.AddOnGenerateCallBack(func(b []byte) error { return fmt.Errorf("some error") })
+			_, err := a.Assemble()
+			require.EqualError(t, err, "some error")
+		})
+	})
+	// TODO: adds actual e2e assembling case.
+}
+
+func TestAssemblerImpl_CompileStandAlone(t *testing.T) {
+	a := &assemblerImpl{}
+	a.CompileStandAlone(RET)
+	actualNode := a.current
+	require.Equal(t, RET, actualNode.instruction)
+	require.Equal(t, operandTypeNone, actualNode.types.src)
+	require.Equal(t, operandTypeNone, actualNode.types.dst)
+}
+
+func TestAssemblerImpl_CompileConstToRegister(t *testing.T) {
+	a := &assemblerImpl{}
+	a.CompileConstToRegister(MOVQ, 1000, REG_AX)
+	actualNode := a.current
+	require.Equal(t, MOVQ, actualNode.instruction)
+	require.Equal(t, int64(1000), actualNode.srcConst)
+	require.Equal(t, REG_AX, actualNode.dstReg)
+	require.Equal(t, operandTypeConst, actualNode.types.src)
+	require.Equal(t, operandTypeRegister, actualNode.types.dst)
+}
+
+func TestAssemblerImpl_CompileRegisterToRegister(t *testing.T) {
+	a := &assemblerImpl{}
+	a.CompileRegisterToRegister(MOVQ, REG_BX, REG_AX)
+	actualNode := a.current
+	require.Equal(t, MOVQ, actualNode.instruction)
+	require.Equal(t, REG_BX, actualNode.srcReg)
+	require.Equal(t, REG_AX, actualNode.dstReg)
+	require.Equal(t, operandTypeRegister, actualNode.types.src)
+	require.Equal(t, operandTypeRegister, actualNode.types.dst)
+}
+
+func TestAssemblerImpl_CompileMemoryToRegister(t *testing.T) {
+	a := &assemblerImpl{}
+	a.CompileMemoryToRegister(MOVQ, REG_BX, 100, REG_AX)
+	actualNode := a.current
+	require.Equal(t, MOVQ, actualNode.instruction)
+	require.Equal(t, REG_BX, actualNode.srcReg)
+	require.Equal(t, int64(100), actualNode.srcConst)
+	require.Equal(t, REG_AX, actualNode.dstReg)
+	require.Equal(t, operandTypeMemory, actualNode.types.src)
+	require.Equal(t, operandTypeRegister, actualNode.types.dst)
+}
+
+func TestAssemblerImpl_CompileRegisterToMemory(t *testing.T) {
+	a := &assemblerImpl{}
+	a.CompileRegisterToMemory(MOVQ, REG_BX, REG_AX, 100)
+	actualNode := a.current
+	require.Equal(t, MOVQ, actualNode.instruction)
+	require.Equal(t, REG_BX, actualNode.srcReg)
+	require.Equal(t, REG_AX, actualNode.dstReg)
+	require.Equal(t, int64(100), actualNode.dstConst)
+	require.Equal(t, operandTypeRegister, actualNode.types.src)
+	require.Equal(t, operandTypeMemory, actualNode.types.dst)
+}
+
+func TestAssemblerImpl_CompileJump(t *testing.T) {
+	a := &assemblerImpl{}
+	a.CompileJump(JMP)
+	actualNode := a.current
+	require.Equal(t, JMP, actualNode.instruction)
+	require.Equal(t, operandTypeNone, actualNode.types.src)
+	require.Equal(t, operandTypeBranch, actualNode.types.dst)
+}
+
+func TestAssemblerImpl_CompileJumpToRegister(t *testing.T) {
+	a := &assemblerImpl{}
+	a.CompileJumpToRegister(JNE, REG_AX)
+	actualNode := a.current
+	require.Equal(t, JNE, actualNode.instruction)
+	require.Equal(t, REG_AX, actualNode.dstReg)
+	require.Equal(t, operandTypeNone, actualNode.types.src)
+	require.Equal(t, operandTypeRegister, actualNode.types.dst)
+}
+
+func TestAssemblerImpl_CompileJumpToMemory(t *testing.T) {
+	a := &assemblerImpl{}
+	a.CompileJumpToMemory(JNE, REG_AX, 100)
+	actualNode := a.current
+	require.Equal(t, JNE, actualNode.instruction)
+	require.Equal(t, REG_AX, actualNode.dstReg)
+	require.Equal(t, int64(100), actualNode.dstConst)
+	require.Equal(t, operandTypeNone, actualNode.types.src)
+	require.Equal(t, operandTypeMemory, actualNode.types.dst)
+}
+
+func TestAssemblerImpl_CompileReadInstructionAddress(t *testing.T) {
+	t.Skip("TODO: unimplemented")
+}
+
+func TestAssemblerImpl_CompileRegisterToRegisterWithMode(t *testing.T) {
+	a := &assemblerImpl{}
+	a.CompileRegisterToRegisterWithMode(MOVQ, REG_BX, REG_AX, 123)
+	actualNode := a.current
+	require.Equal(t, MOVQ, actualNode.instruction)
+	require.Equal(t, REG_BX, actualNode.srcReg)
+	require.Equal(t, REG_AX, actualNode.dstReg)
+	require.Equal(t, byte(123), actualNode.mode)
+	require.Equal(t, operandTypeRegister, actualNode.types.src)
+	require.Equal(t, operandTypeRegister, actualNode.types.dst)
+}
+
+func TestAssemblerImpl_CompileMemoryWithIndexToRegister(t *testing.T) {
+	a := &assemblerImpl{}
+	a.CompileMemoryWithIndexToRegister(MOVQ, REG_BX, 100, REG_R10, 8, REG_AX)
+	actualNode := a.current
+	require.Equal(t, MOVQ, actualNode.instruction)
+	require.Equal(t, REG_BX, actualNode.srcReg)
+	require.Equal(t, int64(100), actualNode.srcConst)
+	require.Equal(t, REG_R10, actualNode.srcMemIndex)
+	require.Equal(t, byte(8), actualNode.srcMemScale)
+	require.Equal(t, REG_AX, actualNode.dstReg)
+	require.Equal(t, operandTypeMemory, actualNode.types.src)
+	require.Equal(t, operandTypeRegister, actualNode.types.dst)
+}
+
+func TestAssemblerImpl_CompileRegisterToConst(t *testing.T) {
+	a := &assemblerImpl{}
+	a.CompileRegisterToConst(MOVQ, REG_BX, 123)
+	actualNode := a.current
+	require.Equal(t, REG_BX, actualNode.srcReg)
+	require.Equal(t, int64(123), actualNode.dstConst)
+	require.Equal(t, operandTypeRegister, actualNode.types.src)
+	require.Equal(t, operandTypeConst, actualNode.types.dst)
+}
+
+func TestAssemblerImpl_CompileRegisterToNone(t *testing.T) {
+	a := &assemblerImpl{}
+	a.CompileRegisterToNone(MOVQ, REG_BX)
+	actualNode := a.current
+	require.Equal(t, REG_BX, actualNode.srcReg)
+	require.Equal(t, operandTypeRegister, actualNode.types.src)
+	require.Equal(t, operandTypeNone, actualNode.types.dst)
+}
+
+func TestAssemblerImpl_CompileNoneToRegister(t *testing.T) {
+	a := &assemblerImpl{}
+	a.CompileNoneToRegister(MOVQ, REG_BX)
+	actualNode := a.current
+	require.Equal(t, REG_BX, actualNode.dstReg)
+	require.Equal(t, operandTypeNone, actualNode.types.src)
+	require.Equal(t, operandTypeRegister, actualNode.types.dst)
+}
+
+func TestAssemblerImpl_CompileNoneToMemory(t *testing.T) {
+	a := &assemblerImpl{}
+	a.CompileNoneToMemory(MOVQ, REG_BX, 1234)
+	actualNode := a.current
+	require.Equal(t, REG_BX, actualNode.dstReg)
+	require.Equal(t, int64(1234), actualNode.dstConst)
+	require.Equal(t, operandTypeNone, actualNode.types.src)
+	require.Equal(t, operandTypeMemory, actualNode.types.dst)
+}
+
+func TestAssemblerImpl_CompileConstToMemory(t *testing.T) {
+	a := &assemblerImpl{}
+	a.CompileConstToMemory(MOVQ, -9999, REG_BX, 1234)
+	actualNode := a.current
+	require.Equal(t, REG_BX, actualNode.dstReg)
+	require.Equal(t, int64(-9999), actualNode.srcConst)
+	require.Equal(t, int64(1234), actualNode.dstConst)
+	require.Equal(t, operandTypeConst, actualNode.types.src)
+	require.Equal(t, operandTypeMemory, actualNode.types.dst)
+}
+
+func TestAssemblerImpl_CompileMemoryToConst(t *testing.T) {
+	a := &assemblerImpl{}
+	a.CompileMemoryToConst(MOVQ, REG_BX, 1234, -9999)
+	actualNode := a.current
+	require.Equal(t, REG_BX, actualNode.srcReg)
+	require.Equal(t, int64(1234), actualNode.srcConst)
+	require.Equal(t, int64(-9999), actualNode.dstConst)
+	require.Equal(t, operandTypeMemory, actualNode.types.src)
+	require.Equal(t, operandTypeConst, actualNode.types.dst)
+}