alduin 0.0.1

WIP: A toy compiler backend
Documentation
use crate::{
    compiler::{
        code_space::CodeBuffer,
        graph::{Signature, Type},
    },
    rt::Value,
};
use gimli::write::CallFrameInstruction;
use gimli::write::FrameDescriptionEntry;
use gimli::write::{Address, EhFrame, EndianVec, FrameTable};
use gimli::LittleEndian;
use gimli::{write::CommonInformationEntry, Encoding, Format, X86_64};

extern "C" {
    fn __register_frame(v: usize);
}

/// Create CIE entry in .eh_frame section
fn create_cie() -> CommonInformationEntry {
    let mut entry = CommonInformationEntry::new(
        Encoding {
            address_size: 8,
            format: Format::Dwarf32,
            version: 1,
        },
        1,
        -8,
        X86_64::RA,
    );
    entry.add_instruction(CallFrameInstruction::Cfa(X86_64::RSP, 8));
    entry.add_instruction(CallFrameInstruction::Offset(X86_64::RA, -8));
    entry
}

/// Generate and register .eh_frame section, so that libunwind can unwind jit function stacks
/// TODO: de-register the section after code is unloaded.
#[allow(unused)]
pub fn register_unwind_info(buf: &CodeBuffer) {
    let cie = create_cie();
    let mut dwarf_frametable = FrameTable::default();
    // generate CIE
    let cie_id = dwarf_frametable.add_cie(cie);
    // generate FDE
    let up_to_sp = 16;
    use CallFrameInstruction::*;
    let unwind_instructions = vec![
        (1, CfaOffset(up_to_sp as i32)),
        (1, Offset(X86_64::RBP, -(up_to_sp as i32))),
        (4, CfaRegister(X86_64::RBP)),
    ];
    let fde = {
        let mut fde =
            FrameDescriptionEntry::new(Address::Constant(buf.as_ptr() as _), buf.len() as _);
        for (offset, inst) in &unwind_instructions {
            fde.add_instruction(*offset, inst.clone());
        }
        fde
    };
    dwarf_frametable.add_fde(cie_id, fde);
    // Emit eh_frame
    let mut eh_frame = Box::leak(Box::new(EhFrame(EndianVec::new(LittleEndian))));
    dwarf_frametable.write_eh_frame(&mut eh_frame).unwrap();
    // Register
    let ptr = eh_frame.slice().as_ptr();
    unsafe { __register_frame(ptr as _) }
}

pub fn invoke_compiled_method(
    signature: &Signature,
    entry: *const u8,
    args: &[Value],
) -> Option<Value> {
    let mut raw_args = vec![];
    for arg in args {
        let raw = match *arg {
            Value::Bool(x) => RawValue(0, x as _),
            Value::I8(x) => RawValue(1, x as _),
            Value::I16(x) => RawValue(2, x as _),
            Value::I32(x) => RawValue(3, x as _),
            Value::I64(x) => RawValue(4, x as _),
            Value::F32(x) => RawValue(5, {
                let v = (x, 0i32);
                unsafe { std::mem::transmute::<_, u64>(v) }
            }),
            Value::F64(x) => RawValue(6, unsafe { std::mem::transmute::<_, u64>(x) }),
        };
        raw_args.push(raw);
    }
    let mut result: u64 = 0;
    let result_tag = match signature.1 {
        Type::Bool | Type::Void => 0,
        Type::I8 => 1,
        Type::I16 => 2,
        Type::I32 => 3,
        Type::I64 => 4,
        Type::F32 => 5,
        Type::F64 => 6,
        _ => unreachable!(),
    };
    unsafe {
        invoke_trampoline(
            entry,
            raw_args.as_ptr(),
            args.len(),
            result_tag,
            &mut result,
        )
    }
    // Get return value
    let result_ptr = &result as *const u64;
    let v = match signature.1 {
        Type::Void => None,
        Type::Bool => Some(Value::Bool(unsafe { *(result_ptr as *const bool) })),
        Type::I32 => Some(Value::I32(unsafe { *(result_ptr as *const i32) })),
        Type::I64 => Some(Value::I64(unsafe { *(result_ptr as *const i64) })),
        Type::F32 => Some(Value::F32(unsafe { *(result_ptr as *const f32) })),
        Type::F64 => Some(Value::F64(unsafe { *(result_ptr as *const f64) })),
        _ => unimplemented!(),
    };
    // Clean up
    v
}

#[repr(C)]
struct RawValue(u64, u64);

extern "C" {
    fn invoke_trampoline(
        func: *const u8,
        args: *const RawValue,
        num_args: usize,
        return_tag: u64,
        result: *mut u64,
    );
}

std::arch::global_asm! { r#"
.global invoke_trampoline
invoke_trampoline:
    push rbp
    mov rbp, rsp
    push rbx
    push r12
    push r13
    push r14
    push r15
    mov rax, rdi # func
    mov r14, rsi # args
    mov r15, rdx # num_args
    shl r15, 4
    add r15, r14 # r15 = end_address
    xor r12, r12 # int args count = 0
    xor r13, r13 # float args count = 0
    mov rbx, rcx
    push r8
set_arg:
    cmp r14, r15 # Finished?
    jge end
    mov r11b, [r14] # Get value tag
    add r14, 8 # Bump buffer pointer
    cmp r11b, 5 # Check f32 arg
    je set_f32_arg
    cmp r11b, 6 # Check f64 arg
    je set_f64_arg
set_int_arg:
    # Check arg index
    cmp r12, 0
    je set_int_arg0
    cmp r12, 1
    je set_int_arg1
    cmp r12, 2
    je set_int_arg2
    cmp r12, 3
    je set_int_arg3
    cmp r12, 4
    je set_int_arg4
    cmp r12, 5
    je set_int_arg5
    int3
set_int_arg0:
    mov rdi, [r14]
    jmp set_int_arg_end
set_int_arg1:
    mov rsi, [r14]
    jmp set_int_arg_end
set_int_arg2:
    mov rdx, [r14]
    jmp set_int_arg_end
set_int_arg3:
    mov rcx, [r14]
    jmp set_int_arg_end
set_int_arg4:
    mov r8, [r14]
    jmp set_int_arg_end
set_int_arg5:
    mov r9, [r14]
    jmp set_int_arg_end
set_int_arg_end:
    add r12, 1
    add r14, 8
    jmp set_arg
set_f32_arg:
    # Check arg index
    cmp r13, 0
    je set_f32_arg0
    cmp r13, 1
    je set_f32_arg1
    cmp r13, 2
    je set_f32_arg2
    cmp r13, 3
    je set_f32_arg3
    cmp r13, 4
    je set_f32_arg4
    cmp r13, 5
    je set_f32_arg5
    int3
set_f32_arg0:
    movss xmm0, [r14]
    jmp set_f32_arg_end
set_f32_arg1:
    movss xmm1, [r14]
    jmp set_f32_arg_end
set_f32_arg2:
    movss xmm2, [r14]
    jmp set_f32_arg_end
set_f32_arg3:
    movss xmm3, [r14]
    jmp set_f32_arg_end
set_f32_arg4:
    movss xmm4, [r14]
    jmp set_f32_arg_end
set_f32_arg5:
    movss xmm5, [r14]
    jmp set_f32_arg_end
set_f32_arg_end:
    add r13, 1
    add r14, 8
    jmp set_arg
set_f64_arg:
    # Check arg index
    cmp r13, 0
    je set_f64_arg0
    cmp r13, 1
    je set_f64_arg1
    cmp r13, 2
    je set_f64_arg2
    cmp r13, 3
    je set_f64_arg3
    cmp r13, 4
    je set_f64_arg4
    cmp r13, 5
    je set_f64_arg5
    int3
set_f64_arg0:
    movsd xmm0, [r14]
    jmp set_f64_arg_end
set_f64_arg1:
    movsd xmm1, [r14]
    jmp set_f64_arg_end
set_f64_arg2:
    movsd xmm2, [r14]
    jmp set_f64_arg_end
set_f64_arg3:
    movsd xmm3, [r14]
    jmp set_f64_arg_end
set_f64_arg4:
    movsd xmm4, [r14]
    jmp set_f64_arg_end
set_f64_arg5:
    movsd xmm5, [r14]
    jmp set_f64_arg_end
set_f64_arg_end:
    add r13, 1
    add r14, 8
    jmp set_arg
end:
    call rax
    pop r8
    cmp rbx, 5
    je set_f32_result
    cmp rbx, 6
    je set_f64_result
set_int_result:
    mov [r8], rax
    jmp set_result_end
set_f32_result:
    movss [r8], xmm0
    jmp set_result_end
set_f64_result:
    movsd [r8], xmm0
    jmp set_result_end
set_result_end:
    pop r15
    pop r14
    pop r13
    pop r12
    pop rbx
    mov rsp, rbp
    pop rbp
    ret
"# }