Generate intructions to be executed
This commit is contained in:
parent
34becd4017
commit
82e4c07343
Notes:
git
2025-04-18 13:49:37 +00:00
@ -1,6 +1,6 @@
|
||||
//use std::fmt;
|
||||
use std::mem;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::codegen::CodeBlock;
|
||||
|
||||
/*
|
||||
use crate::core::IseqPayload;
|
||||
@ -57,273 +57,6 @@ pub struct LabelRef {
|
||||
encode: fn(&mut CodeBlock, i64, i64)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Block of memory into which instructions can be assembled
|
||||
pub struct CodeBlock {
|
||||
// Memory block size
|
||||
mem_size: usize,
|
||||
|
||||
// Current writing position
|
||||
write_pos: usize,
|
||||
|
||||
// Table of registered label addresses
|
||||
label_addrs: Vec<usize>,
|
||||
|
||||
// Table of registered label names
|
||||
label_names: Vec<String>,
|
||||
|
||||
// References to labels
|
||||
label_refs: Vec<LabelRef>,
|
||||
|
||||
// A switch for keeping comments. They take up memory.
|
||||
keep_comments: bool,
|
||||
|
||||
// Comments for assembly instructions, if that feature is enabled
|
||||
asm_comments: BTreeMap<usize, Vec<String>>,
|
||||
}
|
||||
|
||||
/// Set of CodeBlock label states. Used for recovering the previous state.
|
||||
pub struct LabelState {
|
||||
label_addrs: Vec<usize>,
|
||||
label_names: Vec<String>,
|
||||
label_refs: Vec<LabelRef>,
|
||||
}
|
||||
|
||||
impl CodeBlock {
|
||||
|
||||
|
||||
|
||||
/// Add an assembly comment if the feature is on.
|
||||
pub fn add_comment(&mut self, _comment: &str) {
|
||||
/*
|
||||
if !self.keep_comments {
|
||||
return;
|
||||
}
|
||||
|
||||
let cur_ptr = self.get_write_ptr().raw_addr(self);
|
||||
|
||||
// If there's no current list of comments for this line number, add one.
|
||||
let this_line_comments = self.asm_comments.entry(cur_ptr).or_default();
|
||||
|
||||
// Unless this comment is the same as the last one at this same line, add it.
|
||||
if this_line_comments.last().map(String::as_str) != Some(comment) {
|
||||
this_line_comments.push(comment.to_string());
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn comments_at(&self, pos: usize) -> Option<&Vec<String>> {
|
||||
self.asm_comments.get(&pos)
|
||||
}
|
||||
|
||||
pub fn remove_comments(&mut self, start_addr: CodePtr, end_addr: CodePtr) {
|
||||
if self.asm_comments.is_empty() {
|
||||
return;
|
||||
}
|
||||
for addr in start_addr.raw_addr(self)..end_addr.raw_addr(self) {
|
||||
self.asm_comments.remove(&addr);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_comments(&mut self) {
|
||||
self.asm_comments.clear();
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
pub fn get_mem_size(&self) -> usize {
|
||||
self.mem_size
|
||||
}
|
||||
|
||||
pub fn get_write_pos(&self) -> usize {
|
||||
self.write_pos
|
||||
}
|
||||
|
||||
// Set the current write position
|
||||
pub fn set_pos(&mut self, pos: usize) {
|
||||
// No bounds check here since we can be out of bounds
|
||||
// when the code block fills up. We want to be able to
|
||||
// restore to the filled up state after patching something
|
||||
// in the middle.
|
||||
self.write_pos = pos;
|
||||
}
|
||||
|
||||
/// Write a single byte at the current position.
|
||||
pub fn write_byte(&mut self, _byte: u8) {
|
||||
/*
|
||||
let write_ptr = self.get_write_ptr();
|
||||
if self.has_capacity(1) && self.mem_block.borrow_mut().write_byte(write_ptr, byte).is_ok() {
|
||||
self.write_pos += 1;
|
||||
} else {
|
||||
self.dropped_bytes = true;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// Write multiple bytes starting from the current position.
|
||||
pub fn write_bytes(&mut self, bytes: &[u8]) {
|
||||
for byte in bytes {
|
||||
self.write_byte(*byte);
|
||||
}
|
||||
}
|
||||
|
||||
/// Write an integer over the given number of bits at the current position.
|
||||
fn write_int(&mut self, val: u64, num_bits: u32) {
|
||||
assert!(num_bits > 0);
|
||||
assert!(num_bits % 8 == 0);
|
||||
|
||||
// Switch on the number of bits
|
||||
match num_bits {
|
||||
8 => self.write_byte(val as u8),
|
||||
16 => self.write_bytes(&[(val & 0xff) as u8, ((val >> 8) & 0xff) as u8]),
|
||||
32 => self.write_bytes(&[
|
||||
(val & 0xff) as u8,
|
||||
((val >> 8) & 0xff) as u8,
|
||||
((val >> 16) & 0xff) as u8,
|
||||
((val >> 24) & 0xff) as u8,
|
||||
]),
|
||||
_ => {
|
||||
let mut cur = val;
|
||||
|
||||
// Write out the bytes
|
||||
for _byte in 0..(num_bits / 8) {
|
||||
self.write_byte((cur & 0xff) as u8);
|
||||
cur >>= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate a new label with a given name
|
||||
pub fn new_label(&mut self, name: String) -> usize {
|
||||
assert!(!name.contains(' '), "use underscores in label names, not spaces");
|
||||
|
||||
// This label doesn't have an address yet
|
||||
self.label_addrs.push(0);
|
||||
self.label_names.push(name);
|
||||
|
||||
return self.label_addrs.len() - 1;
|
||||
}
|
||||
|
||||
/// Write a label at the current address
|
||||
pub fn write_label(&mut self, label_idx: usize) {
|
||||
self.label_addrs[label_idx] = self.write_pos;
|
||||
}
|
||||
|
||||
// Add a label reference at the current write position
|
||||
pub fn label_ref(&mut self, label_idx: usize, num_bytes: usize, encode: fn(&mut CodeBlock, i64, i64)) {
|
||||
assert!(label_idx < self.label_addrs.len());
|
||||
|
||||
// Keep track of the reference
|
||||
self.label_refs.push(LabelRef { pos: self.write_pos, label_idx, num_bytes, encode });
|
||||
|
||||
/*
|
||||
// Move past however many bytes the instruction takes up
|
||||
if self.has_capacity(num_bytes) {
|
||||
self.write_pos += num_bytes;
|
||||
} else {
|
||||
self.dropped_bytes = true; // retry emitting the Insn after next_page
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// Link internal label references
|
||||
pub fn link_labels(&mut self) {
|
||||
let orig_pos = self.write_pos;
|
||||
|
||||
// For each label reference
|
||||
for label_ref in mem::take(&mut self.label_refs) {
|
||||
let ref_pos = label_ref.pos;
|
||||
let label_idx = label_ref.label_idx;
|
||||
assert!(ref_pos < self.mem_size);
|
||||
|
||||
let label_addr = self.label_addrs[label_idx];
|
||||
assert!(label_addr < self.mem_size);
|
||||
|
||||
self.set_pos(ref_pos);
|
||||
(label_ref.encode)(self, (ref_pos + label_ref.num_bytes) as i64, label_addr as i64);
|
||||
|
||||
// Assert that we've written the same number of bytes that we
|
||||
// expected to have written.
|
||||
assert!(self.write_pos == ref_pos + label_ref.num_bytes);
|
||||
}
|
||||
|
||||
self.write_pos = orig_pos;
|
||||
|
||||
// Clear the label positions and references
|
||||
self.label_addrs.clear();
|
||||
self.label_names.clear();
|
||||
assert!(self.label_refs.is_empty());
|
||||
}
|
||||
|
||||
pub fn clear_labels(&mut self) {
|
||||
self.label_addrs.clear();
|
||||
self.label_names.clear();
|
||||
self.label_refs.clear();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
#[cfg(test)]
|
||||
impl CodeBlock {
|
||||
/// Stubbed CodeBlock for testing. Can't execute generated code.
|
||||
pub fn new_dummy(mem_size: usize) -> Self {
|
||||
use std::ptr::NonNull;
|
||||
use crate::virtualmem::*;
|
||||
use crate::virtualmem::tests::TestingAllocator;
|
||||
|
||||
let alloc = TestingAllocator::new(mem_size);
|
||||
let mem_start: *const u8 = alloc.mem_start();
|
||||
let virt_mem = VirtualMem::new(alloc, 1, NonNull::new(mem_start as *mut u8).unwrap(), mem_size, 128 * 1024 * 1024);
|
||||
|
||||
Self::new(Rc::new(RefCell::new(virt_mem)), false, Rc::new(None), true)
|
||||
}
|
||||
|
||||
/// Stubbed CodeBlock for testing conditions that can arise due to code GC. Can't execute generated code.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub fn new_dummy_with_freed_pages(mut freed_pages: Vec<usize>) -> Self {
|
||||
use std::ptr::NonNull;
|
||||
use crate::virtualmem::*;
|
||||
use crate::virtualmem::tests::TestingAllocator;
|
||||
|
||||
freed_pages.sort_unstable();
|
||||
let mem_size = Self::PREFERRED_CODE_PAGE_SIZE *
|
||||
(1 + freed_pages.last().expect("freed_pages vec should not be empty"));
|
||||
|
||||
let alloc = TestingAllocator::new(mem_size);
|
||||
let mem_start: *const u8 = alloc.mem_start();
|
||||
let virt_mem = VirtualMem::new(alloc, 1, NonNull::new(mem_start as *mut u8).unwrap(), mem_size, 128 * 1024 * 1024);
|
||||
|
||||
Self::new(Rc::new(RefCell::new(virt_mem)), false, Rc::new(Some(freed_pages)), true)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
/// Produce hex string output from the bytes in a code block
|
||||
impl fmt::LowerHex for CodeBlock {
|
||||
fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
|
||||
for pos in 0..self.write_pos {
|
||||
let mem_block = &*self.mem_block.borrow();
|
||||
let byte = unsafe { mem_block.start_ptr().raw_ptr(mem_block).add(pos).read() };
|
||||
fmtr.write_fmt(format_args!("{:02x}", byte))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/// Compute the number of bits needed to encode a signed value
|
||||
pub fn imm_num_bits(imm: i64) -> u8
|
||||
{
|
||||
@ -425,4 +158,4 @@ mod tests
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
*/
|
||||
|
31
zjit/src/backend.rs
Normal file
31
zjit/src/backend.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use crate::{asm::x86_64::{add, mov, ret, RAX_REG, RDI_REG, RSI_REG}, codegen::CodeBlock, cruby::{Qnil, RUBY_OFFSET_EC_CFP, RUBY_SIZEOF_CONTROL_FRAME}};
|
||||
use crate::asm::x86_64::X86Opnd::Mem;
|
||||
use crate::asm::x86_64::X86Opnd::Reg;
|
||||
use crate::asm::x86_64::X86Opnd::UImm;
|
||||
use crate::asm::x86_64::X86UImm;
|
||||
use crate::asm::x86_64::X86Mem;
|
||||
|
||||
// Emit x86_64 instructions into CodeBlock
|
||||
// TODO: Create a module like YJIT's Assembler and consider putting this there
|
||||
pub fn x86_emit(cb: &mut CodeBlock) { // TODO: take our backend IR
|
||||
// rdi: EC, rsi: CFP
|
||||
let ec = RDI_REG;
|
||||
let cfp = RSI_REG;
|
||||
|
||||
// Pop frame: CFP = CFP + RUBY_SIZEOF_CONTROL_FRAME
|
||||
add(cb, Reg(cfp), UImm(X86UImm { num_bits: 64, value: RUBY_SIZEOF_CONTROL_FRAME as u64 }));
|
||||
|
||||
// Set ec->cfp: *(EC + RUBY_OFFSET_EC_CFP) = CFP
|
||||
let ec_cfp = X86Mem {
|
||||
num_bits: 64,
|
||||
base_reg_no: ec.reg_no,
|
||||
idx_reg_no: None,
|
||||
scale_exp: 0,
|
||||
disp: RUBY_OFFSET_EC_CFP,
|
||||
};
|
||||
mov(cb, Mem(ec_cfp), Reg(RSI_REG));
|
||||
|
||||
// Return Qnil
|
||||
mov(cb, Reg(RAX_REG), UImm(X86UImm { num_bits: 64, value: Qnil.as_u64() }));
|
||||
ret(cb);
|
||||
}
|
@ -11,6 +11,11 @@ pub struct CodeBlock {
|
||||
|
||||
// Current writing position
|
||||
write_pos: usize,
|
||||
|
||||
// Set if the CodeBlock is unable to output some instructions,
|
||||
// for example, when there is not enough space or when a jump
|
||||
// target is too far away.
|
||||
dropped_bytes: bool,
|
||||
}
|
||||
|
||||
|
||||
@ -20,6 +25,7 @@ impl CodeBlock {
|
||||
Self {
|
||||
mem_block,
|
||||
write_pos: 0,
|
||||
dropped_bytes: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,6 +38,74 @@ impl CodeBlock {
|
||||
pub fn get_ptr(&self, offset: usize) -> CodePtr {
|
||||
self.mem_block.borrow().start_ptr().add_bytes(offset)
|
||||
}
|
||||
|
||||
/// Write a single byte at the current position.
|
||||
pub fn write_byte(&mut self, byte: u8) {
|
||||
let write_ptr = self.get_write_ptr();
|
||||
// TODO: check has_capacity()
|
||||
if self.mem_block.borrow_mut().write_byte(write_ptr, byte).is_ok() {
|
||||
self.write_pos += 1;
|
||||
} else {
|
||||
self.dropped_bytes = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Write multiple bytes starting from the current position.
|
||||
pub fn write_bytes(&mut self, bytes: &[u8]) {
|
||||
for byte in bytes {
|
||||
self.write_byte(*byte);
|
||||
}
|
||||
}
|
||||
|
||||
/// Write an integer over the given number of bits at the current position.
|
||||
pub fn write_int(&mut self, val: u64, num_bits: u32) {
|
||||
assert!(num_bits > 0);
|
||||
assert!(num_bits % 8 == 0);
|
||||
|
||||
// Switch on the number of bits
|
||||
match num_bits {
|
||||
8 => self.write_byte(val as u8),
|
||||
16 => self.write_bytes(&[(val & 0xff) as u8, ((val >> 8) & 0xff) as u8]),
|
||||
32 => self.write_bytes(&[
|
||||
(val & 0xff) as u8,
|
||||
((val >> 8) & 0xff) as u8,
|
||||
((val >> 16) & 0xff) as u8,
|
||||
((val >> 24) & 0xff) as u8,
|
||||
]),
|
||||
_ => {
|
||||
let mut cur = val;
|
||||
|
||||
// Write out the bytes
|
||||
for _byte in 0..(num_bits / 8) {
|
||||
self.write_byte((cur & 0xff) as u8);
|
||||
cur >>= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a label reference at the current write position
|
||||
pub fn label_ref(&mut self, _label_idx: usize, _num_bytes: usize, _encode: fn(&mut CodeBlock, i64, i64)) {
|
||||
// TODO: copy labels
|
||||
|
||||
//assert!(label_idx < self.label_addrs.len());
|
||||
|
||||
//// Keep track of the reference
|
||||
//self.label_refs.push(LabelRef { pos: self.write_pos, label_idx, num_bytes, encode });
|
||||
|
||||
//// Move past however many bytes the instruction takes up
|
||||
//if self.has_capacity(num_bytes) {
|
||||
// self.write_pos += num_bytes;
|
||||
//} else {
|
||||
// self.dropped_bytes = true; // retry emitting the Insn after next_page
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::virtualmem::CodePtrBase for CodeBlock {
|
||||
fn base_ptr(&self) -> std::ptr::NonNull<u8> {
|
||||
self.mem_block.borrow().base_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
/// Global state needed for code generation
|
||||
|
@ -8,7 +8,9 @@ mod stats;
|
||||
mod utils;
|
||||
mod virtualmem;
|
||||
mod asm;
|
||||
mod backend;
|
||||
|
||||
use backend::x86_emit;
|
||||
use codegen::ZJITState;
|
||||
use crate::cruby::*;
|
||||
|
||||
@ -79,7 +81,9 @@ pub extern "C" fn rb_zjit_iseq_gen_entry_point(iseq: IseqPtr, _ec: EcPtr) -> *co
|
||||
ir::iseq_to_ssa(iseq);
|
||||
|
||||
let cb = ZJITState::get_code_block();
|
||||
let _start_ptr = cb.get_write_ptr();
|
||||
let start_ptr = cb.get_write_ptr();
|
||||
x86_emit(cb);
|
||||
|
||||
std::ptr::null()
|
||||
// TODO: use std::ptr::null() if compilation fails
|
||||
start_ptr.raw_ptr(cb)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user