summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--zjit/src/asm/mod.rs20
-rw-r--r--zjit/src/asm/x86_64/mod.rs3
-rw-r--r--zjit/src/asm/x86_64/tests.rs10
-rw-r--r--zjit/src/backend/arm64/mod.rs63
-rw-r--r--zjit/src/backend/x86_64/mod.rs3
-rw-r--r--zjit/src/stats.rs6
6 files changed, 78 insertions, 27 deletions
diff --git a/zjit/src/asm/mod.rs b/zjit/src/asm/mod.rs
index 9049624529..1e8f3414ec 100644
--- a/zjit/src/asm/mod.rs
+++ b/zjit/src/asm/mod.rs
@@ -20,7 +20,7 @@ pub mod arm64;
pub struct Label(pub usize);
/// The object that knows how to encode the branch instruction.
-type BranchEncoder = Box<dyn Fn(&mut CodeBlock, i64, i64)>;
+type BranchEncoder = Box<dyn Fn(&mut CodeBlock, i64, i64) -> Result<(), ()>>;
/// Reference to an ASM label
pub struct LabelRef {
@@ -233,7 +233,7 @@ impl CodeBlock {
}
// Add a label reference at the current write position
- pub fn label_ref(&mut self, label: Label, num_bytes: usize, encode: impl Fn(&mut CodeBlock, i64, i64) + 'static) {
+ pub fn label_ref(&mut self, label: Label, num_bytes: usize, encode: impl Fn(&mut CodeBlock, i64, i64) -> Result<(), ()> + 'static) {
assert!(label.0 < self.label_addrs.len());
// Keep track of the reference
@@ -248,8 +248,9 @@ impl CodeBlock {
}
// Link internal label references
- pub fn link_labels(&mut self) {
+ pub fn link_labels(&mut self) -> Result<(), ()> {
let orig_pos = self.write_pos;
+ let mut link_result = Ok(());
// For each label reference
for label_ref in mem::take(&mut self.label_refs) {
@@ -261,11 +262,14 @@ impl CodeBlock {
assert!(label_addr < self.mem_size);
self.write_pos = ref_pos;
- (label_ref.encode.as_ref())(self, (ref_pos + label_ref.num_bytes) as i64, label_addr as i64);
+ let encode_result = (label_ref.encode.as_ref())(self, (ref_pos + label_ref.num_bytes) as i64, label_addr as i64);
+ link_result = link_result.and(encode_result);
- // 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);
+ // Verify number of bytes written when the callback returns Ok
+ if encode_result.is_ok() {
+ assert_eq!(self.write_pos, ref_pos + label_ref.num_bytes, "label_ref \
+ callback didn't write number of bytes it claimed to write upfront");
+ }
}
self.write_pos = orig_pos;
@@ -274,6 +278,8 @@ impl CodeBlock {
self.label_addrs.clear();
self.label_names.clear();
assert!(self.label_refs.is_empty());
+
+ link_result
}
/// Convert a Label to CodePtr
diff --git a/zjit/src/asm/x86_64/mod.rs b/zjit/src/asm/x86_64/mod.rs
index cfedca4540..0eeaae59dd 100644
--- a/zjit/src/asm/x86_64/mod.rs
+++ b/zjit/src/asm/x86_64/mod.rs
@@ -679,6 +679,7 @@ pub fn call_label(cb: &mut CodeBlock, label: Label) {
cb.label_ref(label, 5, |cb, src_addr, dst_addr| {
cb.write_byte(0xE8);
cb.write_int((dst_addr - src_addr) as u64, 32);
+ Ok(())
});
}
@@ -795,6 +796,7 @@ fn write_jcc<const OP: u8>(cb: &mut CodeBlock, label: Label) {
cb.write_byte(0x0F);
cb.write_byte(OP);
cb.write_int((dst_addr - src_addr) as u64, 32);
+ Ok(())
});
}
@@ -834,6 +836,7 @@ pub fn jmp_label(cb: &mut CodeBlock, label: Label) {
cb.label_ref(label, 5, |cb, src_addr, dst_addr| {
cb.write_byte(0xE9);
cb.write_int((dst_addr - src_addr) as u64, 32);
+ Ok(())
});
}
diff --git a/zjit/src/asm/x86_64/tests.rs b/zjit/src/asm/x86_64/tests.rs
index d574bdb034..268fe6b1c0 100644
--- a/zjit/src/asm/x86_64/tests.rs
+++ b/zjit/src/asm/x86_64/tests.rs
@@ -136,7 +136,7 @@ fn test_call_label() {
let cb = compile(|cb| {
let label_idx = cb.new_label("fn".to_owned());
call_label(cb, label_idx);
- cb.link_labels();
+ cb.link_labels().unwrap();
});
assert_disasm_snapshot!(cb.disasm(), @" 0x0: call 0");
assert_snapshot!(cb.hexdump(), @"e8fbffffff");
@@ -255,7 +255,7 @@ fn test_jge_label() {
let cb = compile(|cb| {
let label_idx = cb.new_label("loop".to_owned());
jge_label(cb, label_idx);
- cb.link_labels();
+ cb.link_labels().unwrap();
});
assert_disasm_snapshot!(cb.disasm(), @" 0x0: jge 0");
assert_snapshot!(cb.hexdump(), @"0f8dfaffffff");
@@ -268,14 +268,14 @@ fn test_jmp_label() {
let label_idx = cb.new_label("next".to_owned());
jmp_label(cb, label_idx);
cb.write_label(label_idx);
- cb.link_labels();
+ cb.link_labels().unwrap();
});
// Backwards jump
let cb2 = compile(|cb| {
let label_idx = cb.new_label("loop".to_owned());
cb.write_label(label_idx);
jmp_label(cb, label_idx);
- cb.link_labels();
+ cb.link_labels().unwrap();
});
assert_disasm_snapshot!(disasms!(cb1, cb2), @r"
@@ -301,7 +301,7 @@ fn test_jo_label() {
let cb = compile(|cb| {
let label_idx = cb.new_label("loop".to_owned());
jo_label(cb, label_idx);
- cb.link_labels();
+ cb.link_labels().unwrap();
});
assert_disasm_snapshot!(cb.disasm(), @" 0x0: jo 0");
diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs
index 0ef22be631..ea5d86659f 100644
--- a/zjit/src/backend/arm64/mod.rs
+++ b/zjit/src/backend/arm64/mod.rs
@@ -998,10 +998,17 @@ impl Assembler {
generate_branch::<CONDITION>(cb, src_addr, dst_addr);
},
Target::Label(label_idx) => {
- // We save `cb.conditional_jump_insns` number of bytes since we may use up to that amount
- // `generate_branch` will pad the emitted branch instructions with `nop`s for each unused byte.
- cb.label_ref(label_idx, (cb.conditional_jump_insns() * 4) as usize, |cb, src_addr, dst_addr| {
- generate_branch::<CONDITION>(cb, src_addr - (cb.conditional_jump_insns() * 4) as i64, dst_addr);
+ // Try to use a single B.cond instruction
+ cb.label_ref(label_idx, 4, |cb, src_addr, dst_addr| {
+ // +1 since src_addr is after the instruction while A64
+ // counts the offset relative to the start.
+ let offset = (dst_addr - src_addr) / 4 + 1;
+ if bcond_offset_fits_bits(offset) {
+ bcond(cb, CONDITION, InstructionOffset::from_insns(offset as i32));
+ Ok(())
+ } else {
+ Err(())
+ }
});
},
Target::SideExit { .. } => {
@@ -1399,6 +1406,7 @@ impl Assembler {
// Set output to the raw address of the label
cb.label_ref(*label_idx, 4, |cb, end_addr, dst_addr| {
adr(cb, Self::EMIT_OPND, A64Opnd::new_imm(dst_addr - (end_addr - 4)));
+ Ok(())
});
mov(cb, out.into(), Self::EMIT_OPND);
@@ -1480,14 +1488,17 @@ impl Assembler {
emit_jmp_ptr(cb, dst_ptr, true);
},
Target::Label(label_idx) => {
- // Here we're going to save enough space for
- // ourselves and then come back and write the
- // instruction once we know the offset. We're going
- // to assume we can fit into a single b instruction.
- // It will panic otherwise.
+ // Reserve space for a single B instruction
cb.label_ref(label_idx, 4, |cb, src_addr, dst_addr| {
- let bytes: i32 = (dst_addr - (src_addr - 4)).try_into().unwrap();
- b(cb, InstructionOffset::from_bytes(bytes));
+ // +1 since src_addr is after the instruction while A64
+ // counts the offset relative to the start.
+ let offset = (dst_addr - src_addr) / 4 + 1;
+ if b_offset_fits_bits(offset) {
+ b(cb, InstructionOffset::from_insns(offset as i32));
+ Ok(())
+ } else {
+ Err(())
+ }
});
},
Target::SideExit { .. } => {
@@ -1632,7 +1643,7 @@ impl Assembler {
let gc_offsets = asm.arm64_emit(cb);
if let (Some(gc_offsets), false) = (gc_offsets, cb.has_dropped_bytes()) {
- cb.link_labels();
+ cb.link_labels().or(Err(CompileError::LabelLinkingFailure))?;
// Invalidate icache for newly written out region so we don't run stale code.
unsafe { rb_jit_icache_invalidate(start_ptr.raw_ptr(cb) as _, cb.get_write_ptr().raw_ptr(cb) as _) };
@@ -1749,6 +1760,29 @@ mod tests {
}
#[test]
+ fn test_conditional_branch_to_label() {
+ let (mut asm, mut cb) = setup_asm();
+ let start = asm.new_label("start");
+ let forward = asm.new_label("forward");
+
+ let value = asm.load(Opnd::mem(VALUE_BITS, NATIVE_STACK_PTR, 0));
+ asm.write_label(start.clone());
+ asm.cmp(value, 0.into());
+ asm.jg(forward.clone());
+ asm.jl(start.clone());
+ asm.write_label(forward);
+
+ asm.compile_with_num_regs(&mut cb, 1);
+ assert_disasm_snapshot!(cb.disasm(), @r"
+ 0x0: ldur x0, [sp]
+ 0x4: cmp x0, #0
+ 0x8: b.gt #0x10
+ 0xc: b.lt #4
+ ");
+ assert_snapshot!(cb.hexdump(), @"e00340f81f0000f14c000054cbffff54");
+ }
+
+ #[test]
fn sp_movements_are_single_instruction() {
let (mut asm, mut cb) = setup_asm();
@@ -2571,7 +2605,7 @@ mod tests {
}
#[test]
- fn test_label_branch_generate_bounds() {
+ fn test_exceeding_label_branch_generate_bounds() {
// The immediate in a conditional branch is a 19 bit unsigned integer
// which has a max value of 2^18 - 1.
const IMMEDIATE_MAX_VALUE: usize = 2usize.pow(18) - 1;
@@ -2582,6 +2616,7 @@ mod tests {
let page_size = unsafe { rb_jit_get_page_size() } as usize;
let memory_required = (IMMEDIATE_MAX_VALUE + 8) * 4 + page_size;
+ crate::options::rb_zjit_prepare_options(); // Allow `get_option!` in Assembler
let mut asm = Assembler::new();
let mut cb = CodeBlock::new_dummy_sized(memory_required);
@@ -2595,7 +2630,7 @@ mod tests {
});
asm.write_label(far_label.clone());
- asm.compile_with_num_regs(&mut cb, 1);
+ assert_eq!(Err(CompileError::LabelLinkingFailure), asm.compile(&mut cb));
}
#[test]
diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs
index 9f780617cc..b493a99929 100644
--- a/zjit/src/backend/x86_64/mod.rs
+++ b/zjit/src/backend/x86_64/mod.rs
@@ -836,6 +836,7 @@ impl Assembler {
cb.label_ref(*label, 7, move |cb, src_addr, dst_addr| {
let disp = dst_addr - src_addr;
lea(cb, out.into(), mem_opnd(8, RIP, disp.try_into().unwrap()));
+ Ok(())
});
} else {
// Set output to the jump target's raw address
@@ -1104,7 +1105,7 @@ impl Assembler {
let gc_offsets = asm.x86_emit(cb);
if let (Some(gc_offsets), false) = (gc_offsets, cb.has_dropped_bytes()) {
- cb.link_labels();
+ cb.link_labels().or(Err(CompileError::LabelLinkingFailure))?;
Ok((start_ptr, gc_offsets))
} else {
cb.clear_labels();
diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs
index 506bd82686..f619c798fc 100644
--- a/zjit/src/stats.rs
+++ b/zjit/src/stats.rs
@@ -306,6 +306,7 @@ make_counters! {
compile_error_iseq_stack_too_large,
compile_error_exception_handler,
compile_error_out_of_memory,
+ compile_error_label_linking_failure,
compile_error_jit_to_jit_optional,
compile_error_register_spill_on_ccall,
compile_error_register_spill_on_alloc,
@@ -466,6 +467,10 @@ pub enum CompileError {
ExceptionHandler,
OutOfMemory,
ParseError(ParseError),
+ /// When a ZJIT function is too large, the branches may have
+ /// offsets that don't fit in one instruction. We error in
+ /// error that case.
+ LabelLinkingFailure,
}
/// Return a raw pointer to the exit counter for a given CompileError
@@ -479,6 +484,7 @@ pub fn exit_counter_for_compile_error(compile_error: &CompileError) -> Counter {
IseqStackTooLarge => compile_error_iseq_stack_too_large,
ExceptionHandler => compile_error_exception_handler,
OutOfMemory => compile_error_out_of_memory,
+ LabelLinkingFailure => compile_error_label_linking_failure,
ParseError(parse_error) => match parse_error {
StackUnderflow(_) => compile_error_parse_stack_underflow,
MalformedIseq(_) => compile_error_parse_malformed_iseq,