mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 16:21:11 +00:00
add zero and sign extension
This commit is contained in:
parent
f55f82119c
commit
bed779a290
4 changed files with 162 additions and 23 deletions
|
@ -480,6 +480,37 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32, size: u8) {
|
||||
debug_assert!(size <= 8);
|
||||
if size == 8 {
|
||||
Self::mov_reg64_base32(buf, dst, offset);
|
||||
} else if size == 4 {
|
||||
todo!("sign extending 4 byte values");
|
||||
} else if size == 2 {
|
||||
todo!("sign extending 2 byte values");
|
||||
} else if size == 1 {
|
||||
todo!("sign extending 1 byte values");
|
||||
} else {
|
||||
internal_error!("Invalid size for sign extension: {}", size);
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32, size: u8) {
|
||||
debug_assert!(size <= 8);
|
||||
if size == 8 {
|
||||
Self::mov_reg64_base32(buf, dst, offset);
|
||||
} else if size == 4 {
|
||||
todo!("zero extending 4 byte values");
|
||||
} else if size == 2 {
|
||||
todo!("zero extending 2 byte values");
|
||||
} else if size == 1 {
|
||||
todo!("zero extending 1 byte values");
|
||||
} else {
|
||||
internal_error!("Invalid size for zero extension: {}", size);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mov_freg64_stack32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _offset: i32) {
|
||||
todo!("loading floating point reg from stack for AArch64");
|
||||
|
|
|
@ -172,6 +172,13 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized {
|
|||
fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
|
||||
fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
|
||||
|
||||
/// Sign extends the data at `offset` with `size` as it copies it to `dst`
|
||||
/// size must be less than or equal to 8.
|
||||
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, size: u8);
|
||||
/// Zero extends the data at `offset` with `size` as it copies it to `dst`
|
||||
/// size must be less than or equal to 8.
|
||||
fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, size: u8);
|
||||
|
||||
fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32);
|
||||
fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
|
||||
fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
|
||||
|
@ -1003,6 +1010,13 @@ impl<
|
|||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! sign_extended_builtins {
|
||||
() => {
|
||||
Builtin::Int(IntWidth::I8 | IntWidth::I16 | IntWidth::I32 | IntWidth::I64 | IntWidth::I128)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! single_register_int_builtins {
|
||||
() => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
generic64::{Assembler, CallConv, RegTrait},
|
||||
single_register_floats, single_register_int_builtins, single_register_integers,
|
||||
single_register_layouts, Env,
|
||||
sign_extended_builtins, single_register_floats, single_register_int_builtins,
|
||||
single_register_integers, single_register_layouts, Env,
|
||||
};
|
||||
use bumpalo::collections::Vec;
|
||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
|
@ -49,6 +49,9 @@ enum StackStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
|
|||
base_offset: i32,
|
||||
// Size on the stack in bytes.
|
||||
size: u32,
|
||||
// Whether or not the data is need to be sign extended on load.
|
||||
// If not, it must be zero extended.
|
||||
sign_extend: bool,
|
||||
},
|
||||
/// Complex data (lists, unions, structs, str) stored on the stack.
|
||||
/// Note, this is also used for referencing a value within a struct/union.
|
||||
|
@ -324,20 +327,22 @@ impl<
|
|||
);
|
||||
reg
|
||||
}
|
||||
Stack(ReferencedPrimitive { base_offset, size })
|
||||
if base_offset % 8 == 0 && size == 8 =>
|
||||
{
|
||||
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
|
||||
Stack(ReferencedPrimitive {
|
||||
base_offset,
|
||||
size,
|
||||
sign_extend,
|
||||
}) => {
|
||||
let reg = self.get_general_reg(buf);
|
||||
ASM::mov_reg64_base32(buf, reg, base_offset);
|
||||
if sign_extend {
|
||||
ASM::movsx_reg64_base32(buf, reg, base_offset, size as u8);
|
||||
} else {
|
||||
ASM::movzx_reg64_base32(buf, reg, base_offset, size as u8);
|
||||
}
|
||||
self.general_used_regs.push((reg, *sym));
|
||||
self.symbol_storage_map.insert(*sym, Reg(General(reg)));
|
||||
self.free_reference(sym);
|
||||
reg
|
||||
}
|
||||
Stack(ReferencedPrimitive { .. }) => {
|
||||
todo!("loading referenced primitives")
|
||||
}
|
||||
Stack(Complex { .. }) => {
|
||||
internal_error!("Cannot load large values into general registers: {}", sym)
|
||||
}
|
||||
|
@ -386,9 +391,9 @@ impl<
|
|||
);
|
||||
reg
|
||||
}
|
||||
Stack(ReferencedPrimitive { base_offset, size })
|
||||
if base_offset % 8 == 0 && size == 8 =>
|
||||
{
|
||||
Stack(ReferencedPrimitive {
|
||||
base_offset, size, ..
|
||||
}) if base_offset % 8 == 0 && size == 8 => {
|
||||
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
|
||||
let reg = self.get_float_reg(buf);
|
||||
ASM::mov_freg64_base32(buf, reg, base_offset);
|
||||
|
@ -445,9 +450,9 @@ impl<
|
|||
debug_assert_eq!(base_offset % 8, 0);
|
||||
ASM::mov_reg64_base32(buf, reg, *base_offset);
|
||||
}
|
||||
Stack(ReferencedPrimitive { base_offset, size })
|
||||
if base_offset % 8 == 0 && *size == 8 =>
|
||||
{
|
||||
Stack(ReferencedPrimitive {
|
||||
base_offset, size, ..
|
||||
}) if base_offset % 8 == 0 && *size == 8 => {
|
||||
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
|
||||
ASM::mov_reg64_base32(buf, reg, *base_offset);
|
||||
}
|
||||
|
@ -494,9 +499,9 @@ impl<
|
|||
debug_assert_eq!(base_offset % 8, 0);
|
||||
ASM::mov_freg64_base32(buf, reg, *base_offset);
|
||||
}
|
||||
Stack(ReferencedPrimitive { base_offset, size })
|
||||
if base_offset % 8 == 0 && *size == 8 =>
|
||||
{
|
||||
Stack(ReferencedPrimitive {
|
||||
base_offset, size, ..
|
||||
}) if base_offset % 8 == 0 && *size == 8 => {
|
||||
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
|
||||
ASM::mov_freg64_base32(buf, reg, *base_offset);
|
||||
}
|
||||
|
@ -544,6 +549,10 @@ impl<
|
|||
ReferencedPrimitive {
|
||||
base_offset: data_offset,
|
||||
size,
|
||||
sign_extend: matches!(
|
||||
layout,
|
||||
Layout::Builtin(sign_extended_builtins!())
|
||||
),
|
||||
}
|
||||
} else {
|
||||
Complex {
|
||||
|
@ -589,6 +598,7 @@ impl<
|
|||
Stack(ReferencedPrimitive {
|
||||
base_offset: union_offset + id_offset as i32,
|
||||
size,
|
||||
sign_extend: matches!(id_builtin, sign_extended_builtins!()),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
@ -770,9 +780,12 @@ impl<
|
|||
pub fn stack_offset_and_size(&self, sym: &Symbol) -> (i32, u32) {
|
||||
match self.get_storage_for_sym(sym) {
|
||||
Stack(Primitive { base_offset, .. }) => (*base_offset, 8),
|
||||
Stack(ReferencedPrimitive { base_offset, size } | Complex { base_offset, size }) => {
|
||||
(*base_offset, *size)
|
||||
}
|
||||
Stack(
|
||||
ReferencedPrimitive {
|
||||
base_offset, size, ..
|
||||
}
|
||||
| Complex { base_offset, size },
|
||||
) => (*base_offset, *size),
|
||||
storage => {
|
||||
internal_error!(
|
||||
"Data not on the stack for sym ({}) with storage ({:?})",
|
||||
|
|
|
@ -433,6 +433,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
|
|||
X86_64Assembler::mov_base32_reg64(buf, offset, Self::GENERAL_RETURN_REGS[0]);
|
||||
X86_64Assembler::mov_base32_reg64(buf, offset + 8, Self::GENERAL_RETURN_REGS[1]);
|
||||
}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x if !Self::returns_via_arg_pointer(x) => {
|
||||
let size = layout.stack_size(TARGET_INFO);
|
||||
let offset = storage_manager.claim_stack_area(sym, size);
|
||||
|
@ -451,7 +452,6 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
|
|||
);
|
||||
}
|
||||
}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x => todo!("receiving complex return type, {:?}", x),
|
||||
}
|
||||
}
|
||||
|
@ -1033,6 +1033,37 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
|
|||
mov_base64_offset32_reg64(buf, X86_64GeneralReg::RBP, offset, src)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) {
|
||||
debug_assert!(size <= 8);
|
||||
if size == 8 {
|
||||
Self::mov_reg64_base32(buf, dst, offset);
|
||||
} else if size == 4 {
|
||||
todo!("sign extending 4 byte values");
|
||||
} else if size == 2 {
|
||||
todo!("sign extending 2 byte values");
|
||||
} else if size == 1 {
|
||||
todo!("sign extending 1 byte values");
|
||||
} else {
|
||||
internal_error!("Invalid size for sign extension: {}", size);
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) {
|
||||
debug_assert!(size <= 8);
|
||||
if size == 8 {
|
||||
Self::mov_reg64_base32(buf, dst, offset);
|
||||
} else if size == 4 {
|
||||
todo!("zero extending 4 byte values");
|
||||
} else if size == 2 {
|
||||
todo!("zero extending 2 byte values");
|
||||
} else if size == 1 {
|
||||
movzx_reg64_base8_offset32(buf, dst, X86_64GeneralReg::RBP, offset);
|
||||
} else {
|
||||
internal_error!("Invalid size for zero extension: {}", size);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: i32) {
|
||||
movsd_freg64_base64_offset32(buf, dst, X86_64GeneralReg::RSP, offset)
|
||||
|
@ -1423,6 +1454,27 @@ fn mov_reg64_base64_offset32(
|
|||
buf.extend(&offset.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `MOVZX r64,r/m8` -> Move r/m8 with zero extention to r64, where m8 references a base + offset.
|
||||
#[inline(always)]
|
||||
fn movzx_reg64_base8_offset32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: X86_64GeneralReg,
|
||||
base: X86_64GeneralReg,
|
||||
offset: i32,
|
||||
) {
|
||||
let rex = add_rm_extension(base, REX_W);
|
||||
let rex = add_reg_extension(dst, rex);
|
||||
let dst_mod = (dst as u8 % 8) << 3;
|
||||
let base_mod = base as u8 % 8;
|
||||
buf.reserve(9);
|
||||
buf.extend(&[rex, 0x0F, 0xB6, 0x80 + dst_mod + base_mod]);
|
||||
// Using RSP or R12 requires a secondary index byte.
|
||||
if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 {
|
||||
buf.push(0x24);
|
||||
}
|
||||
buf.extend(&offset.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `MOVSD xmm1,xmm2` -> Move scalar double-precision floating-point value from xmm2 to xmm1 register.
|
||||
#[inline(always)]
|
||||
fn movsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
|
||||
|
@ -2126,6 +2178,35 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movzx_reg64_base8_offset32() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for ((dst, src, offset), expected) in &[
|
||||
(
|
||||
(X86_64GeneralReg::RAX, X86_64GeneralReg::RBP, TEST_I32),
|
||||
vec![0x48, 0x0F, 0xB6, 0x85],
|
||||
),
|
||||
(
|
||||
(X86_64GeneralReg::R15, X86_64GeneralReg::RBP, TEST_I32),
|
||||
vec![0x4C, 0x0F, 0xB6, 0xBD],
|
||||
),
|
||||
(
|
||||
(X86_64GeneralReg::RAX, X86_64GeneralReg::RSP, TEST_I32),
|
||||
vec![0x48, 0x0F, 0xB6, 0x84, 0x24],
|
||||
),
|
||||
(
|
||||
(X86_64GeneralReg::R15, X86_64GeneralReg::RSP, TEST_I32),
|
||||
vec![0x4C, 0x0F, 0xB6, 0xBC, 0x24],
|
||||
),
|
||||
] {
|
||||
buf.clear();
|
||||
movzx_reg64_base8_offset32(&mut buf, *dst, *src, *offset);
|
||||
assert_eq!(expected, &buf[..expected.len()]);
|
||||
assert_eq!(TEST_I32.to_le_bytes(), &buf[expected.len()..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mov_reg64_stack32() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue