Skip to main content

ghostscope_compiler/ebpf/
instruction.rs

1//! Instruction transmission and ringbuf messaging
2//!
3//! This module handles the staged transmission of trace events via ringbuf:
4//! Header → Message → Instructions → EndInstruction
5
6use super::context::{CodeGenError, EbpfContext, Result};
7use ghostscope_protocol::trace_event::{EndInstructionData, TraceEventHeader, TraceEventMessage};
8use ghostscope_protocol::{consts, InstructionType};
9use inkwell::basic_block::BasicBlock;
10use inkwell::values::{FunctionValue, PointerValue};
11use inkwell::AddressSpace;
12use tracing::info;
13
14#[cfg(test)]
15const fn split_pid_tgid(pid_tgid: u64) -> (u32, u32) {
16    ((pid_tgid >> 32) as u32, pid_tgid as u32)
17}
18
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub(crate) enum RuntimeEarlyReturnReason {
21    AccumulationBufferNull,
22    EventBufferOverflow,
23}
24
25#[derive(Clone, Copy)]
26pub(crate) struct RuntimeEarlyReturn<'ctx> {
27    reason: RuntimeEarlyReturnReason,
28    block: BasicBlock<'ctx>,
29}
30
31#[derive(Clone, Copy)]
32pub(crate) struct CodegenContinuation<'ctx> {
33    function: FunctionValue<'ctx>,
34    block: BasicBlock<'ctx>,
35}
36
37pub(crate) struct RuntimeReturnAwareValue<'ctx, T> {
38    value: T,
39    continuation: CodegenContinuation<'ctx>,
40    early_returns: Vec<RuntimeEarlyReturn<'ctx>>,
41}
42
43impl<'ctx, T> RuntimeReturnAwareValue<'ctx, T> {
44    pub(crate) fn into_value_after_runtime_returns(self) -> T {
45        let Self {
46            value,
47            continuation,
48            early_returns,
49        } = self;
50        let _ = (&continuation.function, &continuation.block);
51        for early_return in &early_returns {
52            let _ = (&early_return.reason, &early_return.block);
53        }
54        value
55    }
56}
57
58impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> {
59    /// Reserve `size` bytes in the per-CPU accumulation buffer and return a pointer to the
60    /// beginning of the reserved region. On overflow, resets the event offset and returns
61    /// from the eBPF program early (mirrors existing control-flow style used elsewhere).
62    fn reserve_event_space_or_return_zero(
63        &mut self,
64        size: u64,
65    ) -> Result<RuntimeReturnAwareValue<'ctx, PointerValue<'ctx>>> {
66        let i32_ty = self.context.i32_type();
67        let i64_ty = self.context.i64_type();
68
69        // Lookup accumulation buffer value pointer; early-return if NULL
70        let accum_buffer_lookup = self.get_or_create_perf_accumulation_buffer_or_return_zero()?;
71        let accum_buffer = accum_buffer_lookup.value;
72        let mut early_returns = accum_buffer_lookup.early_returns;
73        let offset_ptr = self.get_or_create_perf_buffer_offset()?;
74
75        // Load current offset
76        let offset_val = self
77            .builder
78            .build_load(i32_ty, offset_ptr, "offset")
79            .map_err(|e| CodeGenError::LLVMError(format!("Failed to load offset: {e}")))?
80            .into_int_value();
81
82        let buffer_size = i32_ty.const_int(self.compile_options.max_trace_event_size as u64, false);
83        let req_size_i32 = i32_ty.const_int(size, false);
84
85        // Branching blocks
86        let CodegenContinuation {
87            function: parent_fn,
88            ..
89        } = self.current_codegen_continuation("reserve event space")?;
90        let bb_overflow = self
91            .context
92            .append_basic_block(parent_fn, "reserve_overflow");
93        let bb_check_size = self
94            .context
95            .append_basic_block(parent_fn, "reserve_check_size");
96        let bb_check_fit = self
97            .context
98            .append_basic_block(parent_fn, "reserve_check_fit");
99
100        // if (offset < buffer_size) goto check_size else overflow
101        let off_in = self
102            .builder
103            .build_int_compare(
104                inkwell::IntPredicate::ULT,
105                offset_val,
106                buffer_size,
107                "off_in",
108            )
109            .map_err(|e| CodeGenError::LLVMError(format!("Failed to compare offset: {e}")))?;
110        self.builder
111            .build_conditional_branch(off_in, bb_check_size, bb_overflow)
112            .map_err(|e| {
113                CodeGenError::LLVMError(format!("Failed to branch on offset bounds: {e}"))
114            })?;
115
116        // overflow: reset offset and return 0
117        self.builder.position_at_end(bb_overflow);
118        self.builder
119            .build_store(offset_ptr, i32_ty.const_zero())
120            .map_err(|e| CodeGenError::LLVMError(format!("Failed to reset offset: {e}")))?;
121        self.builder
122            .build_return(Some(&i32_ty.const_zero()))
123            .map_err(|e| {
124                CodeGenError::LLVMError(format!("Failed to build overflow return: {e}"))
125            })?;
126        early_returns.push(RuntimeEarlyReturn {
127            reason: RuntimeEarlyReturnReason::EventBufferOverflow,
128            block: bb_overflow,
129        });
130
131        // check_size: require size <= buffer_size to avoid underflow in (buffer_size - size)
132        self.builder.position_at_end(bb_check_size);
133        let size_ok = self
134            .builder
135            .build_int_compare(
136                inkwell::IntPredicate::ULE,
137                req_size_i32,
138                buffer_size,
139                "size_ok",
140            )
141            .map_err(|e| CodeGenError::LLVMError(format!("Failed to compare reserve size: {e}")))?;
142        self.builder
143            .build_conditional_branch(size_ok, bb_check_fit, bb_overflow)
144            .map_err(|e| {
145                CodeGenError::LLVMError(format!("Failed to branch on reserve size: {e}"))
146            })?;
147
148        // check_fit: need offset <= buffer_size - size (safe: no underflow)
149        self.builder.position_at_end(bb_check_fit);
150        let limit = self
151            .builder
152            .build_int_sub(buffer_size, req_size_i32, "limit")
153            .map_err(|e| {
154                CodeGenError::LLVMError(format!("Failed to compute reserve limit: {e}"))
155            })?;
156        let fits = self
157            .builder
158            .build_int_compare(inkwell::IntPredicate::ULE, offset_val, limit, "fits")
159            .map_err(|e| CodeGenError::LLVMError(format!("Failed to compare reserve fit: {e}")))?;
160        let bb_ok = self.context.append_basic_block(parent_fn, "reserve_ok");
161        self.builder
162            .build_conditional_branch(fits, bb_ok, bb_overflow)
163            .map_err(|e| {
164                CodeGenError::LLVMError(format!("Failed to branch on reserve fit: {e}"))
165            })?;
166
167        // ok: compute dest, bump offset, return dest
168        self.builder.position_at_end(bb_ok);
169        let off64 = self
170            .builder
171            .build_int_z_extend(offset_val, i64_ty, "off64")
172            .map_err(|e| CodeGenError::LLVMError(format!("Failed to extend offset: {e}")))?;
173        // SAFETY: the reserve bounds check proved offset_val..offset_val+size fits
174        // inside the accumulation buffer.
175        let dest_i8 = unsafe {
176            self.builder
177                .build_gep(self.context.i8_type(), accum_buffer, &[off64], "dest_i8")
178                .map_err(|e| {
179                    CodeGenError::LLVMError(format!("Failed to compute destination: {e}"))
180                })?
181        };
182        let new_off = self
183            .builder
184            .build_int_add(offset_val, req_size_i32, "new_off")
185            .map_err(|e| CodeGenError::LLVMError(format!("Failed to update offset: {e}")))?;
186        self.builder
187            .build_store(offset_ptr, new_off)
188            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store updated offset: {e}")))?;
189        self.compile_time_event_bytes_upper_bound = self
190            .compile_time_event_bytes_upper_bound
191            .saturating_add(size as usize);
192
193        Ok(RuntimeReturnAwareValue {
194            value: dest_i8,
195            continuation: CodegenContinuation {
196                function: parent_fn,
197                block: bb_ok,
198            },
199            early_returns,
200        })
201    }
202
203    /// Wrapper to reserve instruction region directly in the accumulation buffer.
204    pub(crate) fn reserve_instruction_region_or_return_zero(
205        &mut self,
206        size: u64,
207    ) -> Result<RuntimeReturnAwareValue<'ctx, PointerValue<'ctx>>> {
208        self.reserve_event_space_or_return_zero(size)
209    }
210
211    /// Get per-CPU accumulation buffer pointer (event_accum_buffer[0]) and return early if null.
212    fn get_or_create_perf_accumulation_buffer_or_return_zero(
213        &mut self,
214    ) -> Result<RuntimeReturnAwareValue<'ctx, PointerValue<'ctx>>> {
215        let ptr_ty = self.context.ptr_type(AddressSpace::default());
216        let val_ptr = self.lookup_percpu_value_ptr("event_accum_buffer", 0)?;
217
218        // if (val_ptr == NULL) return 0;
219        let is_null = self
220            .builder
221            .build_is_null(val_ptr, "accum_buf_is_null")
222            .map_err(|e| CodeGenError::LLVMError(format!("Failed to check buffer null: {e}")))?;
223        let current_fn = self
224            .current_codegen_continuation("check accumulation buffer")?
225            .function;
226        let cont_bb = self
227            .context
228            .append_basic_block(current_fn, "accum_buf_cont");
229        let ret_bb = self.context.append_basic_block(current_fn, "accum_buf_ret");
230        self.builder
231            .build_conditional_branch(is_null, ret_bb, cont_bb)
232            .map_err(|e| {
233                CodeGenError::LLVMError(format!("Failed to branch on accumulation buffer: {e}"))
234            })?;
235        // return 0 in ret_bb
236        self.builder.position_at_end(ret_bb);
237        self.builder
238            .build_return(Some(&self.context.i32_type().const_zero()))
239            .map_err(|e| {
240                CodeGenError::LLVMError(format!("Failed to build accumulation buffer return: {e}"))
241            })?;
242        // continue in cont_bb
243        self.builder.position_at_end(cont_bb);
244
245        // Cast to i8* if necessary (keep as generic pointer; loads/stores will cast as needed)
246        let accum_buffer = self
247            .builder
248            .build_bit_cast(val_ptr, ptr_ty, "accum_buf_ptr")
249            .map_err(|e| {
250                CodeGenError::LLVMError(format!("Failed to cast accumulation buffer: {e}"))
251            })?;
252        let accum_buffer = match accum_buffer {
253            inkwell::values::BasicValueEnum::PointerValue(ptr) => ptr,
254            _ => {
255                return Err(CodeGenError::LLVMError(
256                    "Accumulation buffer cast did not produce a pointer".to_string(),
257                ));
258            }
259        };
260
261        Ok(RuntimeReturnAwareValue {
262            value: accum_buffer,
263            continuation: CodegenContinuation {
264                function: current_fn,
265                block: cont_bb,
266            },
267            early_returns: vec![RuntimeEarlyReturn {
268                reason: RuntimeEarlyReturnReason::AccumulationBufferNull,
269                block: ret_bb,
270            }],
271        })
272    }
273
274    /// Get pointer to per-invocation stack event offset (u32)
275    fn get_or_create_perf_buffer_offset(&self) -> Result<PointerValue<'ctx>> {
276        self.event_offset_alloca.ok_or_else(|| {
277            CodeGenError::LLVMError("event_offset not allocated in entry block".to_string())
278        })
279    }
280
281    fn current_codegen_continuation(&self, op: &str) -> Result<CodegenContinuation<'ctx>> {
282        let block = self.builder.get_insert_block().ok_or_else(|| {
283            CodeGenError::Builder(format!("{op} requires an active insert block"))
284        })?;
285        let function = block
286            .get_parent()
287            .ok_or_else(|| CodeGenError::Builder(format!("{op} requires a parent function")))?;
288        Ok(CodegenContinuation { function, block })
289    }
290
291    /// Send TraceEventHeader as first segment
292    pub fn send_trace_event_header(&mut self) -> Result<()> {
293        info!("Sending TraceEventHeader segment");
294        self.compile_time_event_bytes_upper_bound = 0;
295
296        // For PerfEventArray: Reset accumulation buffer offset to 0
297        if matches!(
298            self.compile_options.event_map_type,
299            crate::EventMapType::PerfEventArray
300        ) {
301            let offset_ptr = self.get_or_create_perf_buffer_offset()?;
302            self.builder
303                .build_store(offset_ptr, self.context.i32_type().const_zero())
304                .map_err(|e| CodeGenError::LLVMError(format!("Failed to reset offset: {e}")))?;
305        }
306
307        // Buffer is a zero-initialized map value region; explicit memset is unnecessary and not BPF-safe.
308        let header_size = std::mem::size_of::<TraceEventHeader>() as u64;
309        let header_buffer = self
310            .reserve_instruction_region_or_return_zero(header_size)?
311            .into_value_after_runtime_returns();
312
313        // Write TraceEventHeader
314        // magic at offset 0 (only field needed)
315        let magic_ptr = header_buffer;
316        let magic_u32_ptr = self
317            .builder
318            .build_pointer_cast(
319                magic_ptr,
320                self.context.ptr_type(AddressSpace::default()),
321                "magic_u32_ptr",
322            )
323            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast magic ptr: {e}")))?;
324        let magic_val = self
325            .context
326            .i32_type()
327            .const_int(ghostscope_protocol::consts::MAGIC.into(), false);
328        self.builder
329            .build_store(magic_u32_ptr, magic_val)
330            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store magic: {e}")))?;
331
332        // Already wrote into the accumulation buffer; no copy needed
333
334        Ok(())
335    }
336
337    /// Send TraceEventMessage as second segment
338    pub fn send_trace_event_message(&mut self, trace_id: u64) -> Result<()> {
339        info!(
340            "Sending TraceEventMessage segment for trace_id: {}",
341            trace_id
342        );
343
344        // Buffer is zero-initialized in map value; avoid memset which is not allowed in eBPF.
345        let message_size = std::mem::size_of::<TraceEventMessage>() as u64;
346        let message_buffer = self
347            .reserve_instruction_region_or_return_zero(message_size)?
348            .into_value_after_runtime_returns();
349
350        // Write TraceEventMessage
351        // trace_id at offset 0
352        let trace_id_ptr = message_buffer;
353        let trace_id_u64_ptr = self
354            .builder
355            .build_pointer_cast(
356                trace_id_ptr,
357                self.context.ptr_type(AddressSpace::default()),
358                "trace_id_u64_ptr",
359            )
360            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast trace_id ptr: {e}")))?;
361        let trace_id_val = self.context.i64_type().const_int(trace_id, false);
362        self.builder
363            .build_store(trace_id_u64_ptr, trace_id_val)
364            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store trace_id: {e}")))?;
365
366        // timestamp at offset 8
367        let timestamp = self.get_current_timestamp()?;
368        // SAFETY: message_buffer points at a reserved trace event header region
369        // and the timestamp offset is within that header.
370        let timestamp_ptr = unsafe {
371            self.builder
372                .build_gep(
373                    self.context.i8_type(),
374                    message_buffer,
375                    &[self
376                        .context
377                        .i32_type()
378                        .const_int(consts::TRACE_EVENT_MESSAGE_TIMESTAMP_OFFSET as u64, false)],
379                    "timestamp_ptr",
380                )
381                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get timestamp GEP: {e}")))?
382        };
383        let timestamp_u64_ptr = self
384            .builder
385            .build_pointer_cast(
386                timestamp_ptr,
387                self.context.ptr_type(AddressSpace::default()),
388                "timestamp_u64_ptr",
389            )
390            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast timestamp ptr: {e}")))?;
391        self.builder
392            .build_store(timestamp_u64_ptr, timestamp)
393            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store timestamp: {e}")))?;
394
395        // Keep transport metadata on host/event semantics. Namespace-aware
396        // `$pid`/`$tid` remain available through the special var path.
397        let (event_pid, event_tid) = self.get_host_pid_tid_values()?;
398
399        // Store pid at offset 16
400        // SAFETY: message_buffer points at a reserved trace event header region
401        // and the pid offset is within that header.
402        let pid_ptr = unsafe {
403            self.builder
404                .build_gep(
405                    self.context.i8_type(),
406                    message_buffer,
407                    &[self
408                        .context
409                        .i32_type()
410                        .const_int(consts::TRACE_EVENT_MESSAGE_PID_OFFSET as u64, false)],
411                    "pid_ptr",
412                )
413                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get pid GEP: {e}")))?
414        };
415        let pid_u32_ptr = self
416            .builder
417            .build_pointer_cast(
418                pid_ptr,
419                self.context.ptr_type(AddressSpace::default()),
420                "pid_u32_ptr",
421            )
422            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast pid ptr: {e}")))?;
423        self.builder
424            .build_store(pid_u32_ptr, event_pid)
425            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store pid: {e}")))?;
426
427        // Store tid at offset 20
428        // SAFETY: message_buffer points at a reserved trace event header region
429        // and the tid offset is within that header.
430        let tid_ptr = unsafe {
431            self.builder
432                .build_gep(
433                    self.context.i8_type(),
434                    message_buffer,
435                    &[self
436                        .context
437                        .i32_type()
438                        .const_int(consts::TRACE_EVENT_MESSAGE_TID_OFFSET as u64, false)],
439                    "tid_ptr",
440                )
441                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get tid GEP: {e}")))?
442        };
443        let tid_u32_ptr = self
444            .builder
445            .build_pointer_cast(
446                tid_ptr,
447                self.context.ptr_type(AddressSpace::default()),
448                "tid_u32_ptr",
449            )
450            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast tid ptr: {e}")))?;
451        self.builder
452            .build_store(tid_u32_ptr, event_tid)
453            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store tid: {e}")))?;
454
455        // Already wrote into the accumulation buffer; no copy needed
456
457        Ok(())
458    }
459
460    /// Write EndInstruction as final segment into the accumulation buffer.
461    pub(crate) fn write_end_instruction(&mut self, total_instructions: u16) -> Result<()> {
462        info!(
463            "Writing EndInstruction segment with {} total instructions",
464            total_instructions
465        );
466
467        // Avoid memset; destination is in accumulation buffer
468        let total_size =
469            (std::mem::size_of::<ghostscope_protocol::trace_event::InstructionHeader>()
470                + std::mem::size_of::<EndInstructionData>()) as u64;
471        let end_buffer = self
472            .reserve_instruction_region_or_return_zero(total_size)?
473            .into_value_after_runtime_returns();
474
475        // Write InstructionHeader
476        // inst_type at offset 0
477        let inst_type_ptr = end_buffer;
478        let inst_type_val = self
479            .context
480            .i8_type()
481            .const_int(InstructionType::EndInstruction as u64, false);
482        self.builder
483            .build_store(inst_type_ptr, inst_type_val)
484            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store inst_type: {e}")))?;
485
486        // data_length at offset 1
487        // SAFETY: end_buffer points at a reserved EndInstruction region and
488        // data_length is within InstructionHeader.
489        let data_length_ptr = unsafe {
490            self.builder
491                .build_gep(
492                    self.context.i8_type(),
493                    end_buffer,
494                    &[self
495                        .context
496                        .i32_type()
497                        .const_int(consts::INSTRUCTION_HEADER_DATA_LENGTH_OFFSET as u64, false)],
498                    "data_length_ptr",
499                )
500                .map_err(|e| {
501                    CodeGenError::LLVMError(format!("Failed to get data_length GEP: {e}"))
502                })?
503        };
504        let data_length_i16_ptr = self
505            .builder
506            .build_pointer_cast(
507                data_length_ptr,
508                self.context.ptr_type(AddressSpace::default()),
509                "data_length_i16_ptr",
510            )
511            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast data_length ptr: {e}")))?;
512        let data_length_val = self
513            .context
514            .i16_type()
515            .const_int(std::mem::size_of::<EndInstructionData>() as u64, false);
516        self.builder
517            .build_store(data_length_i16_ptr, data_length_val)
518            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_length: {e}")))?;
519
520        // Write EndInstructionData at offset 4
521        // total_instructions
522        // SAFETY: EndInstructionData starts at END_INSTRUCTION_DATA_OFFSET inside
523        // the reserved EndInstruction region.
524        let total_instructions_ptr = unsafe {
525            self.builder
526                .build_gep(
527                    self.context.i8_type(),
528                    end_buffer,
529                    &[self
530                        .context
531                        .i32_type()
532                        .const_int(consts::END_INSTRUCTION_DATA_OFFSET as u64, false)],
533                    "total_instructions_ptr",
534                )
535                .map_err(|e| {
536                    CodeGenError::LLVMError(format!("Failed to get total_instructions GEP: {e}"))
537                })?
538        };
539        let total_instructions_i16_ptr = self
540            .builder
541            .build_pointer_cast(
542                total_instructions_ptr,
543                self.context.ptr_type(AddressSpace::default()),
544                "total_instructions_i16_ptr",
545            )
546            .map_err(|e| {
547                CodeGenError::LLVMError(format!("Failed to cast total_instructions ptr: {e}"))
548            })?;
549        let total_instructions_val = self
550            .context
551            .i16_type()
552            .const_int(total_instructions as u64, false);
553        self.builder
554            .build_store(total_instructions_i16_ptr, total_instructions_val)
555            .map_err(|e| {
556                CodeGenError::LLVMError(format!("Failed to store total_instructions: {e}"))
557            })?;
558
559        // execution_status at offset 6
560        // SAFETY: execution_status offset is within EndInstructionData in the
561        // reserved EndInstruction region.
562        let status_ptr = unsafe {
563            self.builder
564                .build_gep(
565                    self.context.i8_type(),
566                    end_buffer,
567                    &[self.context.i32_type().const_int(
568                        (consts::END_INSTRUCTION_DATA_OFFSET
569                            + consts::END_INSTRUCTION_EXECUTION_STATUS_OFFSET)
570                            as u64,
571                        false,
572                    )],
573                    "status_ptr",
574                )
575                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get status GEP: {e}")))?
576        };
577        // Compute execution_status from runtime flags _gs_any_fail and _gs_any_success
578        let any_fail_ptr = self.get_or_create_flag_global("_gs_any_fail");
579        let any_succ_ptr = self.get_or_create_flag_global("_gs_any_success");
580
581        let any_fail_val = self
582            .builder
583            .build_load(self.context.i8_type(), any_fail_ptr, "any_fail")
584            .map_err(|e| CodeGenError::LLVMError(format!("Failed to load any_fail: {e}")))?
585            .into_int_value();
586        let any_succ_val = self
587            .builder
588            .build_load(self.context.i8_type(), any_succ_ptr, "any_succ")
589            .map_err(|e| CodeGenError::LLVMError(format!("Failed to load any_succ: {e}")))?
590            .into_int_value();
591
592        let zero = self.context.i8_type().const_zero();
593        let is_fail = self
594            .builder
595            .build_int_compare(inkwell::IntPredicate::NE, any_fail_val, zero, "is_fail")
596            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cmp any_fail: {e}")))?;
597        let is_succ = self
598            .builder
599            .build_int_compare(inkwell::IntPredicate::NE, any_succ_val, zero, "is_succ")
600            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cmp any_succ: {e}")))?;
601
602        // status = if is_fail && !is_succ => 2
603        //        else if is_fail && is_succ => 1
604        //        else 0
605        let not_succ = self
606            .builder
607            .build_not(is_succ, "not_succ")
608            .map_err(|e| CodeGenError::LLVMError(format!("Failed to build not: {e}")))?;
609        let only_fail = self
610            .builder
611            .build_and(is_fail, not_succ, "only_fail")
612            .map_err(|e| CodeGenError::LLVMError(format!("Failed to build and: {e}")))?;
613        let both = self
614            .builder
615            .build_and(is_fail, is_succ, "both")
616            .map_err(|e| CodeGenError::LLVMError(format!("Failed to build and: {e}")))?;
617
618        let two = self.context.i8_type().const_int(2, false);
619        let one = self.context.i8_type().const_int(1, false);
620        let sel1 = self
621            .builder
622            .build_select(only_fail, two, zero, "status_sel1")
623            .map_err(|e| CodeGenError::LLVMError(format!("Failed to build select: {e}")))?
624            .into_int_value();
625        let sel2 = self
626            .builder
627            .build_select(both, one, sel1, "status_sel2")
628            .map_err(|e| CodeGenError::LLVMError(format!("Failed to build select: {e}")))?
629            .into_int_value();
630
631        self.builder
632            .build_store(status_ptr, sel2)
633            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store status: {e}")))?;
634
635        // Already accumulated in per-CPU buffer; no extra copy needed
636
637        Ok(())
638    }
639
640    /// Send EndInstruction and immediately output the accumulated event.
641    pub fn send_end_instruction(&mut self, total_instructions: u16) -> Result<()> {
642        self.write_end_instruction(total_instructions)?;
643        self.emit_accumulated_event_output_from_stack_offset()
644    }
645
646    pub(crate) fn emit_accumulated_event_output_from_stack_offset(&mut self) -> Result<()> {
647        let accum_buffer = self
648            .get_or_create_perf_accumulation_buffer_or_return_zero()?
649            .into_value_after_runtime_returns();
650        let offset_ptr = self.get_or_create_perf_buffer_offset()?;
651
652        let total_accumulated_size = self
653            .builder
654            .build_load(self.context.i32_type(), offset_ptr, "total_size")
655            .map_err(|e| CodeGenError::LLVMError(format!("Failed to load total size: {e}")))?
656            .into_int_value();
657        self.emit_accumulated_event_output(accum_buffer, total_accumulated_size)?;
658
659        self.builder
660            .build_store(offset_ptr, self.context.i32_type().const_zero())
661            .map_err(|e| {
662                CodeGenError::LLVMError(format!("Failed to reset offset after send: {e}"))
663            })?;
664        Ok(())
665    }
666
667    pub(crate) fn emit_accumulated_event_output(
668        &mut self,
669        accum_buffer: PointerValue<'ctx>,
670        total_accumulated_size: inkwell::values::IntValue<'ctx>,
671    ) -> Result<()> {
672        let max_size_i32 = self
673            .context
674            .i32_type()
675            .const_int(self.compile_options.max_trace_event_size as u64, false);
676        let size_le_max = self
677            .builder
678            .build_int_compare(
679                inkwell::IntPredicate::ULE,
680                total_accumulated_size,
681                max_size_i32,
682                "size_le_max",
683            )
684            .map_err(|e| CodeGenError::LLVMError(format!("Failed to compare end size: {e}")))?;
685        let clamped_size_i32 = self
686            .builder
687            .build_select(
688                size_le_max,
689                total_accumulated_size,
690                max_size_i32,
691                "clamped_size_i32",
692            )
693            .map_err(|e| CodeGenError::LLVMError(format!("Failed to select clamp size: {e}")))?
694            .into_int_value();
695
696        let total_size_i64 = self
697            .builder
698            .build_int_z_extend(clamped_size_i32, self.context.i64_type(), "total_size_i64")
699            .map_err(|e| CodeGenError::LLVMError(format!("Failed to extend size: {e}")))?;
700
701        match self.compile_options.event_map_type {
702            crate::EventMapType::PerfEventArray => {
703                self.create_perf_event_output_dynamic(accum_buffer, total_size_i64)?;
704            }
705            crate::EventMapType::RingBuf => {
706                self.create_ringbuf_output_dynamic(accum_buffer, total_size_i64)?;
707            }
708        }
709        Ok(())
710    }
711}
712
713#[cfg(test)]
714mod tests {
715    use super::split_pid_tgid;
716
717    #[test]
718    fn split_pid_tgid_uses_tgid_for_pid_and_pid_for_tid() {
719        let raw = (0x1122_3344_u64 << 32) | 0x5566_7788;
720
721        let (pid, tid) = split_pid_tgid(raw);
722
723        assert_eq!(pid, 0x1122_3344);
724        assert_eq!(tid, 0x5566_7788);
725    }
726}