setjmp/longjmp assembly gen

This commit is contained in:
Folkert 2023-07-23 15:05:24 +02:00
parent cdc90f8495
commit f96c78b3a5
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
5 changed files with 284 additions and 1 deletions

View file

@ -455,6 +455,14 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg, AArch64Assembler> for AArch64C
) {
todo!("Loading returned complex symbols for AArch64");
}
fn setjmp(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>) {
todo!()
}
fn longjmp(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>) {
todo!()
}
}
impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
@ -529,7 +537,17 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_fn_name: String,
_dst: AArch64GeneralReg,
) {
todo!("calling functions literal for AArch64");
todo!("function pointer for AArch64");
}
#[inline(always)]
fn data_pointer(
_buf: &mut Vec<'_, u8>,
_relocs: &mut Vec<'_, Relocation>,
_fn_name: String,
_dst: AArch64GeneralReg,
) {
todo!("data pointer for AArch64");
}
#[inline(always)]

View file

@ -134,6 +134,9 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler<Gene
sym: &Symbol,
layout: &InLayout<'a>,
);
fn setjmp(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>);
fn longjmp(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>);
}
pub enum CompareOperation {
@ -238,6 +241,13 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
dst: GeneralReg,
);
fn data_pointer(
buf: &mut Vec<'_, u8>,
relocs: &mut Vec<'_, Relocation>,
fn_name: String,
dst: GeneralReg,
);
/// Jumps by an offset of offset bytes unconditionally.
/// It should always generate the same number of bytes to enable replacement if offset changes.
/// It returns the base offset to calculate the jump from (generally the instruction after the jump).
@ -887,6 +897,22 @@ impl<
(out.into_bump_slice(), offset)
}
fn build_roc_setjmp(&mut self) -> &'a [u8] {
let mut out = bumpalo::vec![in self.env.arena];
CC::setjmp(&mut out, &mut self.relocs);
out.into_bump_slice()
}
fn build_roc_longjmp(&mut self) -> &'a [u8] {
let mut out = bumpalo::vec![in self.env.arena];
CC::longjmp(&mut out, &mut self.relocs);
out.into_bump_slice()
}
fn build_fn_pointer(&mut self, dst: &Symbol, fn_name: String) {
let reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);

View file

