Generate intructions to be executed

This commit is contained in:
Takashi Kokubun 2025-02-06 15:13:39 -05:00
parent 34becd4017
commit 82e4c07343
Notes: git 2025-04-18 13:49:37 +00:00
4 changed files with 114 additions and 272 deletions

View File

@ -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
View 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);
}

View File

@ -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

View File

@ -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)
}