Skip to content

Commit

Permalink
ADD(S) + SUB(S) w/ immediate + MOV/CMP/CMN alias
Browse files Browse the repository at this point in the history
  • Loading branch information
chriso committed Feb 27, 2022
1 parent 4b72da1 commit 138e4a0
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 55 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright 2021 Chris O'Hara <[email protected]>
Copyright 2022 Chris O'Hara <[email protected]>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
An incomplete assembler/disassembler for the [ARMv9-A A64 ISA (2021-12)](https://developer.arm.com/documentation/ddi0602/2021-12).
73 changes: 55 additions & 18 deletions arm64_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,62 @@ func TestARM64(t *testing.T) {
ins Instruction
str string
}{
{0b_0_0_0_11010000_00010_000000_00001_00000, New(ADC).WithOperands(W0, W1, W2), "adc w0, w1, w2"},
{0b_0_0_1_11010000_00010_000000_00001_00000, New(ADCS).WithOperands(W0, W1, W2), "adcs w0, w1, w2"},
{0b_1_0_0_11010000_11101_000000_11111_10101, New(ADC).WithOperands(X21, XZR, X29), "adc x21, xzr, x29"},
{0b_0_1_0_11010000_00111_000000_00101_00011, New(SBC).WithOperands(W3, W5, W7), "sbc w3, w5, w7"},
{0b_1_1_1_11010000_00011_000000_00010_00000, New(SBCS).WithOperands(X0, X2, X3), "sbcs x0, x2, x3"},
{0b_1_1_0_11010000_00010_000000_00001_11111, New(NGC).WithOperands(X1, X2), "ngc x1, x2"},
{0b_0_1_1_11010000_00010_000000_00001_11111, New(NGCS).WithOperands(W1, W2), "ngcs w1, w2"},
// Add/Subtract/Negate with Carry (ADC, ADCS, SBC, SBCS, NGC, NGCS)
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/ADC--Add-with-Carry-
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/ADCS--Add-with-Carry--setting-flags-
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/SBC--Subtract-with-Carry-
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/SBCS--Subtract-with-Carry--setting-flags-
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/NGC--Negate-with-Carry--an-alias-of-SBC-
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/NGCS--Negate-with-Carry--setting-flags--an-alias-of-SBCS-
// 0b_{sf}_{op}_{S}_{11010000}_{Rm(5)}_{000000}_{Rn(5)}_{Rd(5)}
{0b_0_0_0_11010000_00010_000000_00001_00000, New(ADC, W0, W1, W2), "adc w0, w1, w2"},
{0b_0_0_1_11010000_00010_000000_00001_00000, New(ADCS, W0, W1, W2), "adcs w0, w1, w2"},
{0b_1_0_0_11010000_11101_000000_11111_10101, New(ADC, X21, XZR, X29), "adc x21, xzr, x29"},
{0b_0_1_0_11010000_00111_000000_00101_00011, New(SBC, W3, W5, W7), "sbc w3, w5, w7"},
{0b_1_1_1_11010000_00011_000000_00010_00000, New(SBCS, X0, X2, X3), "sbcs x0, x2, x3"},
{0b_1_1_0_11010000_00010_000000_00001_11111, New(NGC, X1, X2), "ngc x1, x2"},
{0b_0_1_1_11010000_00010_000000_00001_11111, New(NGCS, W1, W2), "ngcs w1, w2"},

{0b_0_0_0_01011001_00011_000_000_00010_00001, New(ADD).WithOperands(W1, W2, W3.ExtShift(UXTB, 0)), "add w1, w2, w3, uxtb"},
{0b_1_0_1_01011001_00011_001_011_00010_00001, New(ADDS).WithOperands(X1, X2, X3.ExtShift(UXTH, 3)), "adds x1, x2, x3, uxth #0x3"},
{0b_1_0_0_01011001_00011_010_010_00010_00001, New(ADD).WithOperands(X1, X2, X3.ExtShift(UXTW, 2)), "add x1, x2, x3, uxtw #0x2"},
{0b_1_1_0_01011001_00011_011_001_00010_00001, New(SUB).WithOperands(X1, X2, X3.ExtShift(UXTX, 1)), "sub x1, x2, x3, uxtx #0x1"},
{0b_0_0_0_01011001_00000_100_100_00000_00000, New(ADD).WithOperands(W0, W0, W0.ExtShift(SXTB, 4)), "add w0, w0, w0, sxtb #0x4"},
{0b_0_0_0_01011001_00001_101_011_00001_00001, New(ADD).WithOperands(W1, W1, W1.ExtShift(SXTH, 3)), "add w1, w1, w1, sxth #0x3"},
{0b_1_0_0_01011001_00010_110_010_00010_00010, New(ADD).WithOperands(X2, X2, X2.ExtShift(SXTW, 2)), "add x2, x2, x2, sxtw #0x2"},
{0b_1_1_1_01011001_00011_111_001_11111_11111, New(SUBS).WithOperands(SP, SP, X3.ExtShift(SXTX, 1)), "subs sp, sp, x3, sxtx #0x1"},
{0b_0_0_1_01011001_11111_010_000_11111_11111, New(ADDS).WithOperands(WSP, WSP, WZR.ExtShift(UXTW, 0)), "adds wsp, wsp, wzr, uxtw"},
{0b_0_0_0_01011001_11111_010_100_11111_11111, New(ADD).WithOperands(WSP, WSP, WZR.ExtShift(LSL, 4)), "add wsp, wsp, wzr, lsl #0x4"},
{0b_1_0_0_01011001_11111_010_100_11111_11111, New(ADD).WithOperands(SP, SP, XZR.ExtShift(LSL, 4)), "add sp, sp, xzr, lsl #0x4"},
// Add/Subtract (ADD, ADDS, SUB, SUBS) - Extended Register
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/ADD--extended-register---Add--extended-register--
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/ADDS--extended-register---Add--extended-register---setting-flags-
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/SUB--extended-register---Subtract--extended-register--
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/SUBS--extended-register---Subtract--extended-register---setting-flags-
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/CMP--extended-register---Compare--extended-register---an-alias-of-SUBS--extended-register--
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/CMN--extended-register---Compare-Negative--extended-register---an-alias-of-ADDS--extended-register--
// 0b_{sf}_{op}_{S}_{01011001}_{Rm(5)}_{option(3)}_{imm3(3)}_{Rn(5)}_{Rd(5)}
{0b_0_0_0_01011001_00011_000_000_00010_00001, New(ADD, W1, W2, W3.ExtShift(UXTB, 0)), "add w1, w2, w3, uxtb"},
{0b_1_0_1_01011001_00011_001_011_00010_00001, New(ADDS, X1, X2, X3.ExtShift(UXTH, 3)), "adds x1, x2, x3, uxth #0x3"},
{0b_1_0_0_01011001_00011_010_010_00010_00001, New(ADD, X1, X2, X3.ExtShift(UXTW, 2)), "add x1, x2, x3, uxtw #0x2"},
{0b_1_1_0_01011001_00011_011_001_00010_00001, New(SUB, X1, X2, X3.ExtShift(UXTX, 1)), "sub x1, x2, x3, uxtx #0x1"},
{0b_0_0_0_01011001_00000_100_100_00000_00000, New(ADD, W0, W0, W0.ExtShift(SXTB, 4)), "add w0, w0, w0, sxtb #0x4"},
{0b_0_0_0_01011001_00001_101_011_00001_00001, New(ADD, W1, W1, W1.ExtShift(SXTH, 3)), "add w1, w1, w1, sxth #0x3"},
{0b_1_0_0_01011001_00010_110_010_00010_00010, New(ADD, X2, X2, X2.ExtShift(SXTW, 2)), "add x2, x2, x2, sxtw #0x2"},
{0b_0_0_0_01011001_11111_010_100_11111_11111, New(ADD, WSP, WSP, WZR.ExtShift(LSL, 4)), "add wsp, wsp, wzr, lsl #0x4"},
{0b_1_0_0_01011001_11111_010_100_11111_11111, New(ADD, SP, SP, XZR.ExtShift(LSL, 4)), "add sp, sp, xzr, lsl #0x4"},
{0b_1_1_1_01011001_00011_111_001_11111_11111, New(CMP, SP, X3.ExtShift(SXTX, 1)), "cmp sp, x3, sxtx #0x1"},
{0b_0_0_1_01011001_11111_010_000_11111_11111, New(CMN, WSP, WZR.ExtShift(UXTW, 0)), "cmn wsp, wzr, uxtw"},

// Add/Subtract (ADD, ADDS, SUB, SUBS) - Immediate
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/ADD--immediate---Add--immediate--
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/ADDS--immediate---Add--immediate---setting-flags-
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/SUB--immediate---Subtract--immediate--
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/SUBS--immediate---Subtract--immediate---setting-flags-
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/MOV--to-from-SP---Move-between-register-and-stack-pointer--an-alias-of-ADD--immediate--
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/CMP--immediate---Compare--immediate---an-alias-of-SUBS--immediate--
// https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/CMN--immediate---Compare-Negative--immediate---an-alias-of-ADDS--immediate--
// 0b_{sf}_{op}_{S}_{100010}_{sh}_{imm12}_{Rn(5)}_{Rd(5)}
{0b_1_0_0_100010_0_111111111111_00010_00001, New(ADD, X1, X2, Immediate(4095)), "add x1, x2, #0xfff"},
{0b_1_0_1_100010_1_111111111111_00010_00001, New(ADDS, X1, X2, Immediate(4095).ExtShift(LSL, 12)), "adds x1, x2, #0xfff, lsl #0xc"},
{0b_0_1_1_100010_0_111111111111_00010_00001, New(SUBS, W1, W2, Immediate(4095)), "subs w1, w2, #0xfff"},
{0b_1_1_0_100010_1_000000000001_00010_00001, New(SUB, X1, X2, Immediate(1).ExtShift(LSL, 12)), "sub x1, x2, #0x1, lsl #0xc"},
{0b_1_0_0_100010_0_000000000010_11111_11111, New(ADD, SP, SP, Immediate(2)), "add sp, sp, #0x2"},
{0b_1_0_0_100010_0_000000000000_00001_11111, New(MOV, SP, X1), "mov sp, x1"},
{0b_0_0_0_100010_0_000000000000_11111_00010, New(MOV, W2, WSP), "mov w2, wsp"},
{0b_0_1_1_100010_0_111111111111_00001_11111, New(CMP, W1, Immediate(4095)), "cmp w1, #0xfff"},
{0b_1_1_1_100010_1_000000000001_00010_11111, New(CMP, X2, Immediate(1).ExtShift(LSL, 12)), "cmp x2, #0x1, lsl #0xc"},
{0b_0_0_1_100010_0_111111111111_00001_11111, New(CMN, W1, Immediate(4095)), "cmn w1, #0xfff"},
{0b_1_0_1_100010_1_000000000001_00010_11111, New(CMN, X2, Immediate(1).ExtShift(LSL, 12)), "cmn x2, #0x1, lsl #0xc"},
} {
t.Run(test.str, func(t *testing.T) {
ins, err := Disassemble(test.raw)
Expand Down
100 changes: 82 additions & 18 deletions assemble.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import (

var ErrInvalidOperand = errors.New("invalid operand")

// Assemble assembles an instruction.
func Assemble(i *Instruction) (uint32, error) {
switch m := i.Mnemonic; m {
case ADC, ADCS, SBC, SBCS:
rd, rn, rm, err := unpackreg3(i)
rd, rn, rm, err := unpackRegister3(i)
if err != nil {
return 0, err
}
Expand All @@ -21,24 +22,87 @@ func Assemble(i *Instruction) (uint32, error) {
s := flag(m == ADCS || m == SBCS)
return encode_addsub_carry(sf, op, s, rm.Index(), rn.Index(), rd.Index())
case ADD, ADDS, SUB, SUBS:
rd, rn, rm, err := unpackreg3(i)
rd, rn, err := unpackRegister2(i)
if err != nil {
return 0, err
}
e, ok := i.Operands[2].(ExtShiftRegister)
if !ok || i.Operands[3] != nil || !rn.Type().GP() || e.Amount > 4 {
if i.Operands[3] != nil {
return 0, ErrInvalidOperand
}
option := e.ExtShift
if option == LSL {
option = UXTW
}
sf := flag(rd.Type() == X)
op := flag(m == SUB || m == SUBS)
s := flag(m == ADDS || m == SUBS)
return encode_addsub_ext(sf, op, s, 0, rm.Index(), uint32(option), uint32(e.Amount), rn.Index(), rd.Index())
switch r := i.Operands[2].(type) {
case ExtShiftRegister:
if !rn.Type().GP() || r.Amount > 4 {
return 0, ErrInvalidOperand
}
option := r.ExtShift
if option == LSL {
option = UXTW
}
return encode_addsub_ext(sf, op, s, 0, r.Index(), uint32(option), uint32(r.Amount), rn.Index(), rd.Index())
case Immediate:
if r < 0 || r > 0xFFF {
return 0, ErrInvalidOperand
}
return encode_addsub_imm(sf, op, s, 0, uint32(r), rn.Index(), rd.Index())
case ExtShiftImmediate:
if r.Immediate < 0 || r.Immediate > 0xFFF {
return 0, ErrInvalidOperand
}
if r.ExtShift != LSL || r.Amount != 12 {
return 0, ErrInvalidOperand
}
return encode_addsub_imm(sf, op, s, 1, uint32(r.Immediate), rn.Index(), rd.Index())
default:
return 0, ErrInvalidOperand
}
case CMN, CMP:
rn, err := unpackRegister(i.Operands[0])
if err != nil {
return 0, err
}
sf := flag(rn.Type() == X)
op := flag(m == CMP)
switch r := i.Operands[1].(type) {
case ExtShiftRegister:
if !rn.Type().GP() || r.Amount > 4 {
return 0, ErrInvalidOperand
}
option := r.ExtShift
if option == LSL {
option = UXTW
}
return encode_addsub_ext(sf, op, 1, 0, r.Index(), uint32(option), uint32(r.Amount), rn.Index(), 31)
case Immediate:
if r < 0 || r > 0xFFF {
return 0, ErrInvalidOperand
}
return encode_addsub_imm(sf, op, 1, 0, uint32(r), rn.Index(), 31)
case ExtShiftImmediate:
if r.Immediate < 0 || r.Immediate > 0xFFF {
return 0, ErrInvalidOperand
}
if r.ExtShift != LSL || r.Amount != 12 {
return 0, ErrInvalidOperand
}
return encode_addsub_imm(sf, op, 1, 1, uint32(r.Immediate), rn.Index(), 31)
default:
return 0, ErrNotImplemented
}
case MOV:
rd, rn, err := unpackRegister2(i)
if err != nil {
return 0, err
}
sf := flag(rd.Type() == X)
if rd.Index() == 31 || rn.Index() == 31 {
return encode_addsub_imm(sf, 0, 0, 0, 0, rn.Index(), rd.Index())
}
return 0, ErrNotImplemented
case NGC, NGCS:
rn, rm, err := unpackreg2(i)
rn, rm, err := unpackRegister2(i)
if err != nil {
return 0, err
}
Expand All @@ -60,7 +124,7 @@ func flag(b bool) uint32 {
return 0
}

func unpackreg(op Operand) (Register, error) {
func unpackRegister(op Operand) (Register, error) {
switch r := op.(type) {
case Register:
return r, nil
Expand All @@ -71,11 +135,11 @@ func unpackreg(op Operand) (Register, error) {
}
}

func unpackreg2(i *Instruction) (a, b Register, err error) {
if a, err = unpackreg(i.Operands[0]); err != nil {
func unpackRegister2(i *Instruction) (a, b Register, err error) {
if a, err = unpackRegister(i.Operands[0]); err != nil {
return
}
if b, err = unpackreg(i.Operands[1]); err != nil {
if b, err = unpackRegister(i.Operands[1]); err != nil {
return
}
if a.Type() != b.Type() {
Expand All @@ -84,14 +148,14 @@ func unpackreg2(i *Instruction) (a, b Register, err error) {
return
}

func unpackreg3(i *Instruction) (a, b, c Register, err error) {
if a, err = unpackreg(i.Operands[0]); err != nil {
func unpackRegister3(i *Instruction) (a, b, c Register, err error) {
if a, err = unpackRegister(i.Operands[0]); err != nil {
return
}
if b, err = unpackreg(i.Operands[1]); err != nil {
if b, err = unpackRegister(i.Operands[1]); err != nil {
return
}
if c, err = unpackreg(i.Operands[2]); err != nil {
if c, err = unpackRegister(i.Operands[2]); err != nil {
return
}
if a.Type() != b.Type() || b.Type() != c.Type() {
Expand Down
41 changes: 31 additions & 10 deletions disassemble.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
var ErrNotImplemented = errors.New("not implemented")
var ErrUndefined = errors.New("undefined")

// Disassemble disassembles a raw 32-bit instruction.
func Disassemble(ins uint32) (Instruction, error) {
var d decoded
if err := decode(ins, &d); err != nil {
Expand All @@ -16,17 +17,16 @@ func Disassemble(ins uint32) (Instruction, error) {

switch d.iclass {
case iclass_addsub_carry:
r := flagr(d.sf, W, X)
r := chooseRegister(d.sf, X, W)
if d.Rd == 31 && d.op == 1 {
m := flagm(d.S, NGC, NGCS)
return New(m).WithOperands(r.At(d.Rn), r.At(d.Rm)), nil
m := chooseMnemonic(d.S, NGCS, NGC)
return New(m, r.At(d.Rn), r.At(d.Rm)), nil
} else {
m := flagm(d.op, flagm(d.S, ADC, ADCS), flagm(d.S, SBC, SBCS))
return New(m).WithOperands(r.At(d.Rd), r.At(d.Rn), r.At(d.Rm)), nil
m := chooseMnemonic(d.op, chooseMnemonic(d.S, SBCS, SBC), chooseMnemonic(d.S, ADCS, ADC))
return New(m, r.At(d.Rd), r.At(d.Rn), r.At(d.Rm)), nil
}
case iclass_addsub_ext:
r := flagr(d.sf, W, X)
m := flagm(d.op, flagm(d.S, ADD, ADDS), flagm(d.S, SUB, SUBS))
r := chooseRegister(d.sf, X, W)
e := ExtShift(d.option)
amount := uint8(d.imm3)
if amount > 4 {
Expand All @@ -35,20 +35,41 @@ func Disassemble(ins uint32) (Instruction, error) {
if (d.Rd == 31 || d.Rn == 31) && e == UXTW && amount != 0 {
e = LSL
}
return New(m).WithOperands(r.At(d.Rd).SP(), r.At(d.Rn).SP(), r.At(d.Rm).ExtShift(e, amount)), nil
if d.S == 1 && d.Rd == 31 {
m := chooseMnemonic(d.op, CMP, CMN)
return New(m, r.At(d.Rn).SP(), r.At(d.Rm).ExtShift(e, amount)), nil
}
m := chooseMnemonic(d.op, chooseMnemonic(d.S, SUBS, SUB), chooseMnemonic(d.S, ADDS, ADD))
return New(m, r.At(d.Rd).SP(), r.At(d.Rn).SP(), r.At(d.Rm).ExtShift(e, amount)), nil
case iclass_addsub_imm:
r := chooseRegister(d.sf, X, W)
var op Operand
if d.sh == 1 {
op = Immediate(d.imm12).ExtShift(LSL, 12)
} else if d.imm12 == 0 && (d.Rd == 31 || d.Rn == 31) {
return New(MOV, r.At(d.Rd).SP(), r.At(d.Rn).SP()), nil
} else {
op = Immediate(d.imm12)
}
if d.S == 1 && d.Rd == 31 {
m := chooseMnemonic(d.op, CMP, CMN)
return New(m, r.At(d.Rn).SP(), op), nil
}
m := chooseMnemonic(d.op, chooseMnemonic(d.S, SUBS, SUB), chooseMnemonic(d.S, ADDS, ADD))
return New(m, r.At(d.Rd).SP(), r.At(d.Rn).SP(), op), nil
default:
return Instruction{}, fmt.Errorf("failed to diassemble %#08x: %w", ins, ErrNotImplemented)
}
}

func flagr(flag uint32, off, on RegisterType) RegisterType {
func chooseRegister(flag uint32, on, off RegisterType) RegisterType {
if flag == 1 {
return on
}
return off
}

func flagm(flag uint32, off, on Mnemonic) Mnemonic {
func chooseMnemonic(flag uint32, on, off Mnemonic) Mnemonic {
if flag == 1 {
return on
}
Expand Down
11 changes: 4 additions & 7 deletions instruction.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,15 @@ type Instruction struct {

const MaxOperands = 4

func New(mnemonic Mnemonic) Instruction {
return Instruction{Mnemonic: mnemonic}
}

func (i Instruction) WithOperands(operands ...Operand) Instruction {
func New(mnemonic Mnemonic, operands ...Operand) (ins Instruction) {
ins.Mnemonic = mnemonic
for n, operand := range operands {
if n >= MaxOperands {
panic("exceeded max operands")
}
i.Operands[n] = operand
ins.Operands[n] = operand
}
return i
return
}

func (i Instruction) String() string {
Expand Down
6 changes: 6 additions & 0 deletions mnemonic.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ const (
ADCS
ADD
ADDS
CMN
CMP
MOV
NGC
NGCS
SBC
Expand All @@ -29,6 +32,9 @@ var mnemonicStrings = []string{
"adcs",
"add",
"adds",
"cmn",
"cmp",
"mov",
"ngc",
"ngcs",
"sbc",
Expand Down
Loading

0 comments on commit 138e4a0

Please sign in to comment.