@ -436,6 +436,90 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
}
}
}
fn setjmp(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>) {
use X86_64GeneralReg::*;
type ASM = X86_64Assembler;
// based on the musl libc setjmp implementation
//
// 000000000020237c <__setjmp>:
// 20237c: 48 89 1f mov QWORD PTR [rdi],rbx
// 20237f: 48 89 6f 08 mov QWORD PTR [rdi+0x8],rbp
// 202383: 4c 89 67 10 mov QWORD PTR [rdi+0x10],r12
// 202387: 4c 89 6f 18 mov QWORD PTR [rdi+0x18],r13
// 20238b: 4c 89 77 20 mov QWORD PTR [rdi+0x20],r14
// 20238f: 4c 89 7f 28 mov QWORD PTR [rdi+0x28],r15
// 202393: 48 8d 54 24 08 lea rdx,[rsp+0x8]
// 202398: 48 89 57 30 mov QWORD PTR [rdi+0x30],rdx
// 20239c: 48 8b 14 24 mov rdx,QWORD PTR [rsp]
// 2023a0: 48 89 57 38 mov QWORD PTR [rdi+0x38],rdx
// 2023a4: 31 c0 xor eax,eax
// 2023a6: c3 ret
let mut offset = 0;
// move the argument to rdi
ASM::mov_mem64_offset32_reg64(buf, RDI, offset, RBX);
offset += 8;
// store the base pointer
ASM::mov_mem64_offset32_reg64(buf, RBP, offset, RBP);
offset += 8;
// store other non-volatile registers
for register in [R12, R13, R14, R15] {
ASM::mov_mem64_offset32_reg64(buf, RDI, offset, register);
offset += 8;
}
// not 100% sure on what this does. It calculates the address that the longjmp will jump
// to, so this just be right after the call to setjmp.
lea_reg64_offset8(buf, RDX, RSP, 0x8);
// store the location to later jump to
ASM::mov_mem64_offset32_reg64(buf, RDI, offset, RDX);
offset += 8;
// store the current stack pointer
ASM::mov_mem64_offset32_reg64(buf, RDI, offset, RSP);
// zero out eax, so we return 0i32 (we do a 64-bit xor for convenience)
ASM::xor_reg64_reg64_reg64(buf, RAX, RAX, RAX);
ASM::ret(buf)
}
fn longjmp(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>) {
use X86_64GeneralReg::*;
type ASM = X86_64Assembler;
// 202358: 31 c0 xor eax,eax
// 20235a: 83 fe 01 cmp esi,0x1
// 20235d: 11 f0 adc eax,esi
// 20235f: 48 8b 1f mov rbx,QWORD PTR [rdi]
// 202362: 48 8b 6f 08 mov rbp,QWORD PTR [rdi+0x8]
// 202366: 4c 8b 67 10 mov r12,QWORD PTR [rdi+0x10]
// 20236a: 4c 8b 6f 18 mov r13,QWORD PTR [rdi+0x18]
// 20236e: 4c 8b 77 20 mov r14,QWORD PTR [rdi+0x20]
// 202372: 4c 8b 7f 28 mov r15,QWORD PTR [rdi+0x28]
// 202376: 48 8b 67 30 mov rsp,QWORD PTR [rdi+0x30]
// 20237a: ff 67 38 jmp QWORD PTR [rdi+0x38]
// make sure something nonzero is returned ?!
ASM::mov_reg64_imm64(buf, RAX, 0x1);
// move the values back into the registers
let mut offset = 0;
let registers = [RBX, RBP, R12, R13, R14, R15, RSP];
for dst in registers {
ASM::mov_reg64_mem64_offset32(buf, dst, RDI, offset);
offset += 8;
}
jmp_reg64_offset8(buf, RDI, offset as i8)
}
}
struct X64_64SystemVStoreArgs {
@ -1023,6 +1107,14 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Windo
) {
todo!("Loading returned complex symbols for X86_64");
}
fn setjmp(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>) {
todo!()
}
fn longjmp(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>) {
todo!()
}
}
impl X86_64WindowsFastcall {
@ -1239,6 +1331,21 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
});
}
#[inline(always)]
fn data_pointer(
buf: &mut Vec<'_, u8>,
relocs: &mut Vec<'_, Relocation>,
fn_name: String,
dst: X86_64GeneralReg,
) {
lea_reg64(buf, dst);
relocs.push(Relocation::LinkedData {
offset: buf.len() as u64 - 4,
name: fn_name,
});
}
#[inline(always)]
fn imul_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
@ -2610,6 +2717,21 @@ fn jmp_imm32(buf: &mut Vec<'_, u8>, imm: i32) {
buf.extend(imm.to_le_bytes());
}
#[inline(always)]
fn jmp_reg64_offset8(buf: &mut Vec<'_, u8>, base: X86_64GeneralReg, offset: i8) {
let rex = add_rm_extension(base, REX_W);
#[allow(clippy::unusual_byte_groupings)]
buf.extend([rex, 0xff, 0b01_100_000 | base as u8 % 8]);
// 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())
}
/// Jump near if not equal (ZF=0).
#[inline(always)]
fn jne_imm32(buf: &mut Vec<'_, u8>, imm: i32) {
@ -2662,6 +2784,32 @@ fn lea_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg) {
])
}
/// `LEA r64, m` -> Store effective address for m in register r64.
#[inline(always)]
fn lea_reg64_offset8(
buf: &mut Vec<'_, u8>,
dst: X86_64GeneralReg,
src: X86_64GeneralReg,
offset: i8,
) {
let rex = add_rm_extension(src, REX_W);
let rex = add_reg_extension(dst, rex);
let dst_mod = dst as u8 % 8;
let src_mod = src as u8 % 8;
#[allow(clippy::unusual_byte_groupings)]
// the upper bits 0b01 of the mod_rm byte indicate 8-bit displacement
buf.extend([rex, 0x8d, 0b01_000_000 | (dst_mod << 3) | src_mod]);
// Using RSP or R12 requires a secondary index byte.
if src == X86_64GeneralReg::RSP || src == X86_64GeneralReg::R12 {
buf.push(0x24);
}
buf.push(offset as u8);
}
fn raw_mov_reg_reg(
buf: &mut Vec<'_, u8>,
register_width: RegisterWidth,
@ -2899,6 +3047,16 @@ fn mov_reg_base_offset32(
buf.extend(offset.to_le_bytes());
}
#[inline(always)]
fn mov_reg64_offset32_reg64(
buf: &mut Vec<'_, u8>,
dst: X86_64GeneralReg,
offset: i32,
src: X86_64GeneralReg,
) {
todo!()
}
/// `MOV r64,r/m64` -> Move r/m64 to r64, where m64 references a base + offset.
#[inline(always)]
fn mov_reg64_base64_offset32(
@ -3832,6 +3990,20 @@ mod tests {
);
}
#[test]
fn test_jmp_reg64_offset8() {
disassembler_test!(
jmp_reg64_offset8,
|base, offset| if offset < 0x10 {
format!("jmp qword ptr [{base} + {offset:x}]")
} else {
format!("jmp qword ptr [{base} + 0x{offset:x}]")
},
ALL_GENERAL_REGS,
[0x8, 0x10]
);
}
#[test]
fn test_jne_imm32() {
const INST_SIZE: i32 = 6;
@ -3877,6 +4049,23 @@ mod tests {
);
}
#[test]
fn test_lea_reg64_offset32() {
disassembler_test!(
lea_reg64_offset8,
|dst, src, offset| {
if offset < 16 {
format!("lea {dst}, [{src} + {offset:x}]")
} else {
format!("lea {dst}, [{src} + 0x{offset:x}]")
}
},
ALL_GENERAL_REGS,
ALL_GENERAL_REGS,
[0x8i8, 0x10i8]
);
}
#[test]
fn test_mov_reg64_reg64() {
disassembler_test!(

View file

@ -463,6 +463,10 @@ trait Backend<'a> {
/// Used for generating wrappers for malloc/realloc/free
fn build_wrapped_jmp(&mut self) -> (&'a [u8], u64);
// use for roc_panic
fn build_roc_setjmp(&mut self) -> &'a [u8];
fn build_roc_longjmp(&mut self) -> &'a [u8];
/// build_proc creates a procedure and outputs it to the wrapped object writer.
/// Returns the procedure bytes, its relocations, and the names of the refcounting functions it references.
fn build_proc(

View file

@ -30,6 +30,8 @@ pub fn build_module<'a, 'r>(
target: &Triple,
procedures: MutMap<(symbol::Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Object<'a> {
dbg!(&target);
match target {
Triple {
architecture: TargetArch::X86_64,
@ -118,6 +120,46 @@ pub fn build_module<'a, 'r>(
}
}
fn generate_setlongjmp_buffer<'a, B: Backend<'a>>(
backend: &mut B,
output: &mut Object,
) -> SymbolId {
let bss_section = output.section_id(StandardSection::UninitializedData);
dbg!(bss_section);
let symbol = Symbol {
name: b"setlongjmp_buffer".to_vec(),
value: 0,
size: 8 * core::mem::size_of::<u64>() as u64,
kind: SymbolKind::Data,
scope: SymbolScope::Linkage,
weak: false,
section: SymbolSection::Section(bss_section),
flags: SymbolFlags::None,
};
dbg!(output.add_symbol(symbol))
}
fn generate_roc_setjmp<'a, B: Backend<'a>>(backend: &mut B, output: &mut Object) {
let text_section = output.section_id(StandardSection::Text);
let proc_symbol = Symbol {
name: b"roc_setjmp".to_vec(),
value: 0,
size: 0,
kind: SymbolKind::Text,
scope: SymbolScope::Dynamic,
weak: false,
section: SymbolSection::Section(text_section),
flags: SymbolFlags::None,
};
let proc_id = output.add_symbol(proc_symbol);
let (proc_data, offset) = backend.build_wrapped_jmp();
let proc_offset = output.add_symbol_data(proc_id, text_section, proc_data, 16);
}
fn generate_wrapper<'a, B: Backend<'a>>(
backend: &mut B,
output: &mut Object,
@ -178,6 +220,8 @@ fn build_object<'a, B: Backend<'a>>(
) -> Object<'a> {
let data_section = output.section_id(StandardSection::Data);
dbg!("build object");
let arena = backend.env().arena;
/*
@ -190,6 +234,8 @@ fn build_object<'a, B: Backend<'a>>(
);
*/
let setlongjmp_buffer = generate_setlongjmp_buffer(&mut backend, &mut output);
if backend.env().mode.generate_allocators() {
generate_wrapper(
&mut backend,