///|
// Binary (machine-code) ARM64 emitter: the self-contained counterpart of
// emit_arm64.mbt. It consumes the same post-rega IR and produces 32-bit
// instruction words plus relocations, so it can feed object/macho.mbt and the
// in-memory JIT without an assembler or linker in the loop.
//
// Scope so far: leaf integer (and double) functions. Calls, global addresses,
// float constants and single-precision ops raise an ICE instead of silently
// miscompiling; later rounds extend the covered set and add Mach-O relocations.
///|
priv struct BinFix {
word : Int
kind : Int // 0 = b, 1 = bcond
cond : Int
label : Int
}
///|
priv struct Arm64BinState {
fn_ : @types.Fn
interner : @util.Interner
apple : Bool
words : Array[Int]
labels : Map[Int, Int]
fixups : Array[BinFix]
// Direct calls: (word index of the bl, callee symbol name). Resolved either
// by the module emitter (intra-module) or turned into relocations later.
calls : Array[(Int, String)]
// Global-address fixups: (adrp word, add word, dest reg, symbol, byte offset).
addrs : Array[(Int, Int, Int, String, Int)]
mut frame : Int
mut padding : Int
}
///|
fn arm64_bin_new(
fn_ : @types.Fn,
interner : @util.Interner,
apple : Bool,
) -> Arm64BinState {
Arm64BinState::{
fn_,
interner,
apple,
words: [],
labels: Map([]),
fixups: [],
calls: [],
addrs: [],
frame: 0,
padding: 0,
}
}
///|
// Map a QBE register id (ARM64_R0.., ARM64_V0.., ARM64_SP) to an AArch64
// register number.
fn arm64_bin_reg(r : Int) -> Int raise {
if r == @types.ARM64_SP {
31
} else if r >= @types.ARM64_R0 && r <= @types.ARM64_LR {
r - @types.ARM64_R0
} else if r >= @types.ARM64_V0 && r <= @types.ARM64_V30 {
r - @types.ARM64_V0
} else {
raise @util.QbeError::Ice("arm64 bin: bad register id \{r}")
}
}
///|
// ARM condition codes for the QBE comparison codes (arm64_cond order).
let arm64_bin_cond : Array[Int] = [
0, 1, 10, 12, 13, 11, 2, 8, 9, 3, 0, 10, 12, 9, 4, 1, 7, 6,
]
///|
let arm64_bin_cond_neg : Array[Int] = [
1, 0, 11, 13, 12, 10, 3, 9, 8, 2, 1, 11, 13, 8, 5, 0, 6, 7,
]
///|
fn arm64_bin_emit(st : Arm64BinState, w : Int) -> Unit {
st.words.push(w)
}
///|
fn arm64_bin_label(st : Arm64BinState, lid : Int) -> Unit {
st.labels[lid] = st.words.length()
}
///|
fn arm64_bin_b(st : Arm64BinState, lid : Int) -> Unit {
st.fixups.push(BinFix::{
word: st.words.length(),
kind: 0,
cond: 0,
label: lid,
})
st.words.push(0)
}
///|
fn arm64_bin_bcond(st : Arm64BinState, cond : Int, lid : Int) -> Unit {
st.fixups.push(BinFix::{ word: st.words.length(), kind: 1, cond, label: lid, })
st.words.push(0)
}
///|
fn arm64_bin_resolve(st : Arm64BinState) -> Unit raise {
for fx in st.fixups {
let target = match st.labels.get(fx.label) {
Some(t) => t
None =>
raise @util.QbeError::Ice("arm64 bin: undefined label \{fx.label}")
}
let off = (target - fx.word) * 4
st.words[fx.word] = if fx.kind == 0 {
@object.enc_b(off)
} else {
@object.enc_bcond(fx.cond, off)
}
}
}
///|
// Resolve a %M memory operand to (base register, byte offset).
fn arm64_bin_mem(st : Arm64BinState, r : @types.Ref) -> (Int, Int) raise {
if r.is_tmp() {
(arm64_bin_reg(r.tmp_val()), 0)
} else if r.is_slot() {
(
29,
arm64_slot_off(
st.frame,
st.padding,
st.fn_.is_vararg,
st.apple,
r.slot_val(),
),
)
} else {
raise @util.QbeError::Ice("arm64 bin: unhandled memory operand")
}
}
///|
// Materialize a constant into a register with movz/movk. The text emitter can
// pick a single MOV bitmask-immediate; movz+movk is functionally identical and
// is byte-identical whenever the constant fits one 16-bit chunk.
fn arm64_bin_popcount(x : Int64) -> Int {
let mut v = x
let mut c = 0
while v != 0L {
v = v & (v - 1L)
c = c + 1
}
c
}
///|
fn arm64_bin_ror(x : Int64, r : Int, esize : Int) -> Int64 {
// 1L << 64 wraps in MoonBit, so the full-width mask is explicit.
let mask = if esize == 64 { -1L } else { (1L << esize) - 1L }
if r == 0 {
x & mask
} else {
((x >> r) | (x << (esize - r))) & mask
}
}
///|
fn arm64_bin_log2(e : Int) -> Int {
match e {
2 => 1
4 => 2
8 => 3
16 => 4
32 => 5
64 => 6
_ => 0
}
}
///|
// Encode a 32/64-bit value as an AArch64 bitmask immediate (N, immr, imms).
fn arm64_bin_encode_logimm(value : Int64, wide : Bool) -> (Int, Int, Int)? {
let bits = if wide { 64 } else { 32 }
let mask = if wide { -1L } else { 0xFFFFFFFFL }
let v = value & mask
if v == 0L || v == mask {
return None
}
let mut esize = 2
while esize <= bits {
if bits % esize == 0 {
let emask = if esize == 64 { -1L } else { (1L << esize) - 1L }
let pattern = v & emask
let mut rep = 0L
let mut i = 0
while i < bits {
rep = rep | (pattern << i)
i = i + esize
}
if rep == v && pattern != 0L {
let ones = arm64_bin_popcount(pattern)
let contiguous = (1L << ones) - 1L
let mut r = 0
while r < esize {
if arm64_bin_ror(contiguous, r, esize) == pattern {
let d = arm64_bin_log2(esize)
let n = if esize == 64 { 1 } else { 0 }
let not_imms = (1 << d) | (((ones - 1) ^ -1) & ((1 << d) - 1))
let imms = (not_imms ^ -1) & 0x3F
return Some((n, r, imms))
}
r = r + 1
}
}
}
esize = esize * 2
}
None
}
///|
// A value encodable as a single MOVZ/MOVN: (imm16, hw), or None.
fn arm64_bin_movz_single(value : Int64, bits : Int) -> (Int, Int)? {
if value == 0L {
return Some((0, 0))
}
let mut hw = 0
while hw < bits / 16 {
let chunk = (value >> (16 * hw)) & 0xFFFFL
if chunk != 0L {
// Guard the last chunk: value >> 64 would wrap in MoonBit.
if hw + 1 < bits / 16 && value >> (16 * (hw + 1)) != 0L {
return None
}
return Some((chunk.to_int(), hw))
}
hw = hw + 1
}
None
}
///|
fn arm64_bin_loadcon(
st : Arm64BinState,
con : @types.Con,
r : Int,
k : @types.Class,
) -> Unit raise {
if con.kind == @types.CAddr {
// adrp/add with a placeholder page/offset; the module emitter patches both
// once the data section is laid out.
let rd = arm64_bin_reg(r)
st.addrs.push(
(
st.words.length(),
st.words.length() + 1,
rd,
st.interner.get(con.label),
con.bits.i.to_int(),
),
)
st.words.push(@object.enc_adrp(rd, 0))
st.words.push(@object.enc_add_imm(rd, rd, 0))
return
}
let w = k.wide()
let sf = w != 0
let bits = if sf { 64 } else { 32 }
let mask = if sf { -1L } else { 0xFFFFFFFFL }
let mut n = con.raw_bits()
if w == 0 {
n = arm64_s32(n)
}
let rd = arm64_bin_reg(r)
let li = arm64_bin_encode_logimm(n, sf)
// The text emitter prints a single MOV when (n | 0xFFFF) == -1 (negative
// values with the low half set) or n is a logical immediate.
if (n | 0xFFFFL) == -1L || li.is_some() {
// The text emitter prints one MOV for these; mirror GAS's encoding choice
// (MOVZ, else MOVN, else the ORR bitmask immediate).
match arm64_bin_movz_single(n, bits) {
Some(p) => arm64_bin_emit(st, @object.enc_movz_sf(sf, rd, p.0, p.1))
None =>
match arm64_bin_movz_single((n ^ -1L) & mask, bits) {
Some(p) => arm64_bin_emit(st, @object.enc_movn_sf(sf, rd, p.0, p.1))
None =>
match li {
Some(t) =>
arm64_bin_emit(
st,
@object.enc_logimm_sf(sf, 1, rd, 31, t.0, t.1, t.2),
)
None => raise @util.QbeError::Ice("arm64 bin: unencodable MOV")
}
}
}
return
}
arm64_bin_emit(st, @object.enc_movz_sf(sf, rd, (n & 0xFFFFL).to_int(), 0))
let mut sh = 16
let mut m = n >> 16
while m != 0L {
if (w == 0 && sh == 32) || sh == 64 {
break
}
arm64_bin_emit(
st,
@object.enc_movk_sf(sf, rd, (m & 0xFFFFL).to_int(), sh / 16),
)
m = m >> 16
sh = sh + 16
}
}
///|
// Decode an immediate operand that must fit one 12-bit ADD/SUB immediate.
fn arm64_bin_imm12(st : Arm64BinState, r : @types.Ref) -> Int raise {
if r.is_con() {
let n = st.fn_.cons[r.con_val()].bits.i.reinterpret_as_uint64()
if n >> 12 == 0UL {
n.to_int()
} else if (n & 0xFFFUL) == 0UL && n >> 24 == 0UL {
raise @util.QbeError::Ice(
"arm64 bin: imm12 with lsl #12 not supported yet",
)
} else {
raise @util.QbeError::Ice("arm64 bin: immediate does not fit #imm12")
}
} else {
raise @util.QbeError::Ice("arm64 bin: expected an immediate operand")
}
}
///|
fn arm64_bin_r(st : Arm64BinState, r : @types.Ref) -> Int raise {
if r.is_tmp() {
arm64_bin_reg(r.tmp_val())
} else {
raise @util.QbeError::Ice("arm64 bin: expected a register operand")
}
}
///|
fn arm64_bin_table(st : Arm64BinState, i : @types.Ins) -> Unit raise {
// Flag-setting ops (Acmp/Acmn/Afcmp) and stores have no destination.
let rd = if i.to.is_tmp() { arm64_bin_reg(i.to.tmp_val()) } else { 0 }
let sf = i.cls == @types.Kl
let is_d = i.cls == @types.Kd
let is_s = i.cls == @types.Ks
if is_s {
// Single-precision arithmetic has its own opcodes; handle it before the
// integer fallthrough so it can never be silently miscompiled.
match i.op {
@types.Add => {
arm64_bin_emit(
st,
@object.enc_fadd_s(
rd,
arm64_bin_r(st, i.arg1),
arm64_bin_r(st, i.arg2),
),
)
return
}
@types.Sub => {
arm64_bin_emit(
st,
@object.enc_fsub_s(
rd,
arm64_bin_r(st, i.arg1),
arm64_bin_r(st, i.arg2),
),
)
return
}
@types.Mul => {
arm64_bin_emit(
st,
@object.enc_fmul_s(
rd,
arm64_bin_r(st, i.arg1),
arm64_bin_r(st, i.arg2),
),
)
return
}
@types.Div => {
arm64_bin_emit(
st,
@object.enc_fdiv_s(
rd,
arm64_bin_r(st, i.arg1),
arm64_bin_r(st, i.arg2),
),
)
return
}
@types.Afcmp => {
arm64_bin_emit(
st,
@object.enc_fcmpe_s(arm64_bin_r(st, i.arg1), arm64_bin_r(st, i.arg2)),
)
return
}
_ => ()
}
}
match i.op {
@types.Add =>
if is_d {
arm64_bin_emit(
st,
@object.enc_fadd_d(
rd,
arm64_bin_r(st, i.arg1),
arm64_bin_r(st, i.arg2),
),
)
} else if i.arg2.is_con() {
arm64_bin_emit(
st,
@object.enc_add_imm_sf(
sf,
rd,
arm64_bin_r(st, i.arg1),
arm64_bin_imm12(st, i.arg2),
),
)
} else {
let rn = arm64_bin_r(st, i.arg1)
let rm = arm64_bin_r(st, i.arg2)
if rd == 31 || rn == 31 {
arm64_bin_emit(st, @object.enc_add_ext(rd, rn, rm))
} else {
arm64_bin_emit(st, @object.enc_add_reg_sf(sf, rd, rn, rm))
}
}
@types.Sub =>
if is_d {
arm64_bin_emit(
st,
@object.enc_fsub_d(
rd,
arm64_bin_r(st, i.arg1),
arm64_bin_r(st, i.arg2),
),
)
} else if i.arg2.is_con() {
arm64_bin_emit(
st,
@object.enc_sub_imm_sf(
sf,
rd,
arm64_bin_r(st, i.arg1),
arm64_bin_imm12(st, i.arg2),
),
)
} else {
let rn = arm64_bin_r(st, i.arg1)
let rm = arm64_bin_r(st, i.arg2)
if rd == 31 || rn == 31 {
arm64_bin_emit(st, @object.enc_sub_ext(rd, rn, rm))
} else {
arm64_bin_emit(st, @object.enc_sub_reg_sf(sf, rd, rn, rm))
}
}
@types.Mul =>
if is_d {
arm64_bin_emit(
st,
@object.enc_fmul_d(
rd,
arm64_bin_r(st, i.arg1),
arm64_bin_r(st, i.arg2),
),
)
} else {
arm64_bin_emit(
st,
@object.enc_mul_sf(
sf,
rd,
arm64_bin_r(st, i.arg1),
arm64_bin_r(st, i.arg2),
),
)
}
@types.Div =>
if is_d {
arm64_bin_emit(
st,
@object.enc_fdiv_d(
rd,
arm64_bin_r(st, i.arg1),
arm64_bin_r(st, i.arg2),
),
)
} else {
arm64_bin_emit(
st,
@object.enc_sdiv_sf(
sf,
rd,
arm64_bin_r(st, i.arg1),
arm64_bin_r(st, i.arg2),
),
)
}
@types.Udiv =>
arm64_bin_emit(
st,
@object.enc_udiv_sf(
sf,
rd,
arm64_bin_r(st, i.arg1),
arm64_bin_r(st, i.arg2),
),
)
@types.Rem | @types.Urem => {
let rn = arm64_bin_r(st, i.arg1)
let rm = arm64_bin_r(st, i.arg2)
let ip1 = @types.ARM64_IP1 - @types.ARM64_R0
if i.op == @types.Rem {
arm64_bin_emit(st, @object.enc_sdiv_sf(sf, ip1, rn, rm))
} else {
arm64_bin_emit(st, @object.enc_udiv_sf(sf, ip1, rn, rm))
}
arm64_bin_emit(st, @object.enc_msub_sf(sf, rd, ip1, rm, rn))
}
@types.And | @types.Or | @types.Xor => {
let rn = arm64_bin_r(st, i.arg1)
let opc = match i.op {
@types.And => 0
@types.Or => 1
_ => 2
}
if i.arg2.is_con() {
let v = st.fn_.cons[i.arg2.con_val()].raw_bits()
match arm64_bin_encode_logimm(v, sf) {
Some(t) =>
arm64_bin_emit(
st,
@object.enc_logimm_sf(sf, opc, rd, rn, t.0, t.1, t.2),
)
None =>
raise @util.QbeError::Ice(
"arm64 bin: unencodable logical immediate",
)
}
} else {
let rm = arm64_bin_r(st, i.arg2)
match i.op {
@types.And =>
arm64_bin_emit(st, @object.enc_and_reg_sf(sf, rd, rn, rm))
@types.Or =>
arm64_bin_emit(st, @object.enc_orr_reg_sf(sf, rd, rn, rm))
_ => arm64_bin_emit(st, @object.enc_eor_reg_sf(sf, rd, rn, rm))
}
}
}
@types.Shl =>
arm64_bin_emit(
st,
@object.enc_lsl_sf(
sf,
rd,
arm64_bin_r(st, i.arg1),
arm64_bin_r(st, i.arg2),
),
)
@types.Shr =>
arm64_bin_emit(
st,
@object.enc_lsr_sf(
sf,
rd,
arm64_bin_r(st, i.arg1),
arm64_bin_r(st, i.arg2),
),
)
@types.Sar =>
arm64_bin_emit(
st,
@object.enc_asr_sf(
sf,
rd,
arm64_bin_r(st, i.arg1),
arm64_bin_r(st, i.arg2),
),
)
@types.Acmp =>
if i.arg2.is_con() {
arm64_bin_emit(
st,
@object.enc_cmp_imm_sf(
sf,
arm64_bin_r(st, i.arg1),
arm64_bin_imm12(st, i.arg2),
),
)
} else {
arm64_bin_emit(
st,
@object.enc_cmp_sf(
sf,
arm64_bin_r(st, i.arg1),
arm64_bin_r(st, i.arg2),
),
)
}
@types.Acmn =>
if i.arg2.is_con() {
arm64_bin_emit(
st,
@object.enc_cmn_imm_sf(
sf,
arm64_bin_r(st, i.arg1),
arm64_bin_imm12(st, i.arg2),
),
)
} else {
arm64_bin_emit(
st,
@object.enc_cmn_sf(
sf,
arm64_bin_r(st, i.arg1),
arm64_bin_r(st, i.arg2),
),
)
}
@types.Afcmp =>
if is_d {
arm64_bin_emit(
st,
@object.enc_fcmpe_d(arm64_bin_r(st, i.arg1), arm64_bin_r(st, i.arg2)),
)
} else {
raise @util.QbeError::Ice("arm64 bin: unsupported fp compare class")
}
@types.Extsb =>
arm64_bin_emit(st, @object.enc_sxtb_sf(sf, rd, arm64_bin_r(st, i.arg1)))
@types.Extsh =>
arm64_bin_emit(st, @object.enc_sxth_sf(sf, rd, arm64_bin_r(st, i.arg1)))
@types.Extsw =>
arm64_bin_emit(st, @object.enc_sxtw(rd, arm64_bin_r(st, i.arg1)))
@types.Extub =>
arm64_bin_emit(st, @object.enc_uxtb_sf(sf, rd, arm64_bin_r(st, i.arg1)))
@types.Extuh =>
arm64_bin_emit(st, @object.enc_uxth_sf(sf, rd, arm64_bin_r(st, i.arg1)))
@types.Extuw =>
// Zero-extend word to long: a 32-bit mov clears the upper half.
arm64_bin_emit(
st,
@object.enc_mov_reg_sf(false, rd, arm64_bin_r(st, i.arg1)),
)
@types.Exts =>
arm64_bin_emit(st, @object.enc_fcvt_d_s(rd, arm64_bin_r(st, i.arg1)))
@types.Truncd =>
arm64_bin_emit(st, @object.enc_fcvt_s_d(rd, arm64_bin_r(st, i.arg1)))
@types.Stosi =>
if sf {
arm64_bin_emit(st, @object.enc_fcvtzs_x_s(rd, arm64_bin_r(st, i.arg1)))
} else {
arm64_bin_emit(st, @object.enc_fcvtzs_w_s(rd, arm64_bin_r(st, i.arg1)))
}
@types.Dtosi =>
if sf {
arm64_bin_emit(st, @object.enc_fcvtzs_x_d(rd, arm64_bin_r(st, i.arg1)))
} else {
arm64_bin_emit(st, @object.enc_fcvtzs_w_d(rd, arm64_bin_r(st, i.arg1)))
}
@types.Swtof =>
if is_d {
arm64_bin_emit(st, @object.enc_scvtf_d_w(rd, arm64_bin_r(st, i.arg1)))
} else {
arm64_bin_emit(st, @object.enc_scvtf_s_w(rd, arm64_bin_r(st, i.arg1)))
}
@types.Sltof =>
if is_d {
arm64_bin_emit(st, @object.enc_scvtf_d_x(rd, arm64_bin_r(st, i.arg1)))
} else {
arm64_bin_emit(st, @object.enc_scvtf_s_x(rd, arm64_bin_r(st, i.arg1)))
}
@types.Cast =>
match i.cls {
@types.Kw =>
arm64_bin_emit(st, @object.enc_fmov_w_s(rd, arm64_bin_r(st, i.arg1)))
@types.Kl =>
arm64_bin_emit(st, @object.enc_fmov_x_d(rd, arm64_bin_r(st, i.arg1)))
@types.Ks =>
arm64_bin_emit(st, @object.enc_fmov_s_w(rd, arm64_bin_r(st, i.arg1)))
@types.Kd =>
arm64_bin_emit(st, @object.enc_fmov_d_x(rd, arm64_bin_r(st, i.arg1)))
_ => raise @util.QbeError::Ice("arm64 bin: bad cast class")
}
@types.Load => {
let (rn, off) = arm64_bin_mem(st, i.arg1)
let size = if i.cls == @types.Kl || i.cls == @types.Kd { 3 } else { 2 }
if i.cls == @types.Ks || i.cls == @types.Kd {
arm64_bin_emit(st, @object.enc_ldst_fp(size, 1, rd, rn, off))
} else {
arm64_bin_emit(st, @object.enc_ldst(size, 1, rd, rn, off))
}
}
@types.Loadub => {
let (rn, off) = arm64_bin_mem(st, i.arg1)
arm64_bin_emit(st, @object.enc_ldst(0, 1, rd, rn, off))
}
@types.Loaduh => {
let (rn, off) = arm64_bin_mem(st, i.arg1)
arm64_bin_emit(st, @object.enc_ldst(1, 1, rd, rn, off))
}
@types.Loaduw => {
let (rn, off) = arm64_bin_mem(st, i.arg1)
arm64_bin_emit(st, @object.enc_ldst(2, 1, rd, rn, off))
}
@types.Loadsw => {
let (rn, off) = arm64_bin_mem(st, i.arg1)
let opc = if i.cls == @types.Kl { 2 } else { 1 }
arm64_bin_emit(st, @object.enc_ldst(2, opc, rd, rn, off))
}
@types.Loadsb | @types.Loadsh => {
let (rn, off) = arm64_bin_mem(st, i.arg1)
let size = if i.op == @types.Loadsb { 0 } else { 1 }
let opc = if i.cls == @types.Kl { 2 } else { 3 }
arm64_bin_emit(st, @object.enc_ldst(size, opc, rd, rn, off))
}
@types.Storeb
| @types.Storeh
| @types.Storew
| @types.Storel
| @types.Stores
| @types.Stored => {
let (rn, off) = arm64_bin_mem(st, i.arg2)
let rt = arm64_bin_reg(i.arg1.tmp_val())
let (size, opc) = match i.op {
@types.Storeb => (0, 0)
@types.Storeh => (1, 0)
@types.Storew => (2, 0)
@types.Stores => (2, 0)
@types.Stored => (3, 0)
_ => (3, 0)
}
if i.op == @types.Stores || i.op == @types.Stored {
arm64_bin_emit(st, @object.enc_ldst_fp(size, opc, rt, rn, off))
} else {
arm64_bin_emit(st, @object.enc_ldst(size, opc, rt, rn, off))
}
}
@types.Swap => {
let r1 = arm64_bin_r(st, i.arg1)
let r2 = arm64_bin_r(st, i.arg2)
if is_d {
// The text emitter's float scratch is s31/d31, not the integer IP1.
arm64_bin_emit(st, @object.enc_fmov_d(31, r1))
arm64_bin_emit(st, @object.enc_fmov_d(r1, r2))
arm64_bin_emit(st, @object.enc_fmov_d(r2, 31))
} else if is_s {
arm64_bin_emit(st, @object.enc_fmov_s(31, r1))
arm64_bin_emit(st, @object.enc_fmov_s(r1, r2))
arm64_bin_emit(st, @object.enc_fmov_s(r2, 31))
} else {
let ip1 = @types.ARM64_IP1 - @types.ARM64_R0
arm64_bin_emit(st, @object.enc_mov_reg_sf(sf, ip1, r1))
arm64_bin_emit(st, @object.enc_mov_reg_sf(sf, r1, r2))
arm64_bin_emit(st, @object.enc_mov_reg_sf(sf, r2, ip1))
}
}
_ => raise @util.QbeError::Ice("arm64 bin: unsupported op " + i.op.name())
}
}
///|
fn arm64_bin_ins(st : Arm64BinState, i : @types.Ins) -> Unit raise {
let oi = i.op.index()
let fi = @types.Flagieq.index()
if oi >= fi && oi <= @types.Flagfuo.index() {
let c = oi - fi
let rd = arm64_bin_reg(i.to.tmp_val())
arm64_bin_emit(
st,
@object.enc_cset(i.cls == @types.Kl, rd, arm64_bin_cond[c]),
)
return
}
match i.op {
@types.Nop => ()
@types.Copy =>
if i.to.eq(i.arg1) {
()
} else if i.arg1.is_con() {
arm64_bin_loadcon(
st,
st.fn_.cons[i.arg1.con_val()],
i.to.tmp_val(),
i.cls,
)
} else if i.cls == @types.Kd {
arm64_bin_emit(
st,
@object.enc_fmov_d(
arm64_bin_reg(i.to.tmp_val()),
arm64_bin_r(st, i.arg1),
),
)
} else if i.cls == @types.Ks {
arm64_bin_emit(
st,
@object.enc_fmov_s(
arm64_bin_reg(i.to.tmp_val()),
arm64_bin_r(st, i.arg1),
),
)
} else {
arm64_bin_emit(
st,
@object.enc_mov_reg_sf(
i.cls == @types.Kl,
arm64_bin_reg(i.to.tmp_val()),
arm64_bin_r(st, i.arg1),
),
)
}
@types.Addr =>
if i.arg1.is_slot() {
arm64_bin_emit(
st,
@object.enc_add_imm(
arm64_bin_reg(i.to.tmp_val()),
29,
arm64_slot_off(
st.frame,
st.padding,
st.fn_.is_vararg,
st.apple,
i.arg1.slot_val(),
),
),
)
} else {
raise @util.QbeError::Ice("arm64 bin: invalid address")
}
@types.Call | @types.Vacall =>
// The ABI pass has already placed the arguments in x0..x7, so a direct
// call is just bl; the result is left in x0 for later copies.
if i.arg1.is_con() {
let c = st.fn_.cons[i.arg1.con_val()]
if c.kind == @types.CAddr && !c.is_local {
st.calls.push((st.words.length(), st.interner.get(c.label)))
st.words.push(@object.enc_bl(0))
} else {
raise @util.QbeError::Ice("arm64 bin: unsupported call target")
}
} else {
// Indirect call through a register.
arm64_bin_emit(st, @object.enc_blr(arm64_bin_r(st, i.arg1)))
}
@types.Salloc => {
let rn = arm64_bin_r(st, i.arg1)
arm64_bin_emit(st, @object.enc_sub_ext(31, 31, rn))
if !i.to.is_none() {
arm64_bin_emit(
st,
@object.enc_add_imm(arm64_bin_reg(i.to.tmp_val()), 31, 0),
)
}
}
_ => arm64_bin_table(st, i)
}
}
///|
fn arm64_bin_epilogue(st : Arm64BinState) -> Unit raise {
let fn_ = st.fn_
let mut o = st.frame + 16
for r in @types.arm64_rclob {
if (fn_.reg & (1UL << r)) != 0UL {
o = o - 8
let rr = arm64_bin_reg(r)
if r >= @types.ARM64_V0 {
arm64_bin_emit(st, @object.enc_ldst_fp(3, 1, rr, 29, o))
} else {
arm64_bin_emit(st, @object.enc_ldr_x(rr, 29, o))
}
}
}
if fn_.has_dynalloc {
arm64_bin_emit(st, @object.enc_add_imm(31, 29, 0))
}
let mut oo = st.frame + 16
if fn_.is_vararg && !st.apple {
oo = oo + 192
}
if oo > 504 {
arm64_bin_emit(st, @object.enc_ldp_post(29, 30, 31, 16))
arm64_bin_emit(st, @object.enc_add_imm(31, 31, oo - 16))
} else {
arm64_bin_emit(st, @object.enc_ldp_post(29, 30, 31, oo))
}
arm64_bin_emit(st, @object.enc_ret())
}
///|
fn arm64_binfn(st : Arm64BinState) -> Unit raise {
let fn_ = st.fn_
// framelayout (C framelayout)
let mut o = 0
for r in @types.arm64_rclob {
if (fn_.reg & (1UL << r)) != 0UL {
o = o + 1
}
}
let mut f = fn_.slot
f = (f + 3) & -4
o = o + (o & 1)
st.padding = 4 * (f - fn_.slot)
st.frame = 4 * f + 8 * o
// prologue
arm64_bin_emit(st, @object.enc_hint(34))
if fn_.is_vararg && !st.apple {
raise @util.QbeError::Ice("arm64 bin: vararg prologue not supported yet")
}
if st.frame + 16 > 512 {
arm64_bin_emit(st, @object.enc_sub_imm(31, 31, st.frame))
arm64_bin_emit(st, @object.enc_stp_pre(29, 30, 31, -16))
} else {
arm64_bin_emit(st, @object.enc_stp_pre(29, 30, 31, -(st.frame + 16)))
}
arm64_bin_emit(st, @object.enc_add_imm(29, 31, 0))
o = st.frame + 16
for r in @types.arm64_rclob {
if (fn_.reg & (1UL << r)) != 0UL {
o = o - 8
let rr = arm64_bin_reg(r)
if r >= @types.ARM64_V0 {
arm64_bin_emit(st, @object.enc_ldst_fp(3, 0, rr, 29, o))
} else {
arm64_bin_emit(st, @object.enc_str_x(rr, 29, o))
}
}
}
// body
let mut lbl = false
for idx in 0.. 1 {
arm64_bin_label(st, arm64_emit_id0[0] + b.rpo_id)
}
for ins in b.ins {
arm64_bin_ins(st, ins)
}
lbl = true
match b.jmp.kind {
@types.Jret0 => arm64_bin_epilogue(st)
@types.Jjmp =>
if b.jmp.s1 >= 0 && b.jmp.s1 != next {
arm64_bin_b(st, arm64_emit_id0[0] + fn_.blks[b.jmp.s1].rpo_id)
} else {
lbl = false
}
_ => {
let c = b.jmp.kind.index() - @types.Jjfieq.index()
if c < 0 || c > 17 {
raise @util.QbeError::Ice("arm64 bin: unhandled jump")
}
let mut s1 = b.jmp.s1
let mut s2 = b.jmp.s2
let mut neg = false
if next == b.jmp.s2 {
let t = s1
s1 = s2
s2 = t
} else {
neg = true
}
arm64_bin_bcond(
st,
if neg {
arm64_bin_cond_neg[c]
} else {
arm64_bin_cond[c]
},
arm64_emit_id0[0] + fn_.blks[s2].rpo_id,
)
if s1 >= 0 && s1 != next {
arm64_bin_b(st, arm64_emit_id0[0] + fn_.blks[s1].rpo_id)
} else {
lbl = false
}
}
}
}
arm64_emit_id0[0] = arm64_emit_id0[0] + fn_.rpo.length()
arm64_bin_resolve(st)
}
///|
// Emit one function as words plus its unresolved direct calls.
pub fn emit_arm64_bin_fn_ex(
fn_ : @types.Fn,
interner : @util.Interner,
apple : Bool,
) -> (Array[Int], Array[(Int, String)], Array[(Int, Int, Int, String, Int)]) raise {
let st = arm64_bin_new(fn_, interner, apple)
arm64_binfn(st)
(st.words, st.calls, st.addrs)
}
///|
// Emit one function as a flat array of 32-bit instruction words. Only valid for
// functions without direct calls (use the module emitter otherwise).
pub fn emit_arm64_bin_fn(
fn_ : @types.Fn,
interner : @util.Interner,
apple : Bool,
) -> Array[Int] raise {
let (words, calls, addrs) = emit_arm64_bin_fn_ex(fn_, interner, apple)
if calls.length() > 0 || addrs.length() > 0 {
raise @util.QbeError::Ice(
"arm64 bin: function needs module linking; use emit_arm64_bin_module",
)
}
words
}
///|
// Little-endian write of the low n bytes of v.
fn arm64_bin_put_le(buf : Array[Int], off : Int, v : Int64, n : Int) -> Unit {
let mut x = v
for i in 0..> 8
}
}
///|
fn arm64_bin_align_up(v : Int, a : Int) -> Int {
if a <= 1 {
v
} else {
(v + a - 1) / a * a
}
}
///|
fn arm64_bin_dat_width(kind : @types.DatKind) -> Int {
match kind {
@types.DB => 1
@types.DH => 2
@types.DW => 4
@types.DL => 8
_ => 0
}
}
///|
fn arm64_bin_dat_size(d : @types.Dat) -> Int {
if d.is_str {
@utf8.encode(d.str).length()
} else {
arm64_bin_dat_width(d.kind)
}
}
///|
// Lay the module's data items out as a flat byte image, returning the address
// of every named global (byte offset within the image).
fn arm64_bin_layout_data(
datas : Array[@types.Dat],
) -> (Array[(String, Int)], Array[Int], Array[(Int, String, Int)]) {
let addr_of : Array[(String, Int)] = []
// Relocatable pointer entries: (byte offset, target symbol, byte width).
let refs : Array[(Int, String, Int)] = []
let mut off = 0
let mut align = 8
for d in datas {
match d.kind {
@types.DStart => align = 8
@types.DAlign => align = d.num.to_int()
@types.DName => {
off = arm64_bin_align_up(off, align)
addr_of.push((d.str, off))
}
@types.DEnd => ()
@types.DZ => off = off + d.num.to_int()
_ => off = off + arm64_bin_dat_size(d)
}
}
let buf : Array[Int] = Array::make(off, 0)
let mut cur = 0
let mut align = 8
for d in datas {
match d.kind {
@types.DStart => align = 8
@types.DAlign => align = d.num.to_int()
@types.DName => cur = arm64_bin_align_up(cur, align)
@types.DEnd => ()
@types.DZ => cur = cur + d.num.to_int()
_ => {
let w = arm64_bin_dat_size(d)
if d.is_str {
let b = @utf8.encode(d.str)
for i in 0.. byte offset, bytes).
fn arm64_fp_layout() -> (Array[(String, Int)], Array[Int]) {
let syms : Array[(String, Int)] = []
let buf : Array[Int] = []
for i in 0..<@types.fp_stash_len() {
let e = @types.fp_stash_at(i)
let n = if e.size == 16 { 16 } else if e.size == 8 { 8 } else { 4 }
let align = if n == 16 { 16 } else if n == 8 { 8 } else { 4 }
while buf.length() % align != 0 {
buf.push(0)
}
syms.push(("fp" + i.to_string(), buf.length()))
let mut x = e.b0
for _ in 0..> 8
}
}
(syms, buf)
}
///|
// A linked module image: text words plus a data byte image with every
// intra-module direct call and global address already patched.
pub(all) struct Arm64BinModule {
words : Array[Int]
data : Array[Int] // data bytes
// function name -> word offset within the text
fns : Array[(String, Int)]
// global name -> byte offset within the data image
datas : Array[(String, Int)]
exports : Array[String]
// Byte length of the executable code region. When the module has data this is
// page-aligned so the data can stay writable while the code is mprotected RX.
code_bytes : Int
}
///|
// The executable image: text bytes, padded to 16, then the data bytes. Global
// addresses were computed against exactly this layout.
pub fn Arm64BinModule::image(self : Arm64BinModule) -> Bytes {
let a : Array[Byte] = []
for w in self.words {
a.push((w & 0xFF).to_byte())
a.push(((w >> 8) & 0xFF).to_byte())
a.push(((w >> 16) & 0xFF).to_byte())
a.push(((w >> 24) & 0xFF).to_byte())
}
while a.length() < self.code_bytes {
a.push((0).to_byte())
}
for b in self.data {
a.push(b.to_byte())
}
Bytes::from_array(a[:])
}
///|
pub fn emit_arm64_bin_module(
funcs : Array[@types.Fn],
datas : Array[@types.Dat],
interner : @util.Interner,
apple : Bool,
) -> Arm64BinModule raise {
arm64_emit_reset()
let words : Array[Int] = []
let fns : Array[(String, Int)] = []
let pending : Array[(Int, String)] = []
let pending_addrs : Array[(Int, Int, Int, String, Int)] = []
for fn_ in funcs {
let (w, calls, addrs) = emit_arm64_bin_fn_ex(fn_, interner, apple)
let off = words.length()
fns.push((fn_.name, off))
for x in w {
words.push(x)
}
for c in calls {
pending.push((off + c.0, c.1))
}
for a in addrs {
pending_addrs.push((off + a.0, off + a.1, a.2, a.3, a.4))
}
}
for c in pending {
let mut target = -1
for f in fns {
if f.0 == c.1 {
target = f.1
}
}
if target < 0 {
raise @util.QbeError::Ice("arm64 bin: call to undefined symbol " + c.1)
}
words[c.0] = @object.enc_bl((target - c.0) * 4)
}
let (datas_map0, data_bytes0, _data_refs) = arm64_bin_layout_data(datas)
// Append the floating-point constant stash (fpN) after the data section.
let (fp_map, fp_bytes) = arm64_fp_layout()
let fp_base = arm64_bin_align_up(data_bytes0.length(), 16)
let data_bytes = data_bytes0.copy()
while data_bytes.length() < fp_base {
data_bytes.push(0)
}
for b in fp_bytes {
data_bytes.push(b)
}
let datas_map = datas_map0.copy()
for p in fp_map {
datas_map.push((p.0, fp_base + p.1))
}
let text_bytes = words.length() * 4
// Keep the data region in its own page(s): mprotect() protects whole pages,
// and the smallest page size the JIT might run on is 4KB while macOS/aarch64
// uses 16KB, so align well above both.
let code_bytes = if data_bytes.length() > 0 {
arm64_bin_align_up(text_bytes, 65536)
} else {
text_bytes
}
let data_start = code_bytes
for a in pending_addrs {
let mut base = -1
for p in datas_map {
if p.0 == a.3 {
base = p.1
}
}
if base < 0 {
raise @util.QbeError::Ice("arm64 bin: undefined data symbol " + a.3)
}
let target = data_start + base + a.4
let pc = a.0 * 4
words[a.0] = @object.enc_adrp(a.2, (target >> 12) - (pc >> 12))
words[a.1] = @object.enc_add_imm(a.2, a.2, target & 0xFFF)
}
let exports : Array[String] = []
for fn_ in funcs {
if fn_.is_export {
exports.push(fn_.name)
}
}
Arm64BinModule::{
words,
data: data_bytes,
fns,
datas: datas_map,
exports,
code_bytes,
}
}