1use crate::patch;
17use crate::stencil_model::{HoleId, RelocKind, Stencil};
18use crate::{JitError, JitPage};
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct Label(usize);
23
24#[derive(Clone, Copy, Debug)]
26pub enum HoleValue {
27 Cont(u8, Label),
29 Const(u8, i64),
31}
32
33#[derive(Debug)]
35pub enum BufferError {
36 MissingHoleValue {
38 stencil: &'static str,
40 hole: HoleId,
42 },
43 UnresolvedLabel(Label),
45 Patch(patch::PatchError),
47 Jit(JitError),
49 Empty,
51}
52
53impl std::fmt::Display for BufferError {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 match self {
56 BufferError::MissingHoleValue { stencil, hole } => {
57 write!(f, "stencil '{stencil}': no value for hole {hole:?}")
58 }
59 BufferError::UnresolvedLabel(l) => write!(f, "unresolved label {l:?}"),
60 BufferError::Patch(e) => write!(f, "patch failed: {e}"),
61 BufferError::Jit(e) => write!(f, "page failed: {e}"),
62 BufferError::Empty => write!(f, "empty JitBuffer"),
63 }
64 }
65}
66
67impl std::error::Error for BufferError {}
68
69struct Piece {
70 stencil: &'static Stencil,
71 holes: Vec<HoleValue>,
72}
73
74#[derive(Default)]
76pub struct JitBuffer {
77 pieces: Vec<Piece>,
78 patch_marks: Vec<(usize, u8)>,
79}
80
81#[derive(Debug)]
83pub struct JitChain {
84 page: JitPage,
85 pieces: usize,
86 patch_slots: Vec<usize>,
88}
89
90impl JitChain {
91 pub fn from_code(code: &[u8], pieces: usize) -> Result<JitChain, crate::JitError> {
100 let page = JitPage::new(code)?;
101 Ok(JitChain { page, pieces, patch_slots: Vec::new() })
102 }
103
104 pub fn patch_marked(&self, value: u64) -> Result<(), crate::JitError> {
109 for &slot in &self.patch_slots {
110 self.page.patch_word(slot, value)?;
111 }
112 Ok(())
113 }
114
115 pub fn has_patch_marks(&self) -> bool {
117 !self.patch_slots.is_empty()
118 }
119
120 pub unsafe fn entry(
128 &self,
129 ) -> unsafe extern "C" fn(*mut i64, *mut i64, i64, i64, i64, i64, f64, f64, f64, f64, f64, f64) -> i64 {
130 std::mem::transmute::<
131 *const u8,
132 unsafe extern "C" fn(*mut i64, *mut i64, i64, i64, i64, i64, f64, f64, f64, f64, f64, f64) -> i64,
133 >(
134 self.page.as_ptr(),
135 )
136 }
137
138 pub fn run(&self) -> i64 {
140 let mut frame = vec![0i64; 64];
141 self.run_with_frame(&mut frame)
142 }
143
144 pub fn run_with_frame(&self, frame: &mut [i64]) -> i64 {
154 const STACK: usize = 256;
155 const CANARY: usize = 64;
156 const SENTINEL: i64 = 0x5151_5151_5151_5151u64 as i64;
157 if jit_canary_enabled() {
158 let mut stack = vec![0i64; STACK + CANARY];
159 for c in &mut stack[STACK..] {
160 *c = SENTINEL;
161 }
162 let r = unsafe {
163 (self.entry())(frame.as_mut_ptr(), stack.as_mut_ptr(), 0, 0, 0, 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
164 };
165 for (k, c) in stack[STACK..].iter().enumerate() {
166 assert_eq!(
167 *c, SENTINEL,
168 "OPERAND STACK OVERFLOW: canary slot {k} (stack base + {}) clobbered",
169 STACK + k
170 );
171 }
172 return r;
173 }
174 let mut stack = vec![0i64; STACK];
175 unsafe {
178 (self.entry())(frame.as_mut_ptr(), stack.as_mut_ptr(), 0, 0, 0, 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
179 }
180 }
181
182 pub fn bytes(&self) -> &[u8] {
184 unsafe { std::slice::from_raw_parts(self.page.as_ptr(), self.page.len()) }
185 }
186
187 pub fn base(&self) -> u64 {
189 self.page.as_ptr() as u64
190 }
191
192 pub fn piece_count(&self) -> usize {
195 self.pieces
196 }
197}
198
199pub fn jit_canary_enabled() -> bool {
202 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
203 *ON.get_or_init(|| std::env::var("LOGOS_JIT_CANARY").is_ok_and(|v| v == "1"))
204}
205
206const PIECE_ALIGN: usize = 16;
207const SLOT_SIZE: usize = 8;
208
209fn align_up(v: usize, a: usize) -> usize {
210 v.div_ceil(a) * a
211}
212
213impl JitBuffer {
214 pub fn new() -> Self {
216 JitBuffer::default()
217 }
218
219 pub fn mark_patch_hole(&mut self, hole_n: u8) {
224 let pi = self.pieces.len().checked_sub(1).expect("mark after a push");
225 self.patch_marks.push((pi, hole_n));
226 }
227
228 pub fn pieces_pushed(&self) -> usize {
231 self.pieces.len()
232 }
233
234 pub fn push_stencil(&mut self, stencil: &'static Stencil, holes: &[HoleValue]) -> Label {
235 self.pieces.push(Piece { stencil, holes: holes.to_vec() });
236 Label(self.pieces.len() - 1)
237 }
238
239 pub fn label(&self, index: usize) -> Label {
242 Label(index)
243 }
244
245 pub fn finish(self) -> Result<JitChain, BufferError> {
247 if self.pieces.is_empty() {
248 return Err(BufferError::Empty);
249 }
250
251 let mut piece_off: Vec<usize> = Vec::with_capacity(self.pieces.len());
253 let mut cursor = 0usize;
254 for piece in &self.pieces {
255 cursor = align_up(cursor, PIECE_ALIGN);
256 piece_off.push(cursor);
257 cursor += piece.stencil.code.len();
258 }
259
260 let mut value_slot: Vec<Vec<Option<usize>>> = Vec::new(); let mut pointer_slot: std::collections::HashMap<(usize, HoleId), usize> = std::collections::HashMap::new();
269 let mut slot_cursor = align_up(cursor, SLOT_SIZE);
270
271 for (pi, piece) in self.pieces.iter().enumerate() {
272 let mut per_hole: Vec<Option<usize>> = vec![None; 9];
273 for hv in &piece.holes {
274 if let HoleValue::Const(n, _) = hv {
275 if per_hole[*n as usize].is_none() {
276 per_hole[*n as usize] = Some(slot_cursor);
277 slot_cursor += SLOT_SIZE;
278 }
279 }
280 }
281 value_slot.push(per_hole);
282 for reloc in piece.stencil.relocs.iter() {
283 if patch::is_indirect(reloc.kind) {
284 pointer_slot.entry((pi, reloc.target)).or_insert_with(|| {
285 let s = slot_cursor;
286 slot_cursor += SLOT_SIZE;
287 s
288 });
289 }
290 }
291 }
292 let total_len = slot_cursor;
293
294 let find_hole = |piece: &Piece, hole: HoleId| -> Result<HoleValue, BufferError> {
296 for hv in &piece.holes {
297 match (hv, hole) {
298 (HoleValue::Cont(n, _), HoleId::Cont(m)) if *n == m => return Ok(*hv),
299 (HoleValue::Const(n, _), HoleId::ConstI64(m)) if *n == m => return Ok(*hv),
300 _ => {}
301 }
302 }
303 Err(BufferError::MissingHoleValue { stencil: piece.stencil.name, hole })
304 };
305
306 for piece in &self.pieces {
308 for hv in &piece.holes {
309 if let HoleValue::Cont(_, Label(t)) = hv {
310 if *t >= self.pieces.len() {
311 return Err(BufferError::UnresolvedLabel(Label(*t)));
312 }
313 }
314 }
315 }
316
317 let pieces = self.pieces;
318 let patch_marks = self.patch_marks;
319 let mut patch_err: Option<BufferError> = None;
320 let page = JitPage::with_layout(total_len, |base| {
321 let mut buf = vec![0u8; total_len];
322
323 for (pi, piece) in pieces.iter().enumerate() {
325 let off = piece_off[pi];
326 buf[off..off + piece.stencil.code.len()].copy_from_slice(piece.stencil.code);
327 }
328
329 let mut do_patch = || -> Result<(), BufferError> {
330 for (pi, piece) in pieces.iter().enumerate() {
332 for hv in &piece.holes {
333 if let HoleValue::Const(n, v) = hv {
334 if let Some(slot) = value_slot[pi][*n as usize] {
335 buf[slot..slot + 8].copy_from_slice(&v.to_le_bytes());
336 }
337 }
338 }
339 }
340 for (&(pi, hole), &slot) in &pointer_slot {
342 let piece = &pieces[pi];
343 let content: u64 = match find_hole(piece, hole)? {
344 HoleValue::Cont(_, Label(t)) => base + piece_off[t] as u64,
345 HoleValue::Const(n, _) => {
346 let vslot = value_slot[pi][n as usize]
347 .expect("const hole has a value slot");
348 base + vslot as u64
349 }
350 };
351 buf[slot..slot + 8].copy_from_slice(&content.to_le_bytes());
352 }
353
354 for (pi, piece) in pieces.iter().enumerate() {
356 for reloc in piece.stencil.relocs.iter() {
357 let site_off = piece_off[pi] + reloc.offset as usize;
358 let site_addr = base + site_off as u64;
359 let target_addr: u64 = if patch::is_indirect(reloc.kind) {
362 base + pointer_slot[&(pi, reloc.target)] as u64
363 } else {
364 match find_hole(piece, reloc.target)? {
365 HoleValue::Cont(_, Label(t)) => base + piece_off[t] as u64,
366 HoleValue::Const(n, _) => {
367 let vslot = value_slot[pi][n as usize]
368 .expect("const hole has a value slot");
369 base + vslot as u64
370 }
371 }
372 };
373 let r = match reloc.kind {
374 RelocKind::Branch26 => {
375 patch::patch_aarch64_branch26(&mut buf, site_off, site_addr, target_addr)
376 }
377 RelocKind::Page21 | RelocKind::GotPage21 => {
378 patch::patch_aarch64_page21(&mut buf, site_off, site_addr, target_addr)
379 }
380 RelocKind::PageOff12 { scale } => {
381 patch::patch_aarch64_pageoff12(&mut buf, site_off, target_addr, scale)
382 }
383 RelocKind::GotPageOff12 => {
384 patch::patch_aarch64_pageoff12(&mut buf, site_off, target_addr, 3)
385 }
386 RelocKind::Rel32 | RelocKind::GotRel32 => {
387 patch::patch_x64_rel32(&mut buf, site_off, site_addr, target_addr, reloc.addend)
388 }
389 RelocKind::Abs64 => {
390 patch::patch_abs64(&mut buf, site_off, target_addr, reloc.addend);
391 Ok(())
392 }
393 };
394 r.map_err(BufferError::Patch)?;
395 }
396 }
397 Ok(())
398 };
399 if let Err(e) = do_patch() {
400 patch_err = Some(e);
401 }
402 buf
403 })
404 .map_err(BufferError::Jit)?;
405
406 if let Some(e) = patch_err {
407 return Err(e);
408 }
409 let patch_slots: Vec<usize> = patch_marks
410 .iter()
411 .map(|&(pi, n)| {
412 value_slot[pi][n as usize].expect("marked hole has a const value slot")
413 })
414 .collect();
415 Ok(JitChain { page, pieces: pieces.len(), patch_slots })
416 }
417}