Skip to main content

ghostscope_compiler/ebpf/
codegen.rs

1//! Code generation for instructions
2//!
3//! This module handles the conversion from statements to compiled instructions
4//! and generates LLVM IR for individual instructions.
5
6use super::context::{CodeGenError, EbpfContext, Result};
7use crate::script::{PrintStatement, Program, Statement};
8use aya_ebpf_bindings::bindings::bpf_func_id::BPF_FUNC_probe_read_user;
9use ghostscope_protocol::trace_event::{
10    BacktraceData, EndInstructionData, InstructionHeader, PrintComplexFormatData,
11    PrintComplexVariableData, PrintStringIndexData, PrintVariableIndexData, VariableStatus,
12};
13use ghostscope_protocol::{InstructionType, TraceContext, TypeKind};
14use inkwell::values::{BasicValueEnum, IntValue};
15use inkwell::AddressSpace;
16use std::collections::HashMap;
17use tracing::{debug, info, warn};
18
19/// Parameters for generating a PrintComplexVariable with runtime read
20#[derive(Debug, Clone)]
21struct PrintVarRuntimeMeta {
22    var_name_index: u16,
23    type_index: u16,
24    access_path: String,
25    data_len_limit: usize,
26}
27
28/// Source for complex formatted argument data
29#[derive(Debug, Clone)]
30enum ComplexArgSource<'ctx> {
31    RuntimeRead {
32        eval_result: ghostscope_dwarf::EvaluationResult,
33        dwarf_type: ghostscope_dwarf::TypeInfo,
34        module_for_offsets: Option<String>,
35    },
36    /// Memory dump from a pointer/byte address with a static length
37    MemDump {
38        src_addr: inkwell::values::IntValue<'ctx>,
39        len: usize,
40    },
41    /// Memory dump with dynamic runtime length; bytes read up to min(len_value, max_len)
42    MemDumpDynamic {
43        src_addr: inkwell::values::IntValue<'ctx>,
44        len_value: inkwell::values::IntValue<'ctx>,
45        max_len: usize,
46    },
47    ImmediateBytes {
48        bytes: Vec<u8>,
49    },
50    AddressValue {
51        eval_result: ghostscope_dwarf::EvaluationResult,
52        module_for_offsets: Option<String>,
53    },
54    // Newly added: a value computed in LLVM at runtime (e.g., expression result)
55    ComputedInt {
56        value: inkwell::values::IntValue<'ctx>,
57        byte_len: usize, // typically 8
58    },
59}
60
61/// Argument descriptor for PrintComplexFormat
62#[derive(Debug, Clone)]
63struct ComplexArg<'ctx> {
64    var_name_index: u16,
65    type_index: u16,
66    access_path: Vec<u8>,
67    data_len: usize,
68    source: ComplexArgSource<'ctx>,
69}
70
71const DYNAMIC_READ_ERROR_PAYLOAD_LEN: usize = 12;
72
73fn print_complex_format_instruction_budget(
74    max_trace_event_size: usize,
75    bytes_reserved_so_far: usize,
76) -> usize {
77    let end_instruction_size =
78        std::mem::size_of::<InstructionHeader>() + std::mem::size_of::<EndInstructionData>();
79    let event_budget = max_trace_event_size
80        .saturating_sub(bytes_reserved_so_far)
81        .saturating_sub(end_instruction_size);
82    let instruction_budget_cap = std::mem::size_of::<InstructionHeader>() + u16::MAX as usize;
83    event_budget.min(instruction_budget_cap)
84}
85
86fn distribute_budget_fairly(caps: &[usize], budget: usize) -> Vec<usize> {
87    let mut allocations = vec![0; caps.len()];
88    let mut active: Vec<usize> = caps
89        .iter()
90        .enumerate()
91        .filter_map(|(idx, cap)| (*cap > 0).then_some(idx))
92        .collect();
93    let mut remaining = budget;
94
95    while remaining > 0 && !active.is_empty() {
96        let share = remaining / active.len();
97        if share == 0 {
98            for &idx in active.iter().take(remaining) {
99                allocations[idx] += 1;
100            }
101            break;
102        }
103
104        let mut consumed = 0usize;
105        let mut next_active = Vec::with_capacity(active.len());
106        for idx in active {
107            let cap_left = caps[idx].saturating_sub(allocations[idx]);
108            let take = share.min(cap_left);
109            allocations[idx] += take;
110            consumed += take;
111            if allocations[idx] < caps[idx] {
112                next_active.push(idx);
113            }
114        }
115
116        if consumed == 0 {
117            break;
118        }
119
120        remaining = remaining.saturating_sub(consumed);
121        active = next_active;
122    }
123
124    allocations
125}
126
127fn allocate_dynamic_payload_reservations(max_lens: &[usize], available: usize) -> Vec<usize> {
128    if max_lens.is_empty() || available == 0 {
129        return vec![0; max_lens.len()];
130    }
131
132    let base_caps = vec![DYNAMIC_READ_ERROR_PAYLOAD_LEN; max_lens.len()];
133    let base_budget = available.min(DYNAMIC_READ_ERROR_PAYLOAD_LEN.saturating_mul(max_lens.len()));
134    let mut reservations = distribute_budget_fairly(&base_caps, base_budget);
135    let remaining_budget = available.saturating_sub(reservations.iter().sum::<usize>());
136    if remaining_budget == 0 {
137        return reservations;
138    }
139
140    let extra_caps: Vec<usize> = max_lens
141        .iter()
142        .zip(reservations.iter())
143        .map(|(max_len, reserved)| {
144            max_len
145                .max(&DYNAMIC_READ_ERROR_PAYLOAD_LEN)
146                .saturating_sub(*reserved)
147        })
148        .collect();
149    let extras = distribute_budget_fairly(&extra_caps, remaining_budget);
150    for (reservation, extra) in reservations.iter_mut().zip(extras) {
151        *reservation += extra;
152    }
153
154    reservations
155}
156
157impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> {
158    const UNKNOWN_CHAR_ARRAY_READ_FALLBACK: usize = 256;
159
160    fn build_errno_i32(&self, ret: IntValue<'ctx>, name: &str) -> Result<IntValue<'ctx>> {
161        let i32_ty = self.context.i32_type();
162        match ret.get_type().get_bit_width().cmp(&32) {
163            std::cmp::Ordering::Greater => self
164                .builder
165                .build_int_truncate(ret, i32_ty, name)
166                .map_err(|e| CodeGenError::LLVMError(e.to_string())),
167            std::cmp::Ordering::Less => self
168                .builder
169                .build_int_s_extend(ret, i32_ty, name)
170                .map_err(|e| CodeGenError::LLVMError(e.to_string())),
171            std::cmp::Ordering::Equal => Ok(ret),
172        }
173    }
174
175    /// Unified expression resolver: returns a ComplexArg carrying
176    /// a consistent var_name_index/type_index/access_path/data_len/source
177    /// with strict priority: script variables -> DWARF (locals/params/globals).
178    fn resolve_expr_to_arg(&mut self, expr: &crate::script::ast::Expr) -> Result<ComplexArg<'ctx>> {
179        use crate::script::ast::Expr as E;
180        match expr {
181            // 0) Alias variables: resolve to address and render as pointer value
182            E::Variable(name) if self.alias_variable_exists(name) => {
183                let aliased = self.get_alias_variable(name).expect("alias exists");
184                let addr_i64 = self.resolve_ptr_i64_from_expr(&aliased)?;
185                let var_name_index = self.trace_context.add_variable_name(name.clone());
186                Ok(ComplexArg {
187                    var_name_index,
188                    type_index: self.add_synthesized_type_index_for_kind(TypeKind::Pointer),
189                    access_path: Vec::new(),
190                    data_len: 8,
191                    source: ComplexArgSource::ComputedInt {
192                        value: addr_i64,
193                        byte_len: 8,
194                    },
195                })
196            }
197            // 1) Script variables first
198            E::Variable(name) if self.variable_exists(name) => {
199                let val = self.load_variable(name)?;
200                let var_name_index = self.trace_context.add_variable_name(name.clone());
201                // If this is a string variable, print its contents instead of address
202                if self
203                    .get_variable_type(name)
204                    .is_some_and(|t| matches!(t, crate::script::VarType::String))
205                {
206                    let bytes_opt = self.get_string_variable_bytes(name).cloned();
207                    if let Some(bytes) = bytes_opt {
208                        // Build a char[] type with length=bytes.len()
209                        let char_type = ghostscope_dwarf::TypeInfo::BaseType {
210                            name: "char".to_string(),
211                            size: 1,
212                            encoding: ghostscope_dwarf::constants::DW_ATE_unsigned_char.0 as u16,
213                        };
214                        let array_type = ghostscope_dwarf::TypeInfo::ArrayType {
215                            element_type: Box::new(char_type),
216                            element_count: Some(bytes.len() as u64),
217                            total_size: Some(bytes.len() as u64),
218                        };
219                        return Ok(ComplexArg {
220                            var_name_index,
221                            type_index: self.trace_context.add_type(array_type),
222                            access_path: Vec::new(),
223                            data_len: bytes.len(),
224                            source: ComplexArgSource::ImmediateBytes { bytes },
225                        });
226                    }
227                }
228                match val {
229                    BasicValueEnum::IntValue(iv) => {
230                        // Preserve signedness for display: map bit width to I8/I16/I32/I64
231                        let bitw = iv.get_type().get_bit_width();
232                        let (kind, byte_len) = if bitw == 1 {
233                            (TypeKind::Bool, 1)
234                        } else if bitw <= 8 {
235                            (TypeKind::I8, 1)
236                        } else if bitw <= 16 {
237                            (TypeKind::I16, 2)
238                        } else if bitw <= 32 {
239                            (TypeKind::I32, 4)
240                        } else {
241                            (TypeKind::I64, 8)
242                        };
243                        Ok(ComplexArg {
244                            var_name_index,
245                            type_index: self.add_synthesized_type_index_for_kind(kind),
246                            access_path: Vec::new(),
247                            data_len: byte_len,
248                            source: ComplexArgSource::ComputedInt {
249                                value: iv,
250                                byte_len,
251                            },
252                        })
253                    }
254                    BasicValueEnum::PointerValue(pv) => {
255                        // Non-string pointer variable: print as address (hex)
256                        let iv = self
257                            .builder
258                            .build_ptr_to_int(pv, self.context.i64_type(), "ptr_to_i64")
259                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
260                        Ok(ComplexArg {
261                            var_name_index,
262                            type_index: self.add_synthesized_type_index_for_kind(TypeKind::Pointer),
263                            access_path: Vec::new(),
264                            data_len: 8,
265                            source: ComplexArgSource::ComputedInt {
266                                value: iv,
267                                byte_len: 8,
268                            },
269                        })
270                    }
271                    _ => Err(CodeGenError::TypeError(
272                        "Unsupported script variable type for print".to_string(),
273                    )),
274                }
275            }
276
277            // 2) String literal -> Immediate bytes (for formatted args)
278            E::String(s) => {
279                let mut bytes = s.as_bytes().to_vec();
280                bytes.push(0);
281                let char_type = ghostscope_dwarf::TypeInfo::BaseType {
282                    name: "char".to_string(),
283                    size: 1,
284                    encoding: ghostscope_dwarf::constants::DW_ATE_unsigned_char.0 as u16,
285                };
286                let array_type = ghostscope_dwarf::TypeInfo::ArrayType {
287                    element_type: Box::new(char_type),
288                    element_count: Some(bytes.len() as u64),
289                    total_size: Some(bytes.len() as u64),
290                };
291                Ok(ComplexArg {
292                    var_name_index: self
293                        .trace_context
294                        .add_variable_name("__str_literal".to_string()),
295                    type_index: self.trace_context.add_type(array_type),
296                    access_path: Vec::new(),
297                    data_len: bytes.len(),
298                    source: ComplexArgSource::ImmediateBytes { bytes },
299                })
300            }
301
302            // 3) Integer literal -> Immediate i64 bytes
303            E::Int(v) => {
304                let mut bytes = Vec::with_capacity(8);
305                bytes.extend_from_slice(&(*v).to_le_bytes());
306                let int_type = ghostscope_dwarf::TypeInfo::BaseType {
307                    name: "i64".to_string(),
308                    size: 8,
309                    encoding: ghostscope_dwarf::constants::DW_ATE_signed.0 as u16,
310                };
311                Ok(ComplexArg {
312                    var_name_index: self
313                        .trace_context
314                        .add_variable_name("__int_literal".to_string()),
315                    type_index: self.trace_context.add_type(int_type),
316                    access_path: Vec::new(),
317                    data_len: 8,
318                    source: ComplexArgSource::ImmediateBytes { bytes },
319                })
320            }
321
322            // 4) AddressOf: return AddressValue (pointer payload will be produced)
323            E::AddressOf(inner) => {
324                let var = self
325                    .query_dwarf_for_complex_expr(inner)?
326                    .ok_or_else(|| CodeGenError::VariableNotFound(format!("{inner:?}")))?;
327                let inner_ty = var.dwarf_type.as_ref().ok_or_else(|| {
328                    CodeGenError::DwarfError("Expression has no DWARF type information".to_string())
329                })?;
330                let ptr_ty = ghostscope_dwarf::TypeInfo::PointerType {
331                    target_type: Box::new(inner_ty.clone()),
332                    size: 8,
333                };
334                let module_hint = self.take_module_hint();
335                Ok(ComplexArg {
336                    var_name_index: self
337                        .trace_context
338                        .add_variable_name(self.expr_to_name(expr)),
339                    type_index: self.trace_context.add_type(ptr_ty),
340                    access_path: Vec::new(),
341                    data_len: 8,
342                    source: ComplexArgSource::AddressValue {
343                        eval_result: var.evaluation_result.clone(),
344                        module_for_offsets: module_hint,
345                    },
346                })
347            }
348
349            // 5) Complex lvalue shapes -> DWARF runtime read
350            expr @ (E::MemberAccess(_, _)
351            | E::ArrayAccess(_, _)
352            | E::PointerDeref(_)
353            | E::ChainAccess(_)) => {
354                let var = self
355                    .query_dwarf_for_complex_expr(expr)?
356                    .ok_or_else(|| CodeGenError::VariableNotFound(format!("{expr:?}")))?;
357                if matches!(
358                    var.evaluation_result,
359                    ghostscope_dwarf::EvaluationResult::Optimized
360                ) {
361                    let ti = ghostscope_protocol::type_info::TypeInfo::OptimizedOut {
362                        name: var.name.clone(),
363                    };
364                    return Ok(ComplexArg {
365                        var_name_index: self.trace_context.add_variable_name(var.name.clone()),
366                        type_index: self.trace_context.add_type(ti),
367                        access_path: Vec::new(),
368                        data_len: 0,
369                        source: ComplexArgSource::ImmediateBytes { bytes: Vec::new() },
370                    });
371                }
372                let dwarf_type = var.dwarf_type.as_ref().ok_or_else(|| {
373                    CodeGenError::DwarfError("Expression has no DWARF type information".to_string())
374                })?;
375                let data_len = Self::compute_read_size_for_type(dwarf_type);
376                if data_len == 0 {
377                    return Err(CodeGenError::TypeSizeNotAvailable(var.name));
378                }
379                // Previously clamped to 1993 bytes; now use full DWARF size (transport clamps per event size)
380                // data_len unchanged
381                let module_hint = self.take_module_hint();
382                Ok(ComplexArg {
383                    var_name_index: self.trace_context.add_variable_name(var.name.clone()),
384                    type_index: self.trace_context.add_type(dwarf_type.clone()),
385                    access_path: Vec::new(),
386                    data_len,
387                    source: ComplexArgSource::RuntimeRead {
388                        eval_result: var.evaluation_result.clone(),
389                        dwarf_type: dwarf_type.clone(),
390                        module_for_offsets: module_hint,
391                    },
392                })
393            }
394
395            // 6) Variable not in script scope → DWARF variable or computed fast-path for simple scalars
396            E::Variable(name) => {
397                if let Some(v) = self.query_dwarf_for_variable(name)? {
398                    if let Some(ref t) = v.dwarf_type {
399                        // If DWARF reports optimized-out at this PC, emit OptimizedOut type with no data
400                        if matches!(
401                            v.evaluation_result,
402                            ghostscope_dwarf::EvaluationResult::Optimized
403                        ) {
404                            let ti = ghostscope_protocol::type_info::TypeInfo::OptimizedOut {
405                                name: v.name.clone(),
406                            };
407                            return Ok(ComplexArg {
408                                var_name_index: self
409                                    .trace_context
410                                    .add_variable_name(v.name.clone()),
411                                type_index: self.trace_context.add_type(ti),
412                                access_path: Vec::new(),
413                                data_len: 0,
414                                source: ComplexArgSource::ImmediateBytes { bytes: Vec::new() },
415                            });
416                        }
417                        let is_link_addr = matches!(
418                            v.evaluation_result,
419                            ghostscope_dwarf::EvaluationResult::MemoryLocation(
420                                ghostscope_dwarf::LocationResult::Address(_)
421                            )
422                        );
423                        if Self::is_simple_typeinfo(t) && !is_link_addr {
424                            // Prefer computed value to avoid runtime reads
425                            let compiled = self.compile_expr(expr)?;
426                            match compiled {
427                                BasicValueEnum::IntValue(iv) => {
428                                    // Respect DWARF pointer types to keep pointer formatting
429                                    let (kind, byte_len) = if matches!(
430                                        t,
431                                        ghostscope_dwarf::TypeInfo::PointerType { .. }
432                                    ) {
433                                        (TypeKind::Pointer, 8)
434                                    } else {
435                                        let bitw = iv.get_type().get_bit_width();
436                                        if bitw == 1 {
437                                            (TypeKind::Bool, 1)
438                                        } else if bitw <= 8 {
439                                            (TypeKind::I8, 1)
440                                        } else if bitw <= 16 {
441                                            (TypeKind::I16, 2)
442                                        } else if bitw <= 32 {
443                                            (TypeKind::I32, 4)
444                                        } else {
445                                            (TypeKind::I64, 8)
446                                        }
447                                    };
448                                    Ok(ComplexArg {
449                                        var_name_index: self
450                                            .trace_context
451                                            .add_variable_name(self.expr_to_name(expr)),
452                                        type_index: self.add_synthesized_type_index_for_kind(kind),
453                                        access_path: Vec::new(),
454                                        data_len: byte_len,
455                                        source: ComplexArgSource::ComputedInt {
456                                            value: iv,
457                                            byte_len,
458                                        },
459                                    })
460                                }
461                                BasicValueEnum::PointerValue(pv) => {
462                                    // Pointer register-backed → cast to i64 with pointer typeindex
463                                    let iv = self
464                                        .builder
465                                        .build_ptr_to_int(pv, self.context.i64_type(), "ptr_to_i64")
466                                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
467                                    Ok(ComplexArg {
468                                        var_name_index: self
469                                            .trace_context
470                                            .add_variable_name(self.expr_to_name(expr)),
471                                        type_index: self
472                                            .add_synthesized_type_index_for_kind(TypeKind::Pointer),
473                                        access_path: Vec::new(),
474                                        data_len: 8,
475                                        source: ComplexArgSource::ComputedInt {
476                                            value: iv,
477                                            byte_len: 8,
478                                        },
479                                    })
480                                }
481                                _ => {
482                                    // Fall back to runtime read path
483                                    let data_len = Self::compute_read_size_for_type(t);
484                                    if data_len == 0 {
485                                        return Err(CodeGenError::TypeSizeNotAvailable(v.name));
486                                    }
487                                    let module_hint = self.take_module_hint();
488                                    Ok(ComplexArg {
489                                        var_name_index: self
490                                            .trace_context
491                                            .add_variable_name(v.name.clone()),
492                                        type_index: self.trace_context.add_type(t.clone()),
493                                        access_path: Vec::new(),
494                                        data_len,
495                                        source: ComplexArgSource::RuntimeRead {
496                                            eval_result: v.evaluation_result.clone(),
497                                            dwarf_type: t.clone(),
498                                            module_for_offsets: module_hint,
499                                        },
500                                    })
501                                }
502                            }
503                        } else {
504                            // Complex types or link-time addresses: use RuntimeRead
505                            // (globals/statics need memory read; not an address print unless AddressOf)
506                            let data_len = Self::compute_read_size_for_type(t);
507                            if data_len == 0 {
508                                return Err(CodeGenError::TypeSizeNotAvailable(v.name));
509                            }
510                            let module_hint = self.take_module_hint();
511                            Ok(ComplexArg {
512                                var_name_index: self
513                                    .trace_context
514                                    .add_variable_name(v.name.clone()),
515                                type_index: self.trace_context.add_type(t.clone()),
516                                access_path: Vec::new(),
517                                data_len,
518                                source: ComplexArgSource::RuntimeRead {
519                                    eval_result: v.evaluation_result.clone(),
520                                    dwarf_type: t.clone(),
521                                    module_for_offsets: module_hint,
522                                },
523                            })
524                        }
525                    } else {
526                        Err(CodeGenError::DwarfError(
527                            "Variable has no DWARF type information".to_string(),
528                        ))
529                    }
530                } else {
531                    Err(CodeGenError::VariableNotInScope(name.clone()))
532                }
533            }
534
535            // 7) Pointer arithmetic (ptr +/- K) → typed runtime read at computed address
536            E::BinaryOp { left, op, right } => {
537                use crate::script::ast::BinaryOp as BO;
538                // Support: ptr + int, int + ptr, ptr - int (int may be negative)
539                // Only allow when ptr side resolves to DWARF pointer/array; the offset must be an integer literal for now.
540                // We emit a RuntimeRead with computed location, preserving the pointed-to DWARF type.
541                let (ptr_side, int_side, sign) = match (&**left, op, &**right) {
542                    (l, BO::Add, E::Int(k)) => (l, *k, 1),
543                    (E::Int(k), BO::Add, r) => (r, *k, 1),
544                    (l, BO::Subtract, E::Int(k)) => (l, *k, -1),
545                    _ => {
546                        // Fallback to generic expression handling below
547                        let compiled = self.compile_expr(expr)?;
548                        if let BasicValueEnum::IntValue(iv) = compiled {
549                            let bitw = iv.get_type().get_bit_width();
550                            let (kind, byte_len) = if bitw == 1 {
551                                (TypeKind::Bool, 1)
552                            } else if bitw <= 8 {
553                                (TypeKind::I8, 1)
554                            } else if bitw <= 16 {
555                                (TypeKind::I16, 2)
556                            } else if bitw <= 32 {
557                                (TypeKind::I32, 4)
558                            } else {
559                                (TypeKind::I64, 8)
560                            };
561                            return Ok(ComplexArg {
562                                var_name_index: self
563                                    .trace_context
564                                    .add_variable_name(self.expr_to_name(expr)),
565                                type_index: self.add_synthesized_type_index_for_kind(kind),
566                                access_path: Vec::new(),
567                                data_len: byte_len,
568                                source: ComplexArgSource::ComputedInt {
569                                    value: iv,
570                                    byte_len,
571                                },
572                            });
573                        } else {
574                            return Err(CodeGenError::TypeError(
575                                "Non-integer expression not supported in print".to_string(),
576                            ));
577                        }
578                    }
579                };
580
581                // Try DWARF resolution for the pointer side
582                if let Some(var) = self.query_dwarf_for_complex_expr(ptr_side)? {
583                    if var.dwarf_type.is_some() {
584                        // Determine pointed-to/element type and compute location with scaled offset
585                        let index = sign * int_side;
586                        let (eval_result, elem_ty) =
587                            self.compute_pointed_location_with_index(ptr_side, index)?;
588                        let data_len = Self::compute_read_size_for_type(&elem_ty);
589                        let module_hint = self.take_module_hint();
590                        if data_len == 0 {
591                            // Fallback for unsized/void targets: print computed address as pointer
592                            let ptr_ti = ghostscope_dwarf::TypeInfo::PointerType {
593                                target_type: Box::new(elem_ty.clone()),
594                                size: 8,
595                            };
596                            return Ok(ComplexArg {
597                                var_name_index: self
598                                    .trace_context
599                                    .add_variable_name(self.expr_to_name(expr)),
600                                type_index: self.trace_context.add_type(ptr_ti),
601                                access_path: Vec::new(),
602                                data_len: 8,
603                                source: ComplexArgSource::AddressValue {
604                                    eval_result,
605                                    module_for_offsets: module_hint,
606                                },
607                            });
608                        }
609                        return Ok(ComplexArg {
610                            var_name_index: self
611                                .trace_context
612                                .add_variable_name(self.expr_to_name(expr)),
613                            type_index: self.trace_context.add_type(elem_ty.clone()),
614                            access_path: Vec::new(),
615                            data_len,
616                            source: ComplexArgSource::RuntimeRead {
617                                eval_result,
618                                dwarf_type: elem_ty,
619                                module_for_offsets: module_hint,
620                            },
621                        });
622                    }
623                }
624
625                // If pointer side cannot be resolved as DWARF pointer/array, fall back to computed int
626                let compiled = self.compile_expr(expr)?;
627                if let BasicValueEnum::IntValue(iv) = compiled {
628                    let bitw = iv.get_type().get_bit_width();
629                    let (kind, byte_len) = if bitw == 1 {
630                        (TypeKind::Bool, 1)
631                    } else if bitw <= 8 {
632                        (TypeKind::I8, 1)
633                    } else if bitw <= 16 {
634                        (TypeKind::I16, 2)
635                    } else if bitw <= 32 {
636                        (TypeKind::I32, 4)
637                    } else {
638                        (TypeKind::I64, 8)
639                    };
640                    Ok(ComplexArg {
641                        var_name_index: self
642                            .trace_context
643                            .add_variable_name(self.expr_to_name(expr)),
644                        type_index: self.add_synthesized_type_index_for_kind(kind),
645                        access_path: Vec::new(),
646                        data_len: byte_len,
647                        source: ComplexArgSource::ComputedInt {
648                            value: iv,
649                            byte_len,
650                        },
651                    })
652                } else {
653                    Err(CodeGenError::TypeError(
654                        "Non-integer expression not supported in print".to_string(),
655                    ))
656                }
657            }
658
659            // Binary and other rvalue expressions → compile to computed int
660            other => {
661                let compiled = self.compile_expr(other)?;
662                if let BasicValueEnum::IntValue(iv) = compiled {
663                    let bitw = iv.get_type().get_bit_width();
664                    let (kind, byte_len) = if bitw == 1 {
665                        (TypeKind::Bool, 1)
666                    } else if bitw <= 8 {
667                        (TypeKind::I8, 1)
668                    } else if bitw <= 16 {
669                        (TypeKind::I16, 2)
670                    } else if bitw <= 32 {
671                        (TypeKind::I32, 4)
672                    } else {
673                        (TypeKind::I64, 8)
674                    };
675                    Ok(ComplexArg {
676                        var_name_index: self
677                            .trace_context
678                            .add_variable_name(self.expr_to_name(other)),
679                        type_index: self.add_synthesized_type_index_for_kind(kind),
680                        access_path: Vec::new(),
681                        data_len: byte_len,
682                        source: ComplexArgSource::ComputedInt {
683                            value: iv,
684                            byte_len,
685                        },
686                    })
687                } else {
688                    Err(CodeGenError::TypeError(
689                        "Non-integer expression not supported in print".to_string(),
690                    ))
691                }
692            }
693        }
694    }
695
696    /// Emit a single PrintComplexVariable or a single-arg PrintComplexFormat depending on the arg source.
697    fn emit_print_from_arg(&mut self, arg: ComplexArg<'ctx>) -> Result<u16> {
698        match arg.source {
699            ComplexArgSource::ComputedInt { value, byte_len } => {
700                self.generate_print_complex_variable_computed(
701                    arg.var_name_index,
702                    arg.type_index,
703                    byte_len,
704                    value,
705                )?;
706                Ok(1)
707            }
708            ComplexArgSource::RuntimeRead {
709                eval_result,
710                ref dwarf_type,
711                module_for_offsets,
712            } => {
713                let meta = PrintVarRuntimeMeta {
714                    var_name_index: arg.var_name_index,
715                    type_index: arg.type_index,
716                    access_path: String::new(),
717                    data_len_limit: arg.data_len,
718                };
719                self.generate_print_complex_variable_runtime(
720                    meta,
721                    &eval_result,
722                    dwarf_type,
723                    module_for_offsets.as_deref(),
724                )?;
725                Ok(1)
726            }
727            ComplexArgSource::AddressValue { .. } | ComplexArgSource::ImmediateBytes { .. } => {
728                // Use ComplexFormat with "{}" to render address/immediate nicely
729                let fmt_idx = self.trace_context.add_string("{}".to_string());
730                self.generate_print_complex_format_instruction(fmt_idx, &[arg])?;
731                Ok(1)
732            }
733            ComplexArgSource::MemDump { .. } | ComplexArgSource::MemDumpDynamic { .. } => {
734                // Use ComplexFormat with "{}"; generate_print_complex_format_instruction handles MemDump
735                let fmt_idx = self.trace_context.add_string("{}".to_string());
736                self.generate_print_complex_format_instruction(fmt_idx, &[arg])?;
737                Ok(1)
738            }
739        }
740    }
741    /// Generate PrintComplexVariable instruction that embeds a computed integer value (no runtime read)
742    /// This is used for `print expr;` where expr is an rvalue computed in eBPF.
743    fn generate_print_complex_variable_computed(
744        &mut self,
745        var_name_index: u16,
746        type_index: u16,
747        byte_len: usize,
748        value: IntValue<'ctx>,
749    ) -> Result<()> {
750        // Build sizes
751        let header_size = std::mem::size_of::<InstructionHeader>();
752        let data_struct_size = std::mem::size_of::<PrintComplexVariableData>();
753        let access_path_len: usize = 0; // computed expr has no access path
754        let total_data_length = data_struct_size + access_path_len + byte_len;
755        let total_size = header_size + total_data_length;
756
757        // Reserve space directly in the per-CPU accumulation buffer
758        let inst_buffer = self.reserve_instruction_region(total_size as u64);
759
760        // Write InstructionHeader.inst_type
761        let inst_type_val = self
762            .context
763            .i8_type()
764            .const_int(InstructionType::PrintComplexVariable as u64, false);
765        self.builder
766            .build_store(inst_buffer, inst_type_val)
767            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store inst_type: {e}")))?;
768
769        // Write data_length (u16) at offset 1
770        let data_length_ptr = unsafe {
771            self.builder
772                .build_gep(
773                    self.context.i8_type(),
774                    inst_buffer,
775                    &[self.context.i32_type().const_int(1, false)],
776                    "data_length_ptr",
777                )
778                .map_err(|e| {
779                    CodeGenError::LLVMError(format!("Failed to get data_length GEP: {e}"))
780                })?
781        };
782        let data_length_ptr_cast = self
783            .builder
784            .build_pointer_cast(
785                data_length_ptr,
786                self.context.ptr_type(AddressSpace::default()),
787                "data_length_ptr_cast",
788            )
789            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast data_length ptr: {e}")))?;
790        self.builder
791            .build_store(
792                data_length_ptr_cast,
793                self.context
794                    .i16_type()
795                    .const_int(total_data_length as u64, false),
796            )
797            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_length: {e}")))?;
798
799        // Data pointer (after header)
800        let data_ptr = unsafe {
801            self.builder
802                .build_gep(
803                    self.context.i8_type(),
804                    inst_buffer,
805                    &[self.context.i32_type().const_int(header_size as u64, false)],
806                    "data_ptr",
807                )
808                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get data GEP: {e}")))?
809        };
810
811        // var_name_index (u16)
812        let var_name_index_val = self
813            .context
814            .i16_type()
815            .const_int(var_name_index as u64, false);
816        let var_name_index_off =
817            std::mem::offset_of!(PrintComplexVariableData, var_name_index) as u64;
818        let var_name_index_ptr_i8 = unsafe {
819            self.builder
820                .build_gep(
821                    self.context.i8_type(),
822                    data_ptr,
823                    &[self.context.i32_type().const_int(var_name_index_off, false)],
824                    "var_name_index_ptr_i8",
825                )
826                .map_err(|e| {
827                    CodeGenError::LLVMError(format!("Failed to get var_name_index GEP: {e}"))
828                })?
829        };
830        let var_name_index_ptr_i16 = self
831            .builder
832            .build_pointer_cast(
833                var_name_index_ptr_i8,
834                self.context.ptr_type(AddressSpace::default()),
835                "var_name_index_ptr_i16",
836            )
837            .map_err(|e| {
838                CodeGenError::LLVMError(format!("Failed to cast var_name_index ptr: {e}"))
839            })?;
840        self.builder
841            .build_store(var_name_index_ptr_i16, var_name_index_val)
842            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store var_name_index: {e}")))?;
843
844        // type_index (u16)
845        let type_index_offset = std::mem::offset_of!(PrintComplexVariableData, type_index) as u64;
846        let type_index_ptr_i8 = unsafe {
847            self.builder
848                .build_gep(
849                    self.context.i8_type(),
850                    data_ptr,
851                    &[self.context.i32_type().const_int(type_index_offset, false)],
852                    "type_index_ptr_i8",
853                )
854                .map_err(|e| {
855                    CodeGenError::LLVMError(format!("Failed to get type_index GEP: {e}"))
856                })?
857        };
858        let type_index_ptr = self
859            .builder
860            .build_pointer_cast(
861                type_index_ptr_i8,
862                self.context.ptr_type(AddressSpace::default()),
863                "type_index_ptr_i16",
864            )
865            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast type_index ptr: {e}")))?;
866        let type_index_val = self.context.i16_type().const_int(type_index as u64, false);
867        self.builder
868            .build_store(type_index_ptr, type_index_val)
869            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store type_index: {e}")))?;
870
871        // access_path_len (u8) = 0
872        let access_path_len_off =
873            std::mem::offset_of!(PrintComplexVariableData, access_path_len) as u64;
874        let access_path_len_ptr = unsafe {
875            self.builder
876                .build_gep(
877                    self.context.i8_type(),
878                    data_ptr,
879                    &[self
880                        .context
881                        .i32_type()
882                        .const_int(access_path_len_off, false)],
883                    "access_path_len_ptr",
884                )
885                .map_err(|e| {
886                    CodeGenError::LLVMError(format!("Failed to get access_path_len GEP: {e}"))
887                })?
888        };
889        self.builder
890            .build_store(access_path_len_ptr, self.context.i8_type().const_zero())
891            .map_err(|e| {
892                CodeGenError::LLVMError(format!("Failed to store access_path_len: {e}"))
893            })?;
894
895        // status (u8) = 0
896        let status_off = std::mem::offset_of!(PrintComplexVariableData, status) as u64;
897        let status_ptr = unsafe {
898            self.builder
899                .build_gep(
900                    self.context.i8_type(),
901                    data_ptr,
902                    &[self.context.i32_type().const_int(status_off, false)],
903                    "status_ptr",
904                )
905                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get status GEP: {e}")))?
906        };
907        self.builder
908            .build_store(status_ptr, self.context.i8_type().const_zero())
909            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store status: {e}")))?;
910
911        // data_len (u16)
912        let data_len_off = std::mem::offset_of!(PrintComplexVariableData, data_len) as u64;
913        let data_len_ptr = unsafe {
914            self.builder
915                .build_gep(
916                    self.context.i8_type(),
917                    data_ptr,
918                    &[self.context.i32_type().const_int(data_len_off, false)],
919                    "data_len_ptr",
920                )
921                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get data_len GEP: {e}")))?
922        };
923        let data_len_ptr_cast = self
924            .builder
925            .build_pointer_cast(
926                data_len_ptr,
927                self.context.ptr_type(AddressSpace::default()),
928                "data_len_ptr_cast",
929            )
930            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast data_len ptr: {e}")))?;
931        self.builder
932            .build_store(
933                data_len_ptr_cast,
934                self.context.i16_type().const_int(byte_len as u64, false),
935            )
936            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_len: {e}")))?;
937
938        // variable data starts right after PrintComplexVariableData (no access path)
939        let var_data_ptr = unsafe {
940            self.builder
941                .build_gep(
942                    self.context.i8_type(),
943                    data_ptr,
944                    &[self
945                        .context
946                        .i32_type()
947                        .const_int(data_struct_size as u64, false)],
948                    "var_data_ptr",
949                )
950                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get var_data GEP: {e}")))?
951        };
952
953        // Store computed integer value into payload according to byte_len
954        match byte_len {
955            1 => {
956                let bitw = value.get_type().get_bit_width();
957                let v = if bitw == 1 {
958                    // Booleans must serialize as 0/1
959                    self.builder
960                        .build_int_z_extend(value, self.context.i8_type(), "expr_zext_bool_i8")
961                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
962                } else if bitw < 8 {
963                    self.builder
964                        .build_int_s_extend(value, self.context.i8_type(), "expr_sext_i8")
965                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
966                } else if bitw > 8 {
967                    self.builder
968                        .build_int_truncate(value, self.context.i8_type(), "expr_trunc_i8")
969                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
970                } else {
971                    value
972                };
973                self.builder
974                    .build_store(var_data_ptr, v)
975                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
976            }
977            2 => {
978                let bitw = value.get_type().get_bit_width();
979                let v = if bitw < 16 {
980                    self.builder
981                        .build_int_s_extend(value, self.context.i16_type(), "expr_sext_i16")
982                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
983                } else if bitw > 16 {
984                    self.builder
985                        .build_int_truncate(value, self.context.i16_type(), "expr_trunc_i16")
986                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
987                } else {
988                    value
989                };
990                let i16_ptr_ty = self.context.ptr_type(AddressSpace::default());
991                let cast_ptr = self
992                    .builder
993                    .build_pointer_cast(var_data_ptr, i16_ptr_ty, "expr_i16_ptr")
994                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
995                self.builder
996                    .build_store(cast_ptr, v)
997                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
998            }
999            4 => {
1000                let bitw = value.get_type().get_bit_width();
1001                let v = if bitw < 32 {
1002                    self.builder
1003                        .build_int_s_extend(value, self.context.i32_type(), "expr_sext_i32")
1004                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
1005                } else if bitw > 32 {
1006                    self.builder
1007                        .build_int_truncate(value, self.context.i32_type(), "expr_trunc_i32")
1008                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
1009                } else {
1010                    value
1011                };
1012                let i32_ptr_ty = self.context.ptr_type(AddressSpace::default());
1013                let cast_ptr = self
1014                    .builder
1015                    .build_pointer_cast(var_data_ptr, i32_ptr_ty, "expr_i32_ptr")
1016                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1017                self.builder
1018                    .build_store(cast_ptr, v)
1019                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1020            }
1021            8 => {
1022                let v64 = if value.get_type().get_bit_width() < 64 {
1023                    self.builder
1024                        .build_int_s_extend(value, self.context.i64_type(), "expr_sext_i64")
1025                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
1026                } else {
1027                    value
1028                };
1029                let i64_ptr_ty = self.context.ptr_type(AddressSpace::default());
1030                let cast_ptr = self
1031                    .builder
1032                    .build_pointer_cast(var_data_ptr, i64_ptr_ty, "expr_i64_ptr")
1033                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1034                self.builder
1035                    .build_store(cast_ptr, v64)
1036                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1037            }
1038            n => {
1039                // Fallback: write lowest n bytes little-endian
1040                let v64 = if value.get_type().get_bit_width() < 64 {
1041                    self.builder
1042                        .build_int_s_extend(value, self.context.i64_type(), "expr_sext_fallback")
1043                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
1044                } else {
1045                    value
1046                };
1047                for i in 0..n {
1048                    let shift = self.context.i64_type().const_int((i * 8) as u64, false);
1049                    let shifted = self
1050                        .builder
1051                        .build_right_shift(v64, shift, false, &format!("expr_shr_{i}"))
1052                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1053                    let byte = self
1054                        .builder
1055                        .build_int_truncate(
1056                            shifted,
1057                            self.context.i8_type(),
1058                            &format!("expr_byte_{i}"),
1059                        )
1060                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1061                    let byte_ptr = unsafe {
1062                        self.builder
1063                            .build_gep(
1064                                self.context.i8_type(),
1065                                var_data_ptr,
1066                                &[self.context.i32_type().const_int(i as u64, false)],
1067                                &format!("expr_byte_ptr_{i}"),
1068                            )
1069                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
1070                    };
1071                    self.builder
1072                        .build_store(byte_ptr, byte)
1073                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1074                }
1075            }
1076        }
1077
1078        // Already accumulated; EndInstruction will send the whole event
1079        Ok(())
1080    }
1081    /// Determine if a TypeInfo qualifies as a "simple variable" for PrintVariableIndex
1082    /// Simple: base types (bool/int/float/char), enums (with base type 1/2/4/8), pointers;
1083    /// Complex: arrays, structs, unions, functions
1084    fn is_simple_typeinfo(t: &ghostscope_dwarf::TypeInfo) -> bool {
1085        use ghostscope_dwarf::TypeInfo as TI;
1086        match t {
1087            TI::BaseType { size, .. } => matches!(*size, 1 | 2 | 4 | 8),
1088            TI::EnumType { base_type, .. } => {
1089                let sz = base_type.size();
1090                matches!(sz, 1 | 2 | 4 | 8)
1091            }
1092            TI::PointerType { .. } => true,
1093            TI::TypedefType {
1094                underlying_type, ..
1095            }
1096            | TI::QualifiedType {
1097                underlying_type, ..
1098            } => Self::is_simple_typeinfo(underlying_type),
1099            _ => false,
1100        }
1101    }
1102
1103    fn is_char_byte_typeinfo(t: &ghostscope_dwarf::TypeInfo) -> bool {
1104        use ghostscope_dwarf::TypeInfo as TI;
1105        match t {
1106            TI::BaseType { size, encoding, .. } => {
1107                *size == 1
1108                    && (*encoding == ghostscope_dwarf::constants::DW_ATE_unsigned_char.0 as u16
1109                        || *encoding == ghostscope_dwarf::constants::DW_ATE_signed_char.0 as u16
1110                        || *encoding == ghostscope_dwarf::constants::DW_ATE_unsigned.0 as u16
1111                        || *encoding == ghostscope_dwarf::constants::DW_ATE_signed.0 as u16)
1112            }
1113            TI::TypedefType {
1114                underlying_type, ..
1115            }
1116            | TI::QualifiedType {
1117                underlying_type, ..
1118            } => Self::is_char_byte_typeinfo(underlying_type),
1119            _ => false,
1120        }
1121    }
1122
1123    /// Compute read size for a given DWARF type.
1124    /// Keep strict behavior for general unsized arrays; only apply a bounded fallback for char[].
1125    fn compute_read_size_for_type(t: &ghostscope_dwarf::TypeInfo) -> usize {
1126        use ghostscope_dwarf::TypeInfo as TI;
1127        match t {
1128            TI::ArrayType {
1129                element_type,
1130                element_count,
1131                total_size,
1132            } => {
1133                // Prefer DWARF-provided total size
1134                if let Some(ts) = total_size {
1135                    return *ts as usize;
1136                }
1137                // Fallback for arrays without total_size: need element_count * elem_size
1138                let elem_size = element_type.size() as usize;
1139                if elem_size == 0 {
1140                    return 0;
1141                }
1142                if let Some(cnt) = element_count {
1143                    return elem_size * (*cnt as usize);
1144                }
1145                // Some toolchains emit extern/definition pairs where char[] has no bound in DWARF.
1146                // Keep other unsized arrays strict to avoid silently over-reading unknown layouts.
1147                if Self::is_char_byte_typeinfo(element_type) {
1148                    return Self::UNKNOWN_CHAR_ARRAY_READ_FALLBACK;
1149                }
1150                0
1151            }
1152            TI::TypedefType {
1153                underlying_type, ..
1154            }
1155            | TI::QualifiedType {
1156                underlying_type, ..
1157            } => Self::compute_read_size_for_type(underlying_type),
1158            _ => t.size() as usize,
1159        }
1160    }
1161
1162    fn unwrap_alias_candidate_dwarf_type(
1163        mut t: &ghostscope_dwarf::TypeInfo,
1164    ) -> &ghostscope_dwarf::TypeInfo {
1165        while let ghostscope_dwarf::TypeInfo::TypedefType {
1166            underlying_type, ..
1167        }
1168        | ghostscope_dwarf::TypeInfo::QualifiedType {
1169            underlying_type, ..
1170        } = t
1171        {
1172            t = underlying_type.as_ref();
1173        }
1174        t
1175    }
1176
1177    fn is_aliasable_dwarf_type(t: &ghostscope_dwarf::TypeInfo) -> bool {
1178        matches!(
1179            Self::unwrap_alias_candidate_dwarf_type(t),
1180            ghostscope_dwarf::TypeInfo::PointerType { .. }
1181                | ghostscope_dwarf::TypeInfo::ArrayType { .. }
1182                | ghostscope_dwarf::TypeInfo::StructType { .. }
1183                | ghostscope_dwarf::TypeInfo::UnionType { .. }
1184        )
1185    }
1186
1187    fn expr_to_name(&self, expr: &crate::script::ast::Expr) -> String {
1188        use crate::script::ast::Expr as E;
1189        fn inner(e: &E) -> String {
1190            match e {
1191                E::Variable(s) => s.clone(),
1192                E::MemberAccess(obj, field) => format!("{}.{field}", inner(obj)),
1193                E::ArrayAccess(arr, idx) => format!("{}[{}]", inner(arr), inner(idx)),
1194                E::PointerDeref(p) => format!("*{}", inner(p)),
1195                E::AddressOf(p) => format!("&{}", inner(p)),
1196                E::ChainAccess(v) => v.join("."),
1197                E::Int(v) => v.to_string(),
1198                E::String(s) => format!("\"{s}\""),
1199                E::Float(v) => format!("{v}"),
1200                E::UnaryNot(e1) => format!("!{}", inner(e1)),
1201                E::Bool(v) => v.to_string(),
1202                E::SpecialVar(s) => format!("${s}"),
1203                E::BuiltinCall { name, args } => {
1204                    let arg_strs: Vec<String> = args.iter().map(inner).collect();
1205                    format!("{}({})", name, arg_strs.join(", "))
1206                }
1207                E::BinaryOp { left, op, right } => {
1208                    let op_str = match op {
1209                        crate::script::ast::BinaryOp::Add => "+",
1210                        crate::script::ast::BinaryOp::Subtract => "-",
1211                        crate::script::ast::BinaryOp::Multiply => "*",
1212                        crate::script::ast::BinaryOp::Divide => "/",
1213                        crate::script::ast::BinaryOp::Equal => "==",
1214                        crate::script::ast::BinaryOp::NotEqual => "!=",
1215                        crate::script::ast::BinaryOp::LessThan => "<",
1216                        crate::script::ast::BinaryOp::LessEqual => "<=",
1217                        crate::script::ast::BinaryOp::GreaterThan => ">",
1218                        crate::script::ast::BinaryOp::GreaterEqual => ">=",
1219                        crate::script::ast::BinaryOp::LogicalAnd => "&&",
1220                        crate::script::ast::BinaryOp::LogicalOr => "||",
1221                    };
1222                    format!("({}{}{})", inner(left), op_str, inner(right))
1223                }
1224            }
1225        }
1226        let s_full = inner(expr);
1227        const MAX_NAME: usize = 96;
1228        if s_full.chars().count() > MAX_NAME {
1229            // Keep space for ellipsis
1230            let keep = MAX_NAME.saturating_sub(3);
1231            let mut acc = String::with_capacity(MAX_NAME);
1232            for (i, ch) in s_full.chars().enumerate() {
1233                if i >= keep {
1234                    break;
1235                }
1236                acc.push(ch);
1237            }
1238            acc.push_str("...");
1239            acc
1240        } else {
1241            s_full
1242        }
1243    }
1244
1245    fn expr_contains_builtin(expr: &crate::script::ast::Expr) -> bool {
1246        use crate::script::ast::Expr as E;
1247
1248        match expr {
1249            E::BuiltinCall { .. } => true,
1250            E::UnaryNot(inner)
1251            | E::PointerDeref(inner)
1252            | E::AddressOf(inner)
1253            | E::MemberAccess(inner, _) => Self::expr_contains_builtin(inner),
1254            E::ArrayAccess(base, index) => {
1255                Self::expr_contains_builtin(base) || Self::expr_contains_builtin(index)
1256            }
1257            E::BinaryOp { left, right, .. } => {
1258                Self::expr_contains_builtin(left) || Self::expr_contains_builtin(right)
1259            }
1260            E::Int(_)
1261            | E::Float(_)
1262            | E::String(_)
1263            | E::Bool(_)
1264            | E::Variable(_)
1265            | E::ChainAccess(_)
1266            | E::SpecialVar(_) => false,
1267        }
1268    }
1269
1270    fn compile_print_expr_with_builtin_exprerror<T, F>(
1271        &mut self,
1272        expr: &crate::script::ast::Expr,
1273        compile: F,
1274    ) -> Result<T>
1275    where
1276        F: FnOnce(&mut Self) -> Result<T>,
1277    {
1278        if !Self::expr_contains_builtin(expr) {
1279            return compile(self);
1280        }
1281
1282        let prev_context_active = self.condition_context_active;
1283        if prev_context_active {
1284            return compile(self);
1285        }
1286
1287        let expr_index = self.trace_context.add_string(self.expr_to_name(expr));
1288        let entry_event_bytes = self.compile_time_event_bytes_upper_bound;
1289
1290        self.reset_condition_error()?;
1291        self.condition_context_active = true;
1292        let compiled = compile(self);
1293        self.condition_context_active = prev_context_active;
1294        let compiled = compiled?;
1295
1296        let current_function = self
1297            .builder
1298            .get_insert_block()
1299            .ok_or_else(|| CodeGenError::LLVMError("No current basic block".to_string()))?
1300            .get_parent()
1301            .ok_or_else(|| CodeGenError::LLVMError("No parent function".to_string()))?;
1302        let err_block = self
1303            .context
1304            .append_basic_block(current_function, "print_expr_err_block");
1305        let ok_block = self
1306            .context
1307            .append_basic_block(current_function, "print_expr_ok_block");
1308        let merge_block = self
1309            .context
1310            .append_basic_block(current_function, "print_expr_merge_block");
1311        let cond_err_pred = self.build_condition_error_predicate()?;
1312        self.builder
1313            .build_conditional_branch(cond_err_pred, err_block, ok_block)
1314            .map_err(|e| {
1315                CodeGenError::LLVMError(format!("Failed to branch on print expr error: {e}"))
1316            })?;
1317
1318        self.builder.position_at_end(err_block);
1319        self.compile_time_event_bytes_upper_bound = entry_event_bytes;
1320        self.emit_current_condition_exprerror(expr_index, "print_expr")?;
1321        let err_path_event_bytes = self.compile_time_event_bytes_upper_bound;
1322        self.builder
1323            .build_unconditional_branch(merge_block)
1324            .map_err(|e| {
1325                CodeGenError::LLVMError(format!(
1326                    "Failed to branch from print expr error block: {e}"
1327                ))
1328            })?;
1329
1330        self.builder.position_at_end(ok_block);
1331        self.compile_time_event_bytes_upper_bound = entry_event_bytes;
1332        self.builder
1333            .build_unconditional_branch(merge_block)
1334            .map_err(|e| {
1335                CodeGenError::LLVMError(format!("Failed to branch from print expr ok block: {e}"))
1336            })?;
1337
1338        self.builder.position_at_end(merge_block);
1339        self.compile_time_event_bytes_upper_bound = entry_event_bytes.max(err_path_event_bytes);
1340        Ok(compiled)
1341    }
1342
1343    fn emit_current_condition_exprerror(
1344        &mut self,
1345        expr_index: u16,
1346        name_prefix: &str,
1347    ) -> Result<()> {
1348        let cond_err_ptr = self.get_or_create_cond_error_global();
1349        let err_code = self
1350            .builder
1351            .build_load(
1352                self.context.i8_type(),
1353                cond_err_ptr,
1354                &format!("{name_prefix}_err_code"),
1355            )
1356            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
1357            .into_int_value();
1358        let cond_err_addr_ptr = self.get_or_create_cond_error_addr_global();
1359        let err_addr = self
1360            .builder
1361            .build_load(
1362                self.context.i64_type(),
1363                cond_err_addr_ptr,
1364                &format!("{name_prefix}_err_addr"),
1365            )
1366            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
1367            .into_int_value();
1368        let cond_err_flags_ptr = self.get_or_create_cond_error_flags_global();
1369        let err_flags = self
1370            .builder
1371            .build_load(
1372                self.context.i8_type(),
1373                cond_err_flags_ptr,
1374                &format!("{name_prefix}_err_flags"),
1375            )
1376            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
1377            .into_int_value();
1378        self.generate_expr_error(expr_index, err_code, err_flags, err_addr)
1379    }
1380
1381    /// Heuristic to decide if an expression should be bound as a DWARF alias variable.
1382    /// Prefer shapes that resolve to a runtime address via DWARF or address-of:
1383    /// - AddressOf(...)
1384    /// - Member/Array/PointerDeref/Chain access
1385    /// - Variable that is a DWARF-backed symbol (not a script var)
1386    /// - Simple constant-offset on top of an aliasy expression: alias + K (K >= 0)
1387    fn is_alias_candidate_expr(&mut self, expr: &crate::script::ast::Expr) -> bool {
1388        use crate::script::ast::BinaryOp as BO;
1389        use crate::script::ast::Expr as E;
1390        match expr {
1391            // Alias variable names are alias candidates
1392            E::Variable(name) if self.alias_variable_exists(name) => true,
1393            // Explicit address-of is always an alias
1394            E::AddressOf(_) => true,
1395            // Constant offset on top of an alias-eligible expression
1396            E::BinaryOp {
1397                left,
1398                op: BO::Add,
1399                right,
1400            } => {
1401                let is_const_nonneg = |e: &E| matches!(e, E::Int(v) if *v >= 0);
1402                (self.is_alias_candidate_expr(left) && is_const_nonneg(right))
1403                    || (self.is_alias_candidate_expr(right) && is_const_nonneg(left))
1404            }
1405            // Otherwise, only keep address-like or aggregate DWARF expressions as aliases.
1406            // Scalar DWARF expressions should stay concrete so `let n = foo.len;` behaves
1407            // like an integer script variable and remains usable in capture-length formatting.
1408            other => self
1409                .query_dwarf_for_complex_expr(other)
1410                .ok()
1411                .flatten()
1412                .and_then(|var| var.dwarf_type)
1413                .is_some_and(|ty| Self::is_aliasable_dwarf_type(&ty)),
1414        }
1415    }
1416
1417    // removed old helpers (pure lvalue/binary_op detection) — unified resolver handles shapes
1418
1419    /// Main entry point: compile program with staged transmission system
1420    pub fn compile_program_with_staged_transmission(
1421        &mut self,
1422        program: &Program,
1423        _variable_types: HashMap<String, TypeKind>,
1424    ) -> Result<TraceContext> {
1425        info!("Compiling program with staged transmission system");
1426
1427        // Step 1: Send TraceEventHeader
1428        self.send_trace_event_header()?;
1429        info!("Sent TraceEventHeader");
1430
1431        // Step 2: Send TraceEventMessage with dynamic trace_id
1432        let trace_id = self.current_trace_id.map(|id| id as u64).unwrap_or(0);
1433        self.send_trace_event_message(trace_id)?;
1434        info!("Sent TraceEventMessage");
1435
1436        // Reset per-event execution status flags
1437        self.store_flag_value("_gs_any_fail", 0)?;
1438        self.store_flag_value("_gs_any_success", 0)?;
1439
1440        // Step 3: Process each statement and generate LLVM IR on-demand
1441        let mut instruction_count = 0u16;
1442        for statement in &program.statements {
1443            instruction_count += self.compile_statement(statement)?;
1444        }
1445
1446        // Step 4: Send EndInstruction to mark completion
1447        self.send_end_instruction(instruction_count)?;
1448        info!(
1449            "Sent EndInstruction with {} total instructions",
1450            instruction_count
1451        );
1452
1453        // Step 5: Return the trace context for user-space parsing
1454        Ok(self.trace_context.clone())
1455    }
1456
1457    /// Compile a statement and return the number of instructions generated
1458    pub fn compile_statement(&mut self, statement: &Statement) -> Result<u16> {
1459        debug!("Compiling statement: {:?}", statement);
1460
1461        match statement {
1462            Statement::AliasDeclaration { name, target } => {
1463                info!("Registering alias variable: {} = {:?}", name, target);
1464                // Declare in current scope (no redeclaration or shadowing)
1465                self.declare_name_in_current_scope(name)?;
1466                self.set_alias_variable(name, target.clone());
1467                Ok(0)
1468            }
1469            Statement::VarDeclaration { name, value } => {
1470                info!("Processing variable declaration: {} = {:?}", name, value);
1471                // Declare in current scope (no redeclaration or shadowing)
1472                self.declare_name_in_current_scope(name)?;
1473                // Decide whether this is an alias binding (DWARF-backed address/reference)
1474                if self.is_alias_candidate_expr(value) {
1475                    self.set_alias_variable(name, value.clone());
1476                    tracing::debug!(var=%name, "Registered DWARF alias variable");
1477                    Ok(0)
1478                } else {
1479                    // Compile the value expression and store as concrete variable
1480                    // Special-case: string literal and string var copy — record bytes for content printing
1481                    match value {
1482                        crate::script::Expr::String(s) => {
1483                            let mut bytes = s.as_bytes().to_vec();
1484                            bytes.push(0); // NUL terminate for display convenience
1485                            self.set_string_variable_bytes(name, bytes);
1486                        }
1487                        crate::script::Expr::Variable(ref nm) => {
1488                            if self
1489                                .get_variable_type(nm)
1490                                .is_some_and(|t| matches!(t, crate::script::VarType::String))
1491                            {
1492                                if let Some(b) = self.get_string_variable_bytes(nm).cloned() {
1493                                    self.set_string_variable_bytes(name, b);
1494                                }
1495                            }
1496                        }
1497                        _ => {}
1498                    }
1499                    let compiled_value = self.compile_expr(value)?;
1500                    // Disallow storing pointer values in script variables, except for string literals
1501                    if let BasicValueEnum::PointerValue(_) = compiled_value {
1502                        // Allow if RHS is a string literal OR a string variable (VarType::String)
1503                        let allow_string_var_copy = match value {
1504                            crate::script::Expr::String(_) => true,
1505                            crate::script::Expr::Variable(ref nm) => self
1506                                .get_variable_type(nm)
1507                                .is_some_and(|t| matches!(t, crate::script::VarType::String)),
1508                            _ => false,
1509                        };
1510                        if !allow_string_var_copy {
1511                            return Err(CodeGenError::TypeError(
1512                                "script variables cannot store pointer values; use DWARF alias (let v = &expr) or keep it as a string".to_string(),
1513                            ));
1514                        }
1515                    }
1516                    self.store_variable(name, compiled_value)?;
1517                    Ok(0) // VarDeclaration doesn't generate instructions
1518                }
1519            }
1520            Statement::Print(print_stmt) => self.compile_print_statement(print_stmt),
1521            Statement::If {
1522                condition,
1523                then_body,
1524                else_body,
1525            } => {
1526                let entry_event_bytes = self.compile_time_event_bytes_upper_bound;
1527                // Prepare condition context (runtime error capture)
1528                // Pretty expression text for warning
1529                let expr_text = self.expr_to_name(condition);
1530                let expr_index = self.trace_context.add_string(expr_text);
1531                // Activate condition context (compile-time flag) and reset runtime error byte
1532                self.condition_context_active = true;
1533                self.reset_condition_error()?;
1534
1535                // Compile condition expression
1536                let cond_value = self.compile_expr(condition)?;
1537
1538                // Convert condition to i1 (boolean) for branching
1539                let cond_bool = match cond_value {
1540                    BasicValueEnum::IntValue(int_val) => {
1541                        // Convert integer to boolean (non-zero = true)
1542                        self.builder
1543                            .build_int_compare(
1544                                inkwell::IntPredicate::NE,
1545                                int_val,
1546                                int_val.get_type().const_zero(),
1547                                "cond_bool",
1548                            )
1549                            .map_err(|e| {
1550                                CodeGenError::LLVMError(format!("Failed to create condition: {e}"))
1551                            })?
1552                    }
1553                    _ => {
1554                        return Err(CodeGenError::LLVMError(
1555                            "Condition must evaluate to integer".to_string(),
1556                        ));
1557                    }
1558                };
1559
1560                // Get current function from builder
1561                let current_function = self
1562                    .builder
1563                    .get_insert_block()
1564                    .ok_or_else(|| CodeGenError::LLVMError("No current basic block".to_string()))?
1565                    .get_parent()
1566                    .ok_or_else(|| CodeGenError::LLVMError("No parent function".to_string()))?;
1567
1568                // Create basic blocks for error/noerror and then/else paths
1569                let then_block = self
1570                    .context
1571                    .append_basic_block(current_function, "then_block");
1572                let else_block = self
1573                    .context
1574                    .append_basic_block(current_function, "else_block");
1575                let merge_block = self
1576                    .context
1577                    .append_basic_block(current_function, "merge_block");
1578                let err_block = self
1579                    .context
1580                    .append_basic_block(current_function, "cond_err_block");
1581                let ok_block = self
1582                    .context
1583                    .append_basic_block(current_function, "cond_ok_block");
1584                // After cond compiled, deactivate compile-time flag
1585                self.condition_context_active = false;
1586
1587                // First branch: did runtime errors occur while evaluating the condition?
1588                let cond_err_pred = self.build_condition_error_predicate()?;
1589                self.builder
1590                    .build_conditional_branch(cond_err_pred, err_block, ok_block)
1591                    .map_err(|e| {
1592                        CodeGenError::LLVMError(format!("Failed to branch on cond_err: {e}"))
1593                    })?;
1594
1595                // Error path: emit ExprError and decide destination
1596                self.builder.position_at_end(err_block);
1597                self.compile_time_event_bytes_upper_bound = entry_event_bytes;
1598                self.emit_current_condition_exprerror(expr_index, "cond")?;
1599                // Decide where to go on error: if else_body is If (else-if), go to else_block to continue;
1600                // otherwise, skip else (suppress) and jump to merge.
1601                let goto_else = matches!(else_body.as_deref(), Some(Statement::If { .. }));
1602                let err_path_event_bytes = self.compile_time_event_bytes_upper_bound;
1603                if goto_else {
1604                    self.builder
1605                        .build_unconditional_branch(else_block)
1606                        .map_err(|e| {
1607                            CodeGenError::LLVMError(format!(
1608                                "Failed to branch to else on error: {e}"
1609                            ))
1610                        })?;
1611                } else {
1612                    self.builder
1613                        .build_unconditional_branch(merge_block)
1614                        .map_err(|e| {
1615                            CodeGenError::LLVMError(format!(
1616                                "Failed to branch to merge on error: {e}"
1617                            ))
1618                        })?;
1619                }
1620
1621                // No-error path: branch on boolean condition
1622                self.builder.position_at_end(ok_block);
1623                self.compile_time_event_bytes_upper_bound = entry_event_bytes;
1624                self.builder
1625                    .build_conditional_branch(cond_bool, then_block, else_block)
1626                    .map_err(|e| {
1627                        CodeGenError::LLVMError(format!("Failed to create branch: {e}"))
1628                    })?;
1629
1630                // Build then block
1631                self.builder.position_at_end(then_block);
1632                self.compile_time_event_bytes_upper_bound = entry_event_bytes;
1633                let mut then_instructions = 0u16;
1634                self.enter_scope();
1635                for stmt in then_body {
1636                    then_instructions += self.compile_statement(stmt)?;
1637                }
1638                self.exit_scope();
1639                let then_event_bytes = self.compile_time_event_bytes_upper_bound;
1640                self.builder
1641                    .build_unconditional_branch(merge_block)
1642                    .map_err(|e| {
1643                        CodeGenError::LLVMError(format!("Failed to branch to merge: {e}"))
1644                    })?;
1645
1646                // Build else block
1647                self.builder.position_at_end(else_block);
1648                let else_entry_event_bytes = if goto_else {
1649                    entry_event_bytes.max(err_path_event_bytes)
1650                } else {
1651                    entry_event_bytes
1652                };
1653                self.compile_time_event_bytes_upper_bound = else_entry_event_bytes;
1654                let mut else_instructions = 0u16;
1655                if let Some(else_stmt) = else_body {
1656                    self.enter_scope();
1657                    else_instructions += self.compile_statement(else_stmt)?;
1658                    self.exit_scope();
1659                }
1660                self.builder
1661                    .build_unconditional_branch(merge_block)
1662                    .map_err(|e| {
1663                        CodeGenError::LLVMError(format!("Failed to branch to merge: {e}"))
1664                    })?;
1665                let else_event_bytes = self.compile_time_event_bytes_upper_bound;
1666
1667                // Continue with merge block
1668                self.builder.position_at_end(merge_block);
1669                self.compile_time_event_bytes_upper_bound = if goto_else {
1670                    then_event_bytes.max(else_event_bytes)
1671                } else {
1672                    then_event_bytes
1673                        .max(else_event_bytes)
1674                        .max(err_path_event_bytes)
1675                };
1676
1677                // Return the maximum instructions from either branch
1678                Ok(std::cmp::max(then_instructions, else_instructions))
1679            }
1680            Statement::Block(nested_statements) => {
1681                let mut total_instructions = 0u16;
1682                self.enter_scope();
1683                for stmt in nested_statements {
1684                    total_instructions += self.compile_statement(stmt)?;
1685                }
1686                self.exit_scope();
1687                Ok(total_instructions)
1688            }
1689            Statement::TracePoint { pattern: _, body } => {
1690                let mut total_instructions = 0u16;
1691                // Start a new scope for the trace body
1692                self.enter_scope();
1693                for stmt in body {
1694                    total_instructions += self.compile_statement(stmt)?;
1695                }
1696                self.exit_scope();
1697                Ok(total_instructions)
1698            }
1699            _ => {
1700                warn!("Unsupported statement type: {:?}", statement);
1701                Ok(0)
1702            }
1703        }
1704    }
1705
1706    /// Compile print statement and generate LLVM IR on-demand
1707    pub fn compile_print_statement(&mut self, print_stmt: &PrintStatement) -> Result<u16> {
1708        info!("Compiling print statement: {:?}", print_stmt);
1709
1710        match print_stmt {
1711            PrintStatement::String(s) => {
1712                info!("Processing string literal: {}", s);
1713                // 1. Add string to TraceContext
1714                let string_index = self.trace_context.add_string(s.to_string());
1715                // 2. Generate eBPF code for PrintStringIndex
1716                self.generate_print_string_index(string_index)?;
1717                Ok(1) // Generated 1 instruction
1718            }
1719            PrintStatement::Variable(var_name) => {
1720                info!("Processing variable: {}", var_name);
1721                let expr = crate::script::Expr::Variable(var_name.clone());
1722                let arg = self.resolve_expr_to_arg(&expr)?;
1723                let n = self.emit_print_from_arg(arg)?;
1724                tracing::trace!(
1725                    var_name = %var_name,
1726                    instructions = n,
1727                    "compile_print_statement: emitted via unified resolver"
1728                );
1729                Ok(n)
1730            }
1731            PrintStatement::ComplexVariable(expr) => {
1732                info!("Processing complex variable: {:?}", expr);
1733                let arg = self.compile_print_expr_with_builtin_exprerror(expr, |ctx| {
1734                    ctx.resolve_expr_to_arg(expr)
1735                })?;
1736                let n = self.emit_print_from_arg(arg)?;
1737                tracing::trace!(
1738                    instructions = n,
1739                    "compile_print_statement: emitted via unified resolver"
1740                );
1741                Ok(n)
1742            }
1743            PrintStatement::Formatted { format, args } => {
1744                info!(
1745                    "Processing formatted print: '{}' with {} args",
1746                    format,
1747                    args.len()
1748                );
1749                self.compile_formatted_print(format, args)
1750            }
1751        }
1752    }
1753
1754    /// Compile formatted print statement: collect all variable data and send as PrintComplexFormat instruction
1755    fn compile_formatted_print(
1756        &mut self,
1757        format: &str,
1758        args: &[crate::script::ast::Expr],
1759    ) -> Result<u16> {
1760        info!(
1761            "Compiling formatted print: '{}' with {} arguments",
1762            format,
1763            args.len()
1764        );
1765        let format_string_index = self.trace_context.add_string(format.to_string());
1766        let mut complex_args: Vec<ComplexArg<'ctx>> = Vec::with_capacity(args.len());
1767
1768        // Parse placeholders from the format string to support extended specifiers
1769        #[derive(Clone, Copy, Debug, PartialEq)]
1770        enum Conv {
1771            Default,
1772            HexLower,
1773            HexUpper,
1774            Ptr,
1775            Ascii,
1776        }
1777        #[derive(Clone, Debug, PartialEq)]
1778        enum LenSpec {
1779            None,
1780            Static(usize),
1781            Star,
1782            Capture(String),
1783        }
1784
1785        fn parse_slots(fmt: &str) -> Vec<(Conv, LenSpec)> {
1786            let mut res = Vec::new();
1787            let mut it = fmt.chars().peekable();
1788            while let Some(ch) = it.next() {
1789                if ch == '{' {
1790                    if it.peek() == Some(&'{') {
1791                        it.next();
1792                        continue;
1793                    }
1794                    let mut content = String::new();
1795                    for c in it.by_ref() {
1796                        if c == '}' {
1797                            break;
1798                        }
1799                        content.push(c);
1800                    }
1801                    if content.is_empty() {
1802                        res.push((Conv::Default, LenSpec::None));
1803                    } else if let Some(rest) = content.strip_prefix(':') {
1804                        let mut sit = rest.chars();
1805                        let conv = match sit.next().unwrap_or(' ') {
1806                            'x' => Conv::HexLower,
1807                            'X' => Conv::HexUpper,
1808                            'p' => Conv::Ptr,
1809                            's' => Conv::Ascii,
1810                            _ => Conv::Default,
1811                        };
1812                        let rest: String = sit.collect();
1813                        let lens = if rest.is_empty() {
1814                            LenSpec::None
1815                        } else if let Some(r) = rest.strip_prefix('.') {
1816                            if r == "*" {
1817                                LenSpec::Star
1818                            } else if let Some(s) = r.strip_suffix('$') {
1819                                LenSpec::Capture(s.to_string())
1820                            } else if r.chars().all(|c| c.is_ascii_digit()) {
1821                                LenSpec::Static(r.parse::<usize>().unwrap_or(0))
1822                            } else {
1823                                LenSpec::None
1824                            }
1825                        } else {
1826                            LenSpec::None
1827                        };
1828                        res.push((conv, lens));
1829                    } else {
1830                        res.push((Conv::Default, LenSpec::None));
1831                    }
1832                }
1833            }
1834            res
1835        }
1836
1837        let slots = parse_slots(format);
1838        let mut ai = 0usize; // arg cursor
1839        for (conv, lens) in slots.into_iter() {
1840            match conv {
1841                Conv::Default => {
1842                    if ai >= args.len() {
1843                        break;
1844                    }
1845                    let expr = &args[ai];
1846                    let a = self.compile_print_expr_with_builtin_exprerror(expr, |ctx| {
1847                        ctx.resolve_expr_to_arg(expr)
1848                    })?;
1849                    complex_args.push(a);
1850                    ai += 1;
1851                }
1852                Conv::Ptr => {
1853                    if ai >= args.len() {
1854                        break;
1855                    }
1856                    // Force pointer address payload (u64) regardless of DWARF shape
1857                    let expr = &args[ai];
1858                    // Try compile to IntValue or PointerValue
1859                    let val = self.compile_expr(expr)?;
1860                    let iv = match val {
1861                        BasicValueEnum::IntValue(iv) => iv,
1862                        BasicValueEnum::PointerValue(pv) => self
1863                            .builder
1864                            .build_ptr_to_int(pv, self.context.i64_type(), "ptr_to_i64")
1865                            .map_err(|e| CodeGenError::Builder(e.to_string()))?,
1866                        _ => self
1867                            .compile_dwarf_expression(expr)
1868                            .and_then(|bv| match bv {
1869                                BasicValueEnum::IntValue(iv) => Ok(iv),
1870                                BasicValueEnum::PointerValue(pv) => self
1871                                    .builder
1872                                    .build_ptr_to_int(pv, self.context.i64_type(), "ptr_to_i64")
1873                                    .map_err(|e| CodeGenError::Builder(e.to_string())),
1874                                _ => Err(CodeGenError::TypeError("pointer expected".into())),
1875                            })?,
1876                    };
1877                    complex_args.push(ComplexArg {
1878                        var_name_index: self
1879                            .trace_context
1880                            .add_variable_name(self.expr_to_name(expr)),
1881                        type_index: self.add_synthesized_type_index_for_kind(TypeKind::Pointer),
1882                        access_path: Vec::new(),
1883                        data_len: 8,
1884                        source: ComplexArgSource::ComputedInt {
1885                            value: iv,
1886                            byte_len: 8,
1887                        },
1888                    });
1889                    ai += 1;
1890                }
1891                Conv::HexLower | Conv::HexUpper | Conv::Ascii => {
1892                    // Memory dump; handle static length at compile time. Other cases use default read and let user space trim.
1893                    // Handle star: consume length arg (as computed int) then value arg
1894                    let wants_ascii = matches!(conv, Conv::Ascii);
1895                    match lens {
1896                        LenSpec::Static(n) if ai < args.len() => {
1897                            // Resolve value expr address
1898                            let expr = &args[ai];
1899                            // Try get pointer address directly from expr value
1900                            let val = self.compile_expr(expr).ok();
1901                            let mut addr_iv: Option<IntValue> = match val {
1902                                Some(BasicValueEnum::PointerValue(pv)) => Some(
1903                                    self.builder
1904                                        .build_ptr_to_int(pv, self.context.i64_type(), "ptr_to_i64")
1905                                        .map_err(|e| CodeGenError::Builder(e.to_string()))?,
1906                                ),
1907                                _ => None,
1908                            };
1909                            // If compiled value is IntValue but DWARF type is a pointer, treat the IntValue as an address (pointer value)
1910                            if addr_iv.is_none() {
1911                                if let Some(BasicValueEnum::IntValue(iv)) = val {
1912                                    if let Some(var) = self.query_dwarf_for_complex_expr(expr)? {
1913                                        if let Some(ref t) = var.dwarf_type {
1914                                            if matches!(
1915                                                t,
1916                                                ghostscope_dwarf::TypeInfo::PointerType { .. }
1917                                            ) {
1918                                                addr_iv = Some(iv);
1919                                            }
1920                                        }
1921                                    }
1922                                }
1923                            }
1924                            let addr_iv = if let Some(iv) = addr_iv {
1925                                iv
1926                            } else {
1927                                // Fallback: DWARF address (for arrays/char[N])
1928                                let var =
1929                                    self.query_dwarf_for_complex_expr(expr)?.ok_or_else(|| {
1930                                        CodeGenError::VariableNotFound(format!("{expr:?}"))
1931                                    })?;
1932                                let mod_hint = self.take_module_hint();
1933                                self.evaluation_result_to_address_with_hint(
1934                                    &var.evaluation_result,
1935                                    None,
1936                                    mod_hint.as_deref(),
1937                                )?
1938                            };
1939                            complex_args.push(ComplexArg {
1940                                var_name_index: self
1941                                    .trace_context
1942                                    .add_variable_name(self.expr_to_name(expr)),
1943                                type_index: self
1944                                    .trace_context
1945                                    .add_type(ghostscope_dwarf::TypeInfo::ArrayType {
1946                                    element_type: Box::new(ghostscope_dwarf::TypeInfo::BaseType {
1947                                        name: "u8".into(),
1948                                        size: 1,
1949                                        encoding: ghostscope_dwarf::constants::DW_ATE_unsigned_char
1950                                            .0
1951                                            as u16,
1952                                    }),
1953                                    element_count: Some(n as u64),
1954                                    total_size: Some(n as u64),
1955                                }),
1956                                access_path: Vec::new(),
1957                                data_len: n,
1958                                source: ComplexArgSource::MemDump {
1959                                    src_addr: addr_iv,
1960                                    len: n,
1961                                },
1962                            });
1963                            ai += 1;
1964                        }
1965                        LenSpec::Star => {
1966                            // Dynamic length: consume length arg, then create a dynamic mem-dump for value
1967                            if ai + 1 >= args.len() {
1968                                break;
1969                            }
1970                            // length argument
1971                            let len_expr = &args[ai];
1972                            let len_val = self.compile_expr(len_expr)?;
1973                            let (len_iv, byte_len) = match len_val {
1974                                BasicValueEnum::IntValue(iv) => (iv, 8usize),
1975                                _ => {
1976                                    return Err(CodeGenError::TypeError(
1977                                        "length must be integer".into(),
1978                                    ))
1979                                }
1980                            };
1981                            complex_args.push(ComplexArg {
1982                                var_name_index: self
1983                                    .trace_context
1984                                    .add_variable_name("__len".into()),
1985                                type_index: self.add_synthesized_type_index_for_kind(TypeKind::U64),
1986                                access_path: Vec::new(),
1987                                data_len: byte_len,
1988                                source: ComplexArgSource::ComputedInt {
1989                                    value: len_iv,
1990                                    byte_len,
1991                                },
1992                            });
1993
1994                            // value expression -> dynamic memdump with cap
1995                            let val_expr = &args[ai + 1];
1996                            // Resolve base address either from pointer-typed value or DWARF evaluation
1997                            let val = self.compile_expr(val_expr).ok();
1998                            let mut addr_iv: Option<IntValue> = match val {
1999                                Some(BasicValueEnum::PointerValue(pv)) => Some(
2000                                    self.builder
2001                                        .build_ptr_to_int(pv, self.context.i64_type(), "ptr_to_i64")
2002                                        .map_err(|e| CodeGenError::Builder(e.to_string()))?,
2003                                ),
2004                                _ => None,
2005                            };
2006                            if addr_iv.is_none() {
2007                                if let Some(BasicValueEnum::IntValue(iv)) = val {
2008                                    if let Some(var) =
2009                                        self.query_dwarf_for_complex_expr(val_expr)?
2010                                    {
2011                                        if let Some(ref t) = var.dwarf_type {
2012                                            if matches!(
2013                                                t,
2014                                                ghostscope_dwarf::TypeInfo::PointerType { .. }
2015                                            ) {
2016                                                addr_iv = Some(iv);
2017                                            }
2018                                        }
2019                                    }
2020                                }
2021                            }
2022                            let addr_iv = if let Some(iv) = addr_iv {
2023                                iv
2024                            } else {
2025                                let var = self.query_dwarf_for_complex_expr(val_expr)?.ok_or_else(
2026                                    || CodeGenError::VariableNotFound(format!("{val_expr:?}")),
2027                                )?;
2028                                let mod_hint = self.take_module_hint();
2029                                self.evaluation_result_to_address_with_hint(
2030                                    &var.evaluation_result,
2031                                    None,
2032                                    mod_hint.as_deref(),
2033                                )?
2034                            };
2035                            // Reserve up to configured per-arg cap for dynamic slices
2036                            let cap = self.compile_options.mem_dump_cap as usize;
2037                            complex_args.push(ComplexArg {
2038                                var_name_index: self
2039                                    .trace_context
2040                                    .add_variable_name(self.expr_to_name(val_expr)),
2041                                type_index: self
2042                                    .trace_context
2043                                    .add_type(ghostscope_dwarf::TypeInfo::ArrayType {
2044                                    element_type: Box::new(ghostscope_dwarf::TypeInfo::BaseType {
2045                                        name: "u8".into(),
2046                                        size: 1,
2047                                        encoding: ghostscope_dwarf::constants::DW_ATE_unsigned_char
2048                                            .0
2049                                            as u16,
2050                                    }),
2051                                    element_count: Some(cap as u64),
2052                                    total_size: Some(cap as u64),
2053                                }),
2054                                access_path: Vec::new(),
2055                                data_len: cap,
2056                                source: ComplexArgSource::MemDumpDynamic {
2057                                    src_addr: addr_iv,
2058                                    len_value: len_iv,
2059                                    max_len: cap,
2060                                },
2061                            });
2062                            ai += 2;
2063                        }
2064                        LenSpec::Capture(name) => {
2065                            // Use script variable `name` as length; emit a length argument + a dynamic mem-dump argument
2066                            if ai >= args.len() {
2067                                break;
2068                            }
2069                            if !self.variable_exists(&name) {
2070                                return Err(CodeGenError::TypeError(format!(
2071                                    "capture length variable '{name}' not found"
2072                                )));
2073                            }
2074                            // length as computed int
2075                            let len_val = self.load_variable(&name)?;
2076                            let (len_iv, byte_len) = match len_val {
2077                                BasicValueEnum::IntValue(iv) => (iv, 8usize),
2078                                BasicValueEnum::PointerValue(pv) => (
2079                                    self.builder
2080                                        .build_ptr_to_int(
2081                                            pv,
2082                                            self.context.i64_type(),
2083                                            "len_ptr_to_i64",
2084                                        )
2085                                        .map_err(|e| CodeGenError::Builder(e.to_string()))?,
2086                                    8usize,
2087                                ),
2088                                _ => {
2089                                    return Err(CodeGenError::TypeError(
2090                                        "length must be integer/pointer".into(),
2091                                    ))
2092                                }
2093                            };
2094                            complex_args.push(ComplexArg {
2095                                var_name_index: self.trace_context.add_variable_name(name.clone()),
2096                                type_index: self.add_synthesized_type_index_for_kind(TypeKind::U64),
2097                                access_path: Vec::new(),
2098                                data_len: byte_len,
2099                                source: ComplexArgSource::ComputedInt {
2100                                    value: len_iv,
2101                                    byte_len,
2102                                },
2103                            });
2104
2105                            // value
2106                            let val_expr = &args[ai];
2107                            let val = self.compile_expr(val_expr).ok();
2108                            let mut addr_iv: Option<IntValue> = match val {
2109                                Some(BasicValueEnum::PointerValue(pv)) => Some(
2110                                    self.builder
2111                                        .build_ptr_to_int(pv, self.context.i64_type(), "ptr_to_i64")
2112                                        .map_err(|e| CodeGenError::Builder(e.to_string()))?,
2113                                ),
2114                                _ => None,
2115                            };
2116                            if addr_iv.is_none() {
2117                                if let Some(BasicValueEnum::IntValue(iv)) = val {
2118                                    if let Some(var) =
2119                                        self.query_dwarf_for_complex_expr(val_expr)?
2120                                    {
2121                                        if let Some(ref t) = var.dwarf_type {
2122                                            if matches!(
2123                                                t,
2124                                                ghostscope_dwarf::TypeInfo::PointerType { .. }
2125                                            ) {
2126                                                addr_iv = Some(iv);
2127                                            }
2128                                        }
2129                                    }
2130                                }
2131                            }
2132                            let addr_iv = if let Some(iv) = addr_iv {
2133                                iv
2134                            } else {
2135                                let var = self.query_dwarf_for_complex_expr(val_expr)?.ok_or_else(
2136                                    || CodeGenError::VariableNotFound(format!("{val_expr:?}")),
2137                                )?;
2138                                let mod_hint = self.take_module_hint();
2139                                self.evaluation_result_to_address_with_hint(
2140                                    &var.evaluation_result,
2141                                    None,
2142                                    mod_hint.as_deref(),
2143                                )?
2144                            };
2145                            let cap = self.compile_options.mem_dump_cap as usize;
2146                            complex_args.push(ComplexArg {
2147                                var_name_index: self
2148                                    .trace_context
2149                                    .add_variable_name(self.expr_to_name(val_expr)),
2150                                type_index: self
2151                                    .trace_context
2152                                    .add_type(ghostscope_dwarf::TypeInfo::ArrayType {
2153                                    element_type: Box::new(ghostscope_dwarf::TypeInfo::BaseType {
2154                                        name: "u8".into(),
2155                                        size: 1,
2156                                        encoding: ghostscope_dwarf::constants::DW_ATE_unsigned_char
2157                                            .0
2158                                            as u16,
2159                                    }),
2160                                    element_count: Some(cap as u64),
2161                                    total_size: Some(cap as u64),
2162                                }),
2163                                access_path: Vec::new(),
2164                                data_len: cap,
2165                                source: ComplexArgSource::MemDumpDynamic {
2166                                    src_addr: addr_iv,
2167                                    len_value: len_iv,
2168                                    max_len: cap,
2169                                },
2170                            });
2171                            ai += 1;
2172                        }
2173                        _ => {
2174                            // None: resolve value directly
2175                            if ai >= args.len() {
2176                                break;
2177                            }
2178                            complex_args.push(self.resolve_expr_to_arg(&args[ai])?);
2179                            ai += 1;
2180                        }
2181                    }
2182                    let _ = wants_ascii; // reserved for future per-arg metadata
2183                }
2184            }
2185        }
2186        self.generate_print_complex_format_instruction(format_string_index, &complex_args)?;
2187        Ok(1)
2188    }
2189
2190    /// Resolve variable with correct priority: script variables first, then DWARF variables
2191    /// This method is copied from protocol.rs to maintain functionality
2192    pub fn resolve_variable_with_priority(&mut self, var_name: &str) -> Result<(u16, TypeKind)> {
2193        info!("Resolving variable '{}' with correct priority", var_name);
2194
2195        // Step 1: Check if it's a script-defined variable first
2196        if self.variable_exists(var_name) {
2197            info!("Found script variable: {}", var_name);
2198
2199            // Get the variable's LLVM value to infer type
2200            let loaded_value = self.load_variable(var_name)?;
2201            let type_encoding = self.infer_type_from_llvm_value(&loaded_value);
2202
2203            // Add to TraceContext
2204            let var_name_index = self.trace_context.add_variable_name(var_name.to_string());
2205
2206            return Ok((var_name_index, type_encoding));
2207        }
2208
2209        // Step 2: If not found in script variables, try DWARF variables
2210        info!(
2211            "Variable '{}' not found in script variables, checking DWARF",
2212            var_name
2213        );
2214
2215        let compile_context = self.get_compile_time_context()?.clone();
2216        let variable_with_eval = match self.query_dwarf_for_variable(var_name)? {
2217            Some(var) => var,
2218            None => {
2219                return Err(CodeGenError::VariableNotFound(format!(
2220                    "Variable '{}' not found in script or DWARF at PC 0x{:x} in module '{}'",
2221                    var_name, compile_context.pc_address, compile_context.module_path
2222                )));
2223            }
2224        };
2225
2226        // Convert DWARF type information to TypeKind using existing method
2227        let dwarf_type = variable_with_eval.dwarf_type.as_ref().ok_or_else(|| {
2228            CodeGenError::DwarfError("Variable has no DWARF type information".to_string())
2229        })?;
2230        let type_encoding = TypeKind::from(dwarf_type);
2231
2232        // Add to StringTable
2233        let var_name_index = self.trace_context.add_variable_name(var_name.to_string());
2234
2235        info!(
2236            "DWARF variable '{}' resolved successfully with type: {:?}",
2237            var_name, type_encoding
2238        );
2239
2240        Ok((var_name_index, type_encoding))
2241    }
2242
2243    /// Synthesize a DWARF-like TypeInfo for a basic TypeKind (for script variables)
2244    fn synthesize_typeinfo_for_typekind(&self, kind: TypeKind) -> ghostscope_dwarf::TypeInfo {
2245        use ghostscope_dwarf::constants::{
2246            DW_ATE_boolean, DW_ATE_float, DW_ATE_signed, DW_ATE_signed_char, DW_ATE_unsigned,
2247        };
2248        use ghostscope_dwarf::TypeInfo as TI;
2249
2250        match kind {
2251            TypeKind::Bool => TI::BaseType {
2252                name: "bool".to_string(),
2253                size: 1,
2254                encoding: DW_ATE_boolean.0 as u16,
2255            },
2256            TypeKind::F32 => TI::BaseType {
2257                name: "f32".to_string(),
2258                size: 4,
2259                encoding: DW_ATE_float.0 as u16,
2260            },
2261            TypeKind::F64 => TI::BaseType {
2262                name: "f64".to_string(),
2263                size: 8,
2264                encoding: DW_ATE_float.0 as u16,
2265            },
2266            TypeKind::I8 => TI::BaseType {
2267                name: "i8".to_string(),
2268                size: 1,
2269                encoding: DW_ATE_signed_char.0 as u16,
2270            },
2271            TypeKind::I16 => TI::BaseType {
2272                name: "i16".to_string(),
2273                size: 2,
2274                encoding: DW_ATE_signed.0 as u16,
2275            },
2276            TypeKind::I32 => TI::BaseType {
2277                name: "i32".to_string(),
2278                size: 4,
2279                encoding: DW_ATE_signed.0 as u16,
2280            },
2281            TypeKind::I64 => TI::BaseType {
2282                name: "i64".to_string(),
2283                size: 8,
2284                encoding: DW_ATE_signed.0 as u16,
2285            },
2286            TypeKind::U8 | TypeKind::Char => TI::BaseType {
2287                name: "u8".to_string(),
2288                size: 1,
2289                encoding: DW_ATE_unsigned.0 as u16,
2290            },
2291            TypeKind::U16 => TI::BaseType {
2292                name: "u16".to_string(),
2293                size: 2,
2294                encoding: DW_ATE_unsigned.0 as u16,
2295            },
2296            TypeKind::U32 => TI::BaseType {
2297                name: "u32".to_string(),
2298                size: 4,
2299                encoding: DW_ATE_unsigned.0 as u16,
2300            },
2301            TypeKind::U64 => TI::BaseType {
2302                name: "u64".to_string(),
2303                size: 8,
2304                encoding: DW_ATE_unsigned.0 as u16,
2305            },
2306            TypeKind::Pointer | TypeKind::CString | TypeKind::String | TypeKind::Unknown => {
2307                // Use void* as a reasonable default for pointers/strings in script land
2308                TI::PointerType {
2309                    target_type: Box::new(TI::UnknownType {
2310                        name: "void".to_string(),
2311                    }),
2312                    size: 8,
2313                }
2314            }
2315            TypeKind::NullPointer => TI::PointerType {
2316                target_type: Box::new(TI::UnknownType {
2317                    name: "void".to_string(),
2318                }),
2319                size: 8,
2320            },
2321            _ => TI::BaseType {
2322                name: "i64".to_string(),
2323                size: 8,
2324                encoding: DW_ATE_signed.0 as u16,
2325            },
2326        }
2327    }
2328
2329    fn add_synthesized_type_index_for_kind(&mut self, kind: TypeKind) -> u16 {
2330        let ti = self.synthesize_typeinfo_for_typekind(kind);
2331        self.trace_context.add_type(ti)
2332    }
2333
2334    /// Infer TypeKind from LLVM value type
2335    /// Copied from protocol.rs
2336    fn infer_type_from_llvm_value(&self, value: &BasicValueEnum<'_>) -> TypeKind {
2337        match value {
2338            BasicValueEnum::IntValue(int_val) => {
2339                match int_val.get_type().get_bit_width() {
2340                    1 => TypeKind::Bool,
2341                    8 => TypeKind::I8, // Default to signed for script variables
2342                    16 => TypeKind::I16,
2343                    32 => TypeKind::I32,
2344                    64 => TypeKind::I64,
2345                    _ => TypeKind::I64, // Default fallback
2346                }
2347            }
2348            BasicValueEnum::FloatValue(float_val) => {
2349                match float_val.get_type() {
2350                    t if t == self.context.f32_type() => TypeKind::F32,
2351                    t if t == self.context.f64_type() => TypeKind::F64,
2352                    _ => TypeKind::F64, // Default fallback
2353                }
2354            }
2355            BasicValueEnum::PointerValue(_) => TypeKind::Pointer,
2356            _ => TypeKind::I64, // Conservative default
2357        }
2358    }
2359
2360    /// Generate eBPF code for PrintComplexFormat instruction with runtime reads for variables
2361    fn generate_print_complex_format_instruction(
2362        &mut self,
2363        format_string_index: u16,
2364        complex_args: &[ComplexArg<'ctx>],
2365    ) -> Result<()> {
2366        use InstructionType::PrintComplexFormat as IT;
2367
2368        // Keep a single formatted print within the remaining event budget on the current
2369        // control-flow path, while still leaving room for EndInstruction.
2370        let instruction_budget = print_complex_format_instruction_budget(
2371            self.compile_options.max_trace_event_size as usize,
2372            self.compile_time_event_bytes_upper_bound,
2373        );
2374        let fixed_overhead = std::mem::size_of::<InstructionHeader>()
2375            + std::mem::size_of::<PrintComplexFormatData>();
2376
2377        // First pass: accumulate header bytes and static payload, record dynamic args
2378        let mut arg_count = 0u8;
2379        let mut headers_total = 0usize;
2380        let mut static_payload_total = 0usize;
2381        let mut dynamic_max_lens: Vec<usize> = Vec::new();
2382        let mut header_lens: Vec<usize> = Vec::with_capacity(complex_args.len());
2383        for a in complex_args {
2384            // Header bytes per-arg: var_name_index(2) + type_index(2) + access_path_len(1) + status(1) + data_len(2) + access_path
2385            let header_len = 2 + 2 + 1 + 1 + 2 + a.access_path.len();
2386            header_lens.push(header_len);
2387            headers_total += header_len;
2388
2389            match &a.source {
2390                ComplexArgSource::ImmediateBytes { bytes } => static_payload_total += bytes.len(),
2391                ComplexArgSource::AddressValue { .. } => static_payload_total += 8,
2392                ComplexArgSource::RuntimeRead { .. } => {
2393                    static_payload_total +=
2394                        std::cmp::max(a.data_len, DYNAMIC_READ_ERROR_PAYLOAD_LEN)
2395                }
2396                ComplexArgSource::ComputedInt { byte_len, .. } => static_payload_total += *byte_len,
2397                ComplexArgSource::MemDump { len, .. } => {
2398                    static_payload_total += std::cmp::max(*len, DYNAMIC_READ_ERROR_PAYLOAD_LEN)
2399                }
2400                ComplexArgSource::MemDumpDynamic { max_len, .. } => dynamic_max_lens.push(*max_len),
2401            }
2402            arg_count = arg_count.saturating_add(1);
2403        }
2404
2405        // Static payload keeps its existing layout; dynamic payload shares the remaining
2406        // instruction budget fairly so later {:s.*}/{:x.*} arguments do not get starved.
2407        let remaining_for_payload = instruction_budget
2408            .saturating_sub(fixed_overhead)
2409            .saturating_sub(headers_total)
2410            .saturating_sub(static_payload_total);
2411        let dynamic_reservations =
2412            allocate_dynamic_payload_reservations(&dynamic_max_lens, remaining_for_payload);
2413        let mut dynamic_reservations_iter = dynamic_reservations.into_iter();
2414
2415        // Second pass: decide effective reserved payload for each arg
2416        // Default to computed static payload; dynamic args share the event-derived budget
2417        let mut effective_reserved: Vec<usize> = Vec::with_capacity(complex_args.len());
2418        for a in complex_args {
2419            let reserved = match &a.source {
2420                ComplexArgSource::ImmediateBytes { bytes } => bytes.len(),
2421                ComplexArgSource::AddressValue { .. } => 8,
2422                ComplexArgSource::RuntimeRead { .. } => {
2423                    std::cmp::max(a.data_len, DYNAMIC_READ_ERROR_PAYLOAD_LEN)
2424                }
2425                ComplexArgSource::ComputedInt { byte_len, .. } => *byte_len,
2426                ComplexArgSource::MemDump { len, .. } => {
2427                    std::cmp::max(*len, DYNAMIC_READ_ERROR_PAYLOAD_LEN)
2428                }
2429                ComplexArgSource::MemDumpDynamic { .. } => {
2430                    dynamic_reservations_iter.next().unwrap_or(0)
2431                }
2432            };
2433            effective_reserved.push(reserved);
2434        }
2435
2436        // Now compute final inst_data_size using effective reservations
2437        let total_args_payload: usize =
2438            header_lens.iter().sum::<usize>() + effective_reserved.iter().sum::<usize>();
2439        let inst_data_size = std::mem::size_of::<PrintComplexFormatData>() + total_args_payload;
2440        let total_size = std::mem::size_of::<InstructionHeader>() + inst_data_size;
2441
2442        // Reserve buffer directly in accumulation buffer to avoid extra copy
2443        let buffer = self.reserve_instruction_region(total_size as u64);
2444
2445        // Avoid memset; global buffer is zero-initialized
2446
2447        // Write InstructionHeader
2448        let inst_type_val = self.context.i8_type().const_int(IT as u8 as u64, false);
2449        self.builder
2450            .build_store(buffer, inst_type_val)
2451            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store inst_type: {e}")))?;
2452        // data_length at +1
2453        let data_length_ptr = unsafe {
2454            self.builder
2455                .build_gep(
2456                    self.context.i8_type(),
2457                    buffer,
2458                    &[self.context.i32_type().const_int(1, false)],
2459                    "data_length_ptr",
2460                )
2461                .map_err(|e| {
2462                    CodeGenError::LLVMError(format!("Failed to get data_length GEP: {e}"))
2463                })?
2464        };
2465        let data_length_i16_ptr = self
2466            .builder
2467            .build_pointer_cast(
2468                data_length_ptr,
2469                self.context.ptr_type(AddressSpace::default()),
2470                "data_length_i16_ptr",
2471            )
2472            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast data_length ptr: {e}")))?;
2473        let data_length_val = self
2474            .context
2475            .i16_type()
2476            .const_int(inst_data_size as u64, false);
2477        self.builder
2478            .build_store(data_length_i16_ptr, data_length_val)
2479            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_length: {e}")))?;
2480
2481        // Write PrintComplexFormatData at offset 4
2482        let data_ptr = unsafe {
2483            self.builder
2484                .build_gep(
2485                    self.context.i8_type(),
2486                    buffer,
2487                    &[self.context.i32_type().const_int(4, false)],
2488                    "pcf_data_ptr",
2489                )
2490                .map_err(|e| {
2491                    CodeGenError::LLVMError(format!("Failed to get pcf_data_ptr GEP: {e}"))
2492                })?
2493        };
2494
2495        // format_string_index (u16) at +0
2496        let fsi_ptr = self
2497            .builder
2498            .build_pointer_cast(
2499                data_ptr,
2500                self.context.ptr_type(AddressSpace::default()),
2501                "fsi_ptr",
2502            )
2503            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast fsi_ptr: {e}")))?;
2504        let fsi_val = self
2505            .context
2506            .i16_type()
2507            .const_int(format_string_index as u64, false);
2508        self.builder
2509            .build_store(fsi_ptr, fsi_val)
2510            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store fsi: {e}")))?;
2511        // arg_count (u8) at +2
2512        let arg_cnt_ptr = unsafe {
2513            self.builder
2514                .build_gep(
2515                    self.context.i8_type(),
2516                    data_ptr,
2517                    &[self.context.i32_type().const_int(2, false)],
2518                    "arg_count_ptr",
2519                )
2520                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get arg_count GEP: {e}")))?
2521        };
2522        self.builder
2523            .build_store(
2524                arg_cnt_ptr,
2525                self.context.i8_type().const_int(arg_count as u64, false),
2526            )
2527            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store arg_count: {e}")))?;
2528
2529        // Start of variable payload after PrintComplexFormatData — use computed effective reservations
2530        let mut offset = std::mem::size_of::<PrintComplexFormatData>();
2531        for (arg_index, a) in complex_args.iter().enumerate() {
2532            // Per-arg reserved payload length
2533            let reserved_len = effective_reserved[arg_index];
2534
2535            // Base pointer = data_ptr + offset
2536            let arg_base = unsafe {
2537                self.builder
2538                    .build_gep(
2539                        self.context.i8_type(),
2540                        data_ptr,
2541                        &[self.context.i32_type().const_int(offset as u64, false)],
2542                        "arg_base",
2543                    )
2544                    .map_err(|e| {
2545                        CodeGenError::LLVMError(format!("Failed to get arg_base GEP: {e}"))
2546                    })?
2547            };
2548
2549            // var_name_index(u16) at +0
2550            let vni_cast = self
2551                .builder
2552                .build_pointer_cast(
2553                    arg_base,
2554                    self.context.ptr_type(AddressSpace::default()),
2555                    "vni_cast",
2556                )
2557                .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast vni ptr: {e}")))?;
2558            self.builder
2559                .build_store(
2560                    vni_cast,
2561                    self.context
2562                        .i16_type()
2563                        .const_int(a.var_name_index as u64, false),
2564                )
2565                .map_err(|e| CodeGenError::LLVMError(format!("Failed to store vni: {e}")))?;
2566
2567            // type_index(u16) at +2
2568            let ti_ptr = unsafe {
2569                self.builder
2570                    .build_gep(
2571                        self.context.i8_type(),
2572                        arg_base,
2573                        &[self.context.i32_type().const_int(2, false)],
2574                        "ti_ptr",
2575                    )
2576                    .map_err(|e| CodeGenError::LLVMError(format!("Failed to get ti GEP: {e}")))?
2577            };
2578            let ti_cast = self
2579                .builder
2580                .build_pointer_cast(
2581                    ti_ptr,
2582                    self.context.ptr_type(AddressSpace::default()),
2583                    "ti_cast",
2584                )
2585                .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast ti ptr: {e}")))?;
2586            self.builder
2587                .build_store(
2588                    ti_cast,
2589                    self.context
2590                        .i16_type()
2591                        .const_int(a.type_index as u64, false),
2592                )
2593                .map_err(|e| CodeGenError::LLVMError(format!("Failed to store ti: {e}")))?;
2594
2595            // status(u8) at +5
2596            let apl_ptr = unsafe {
2597                self.builder
2598                    .build_gep(
2599                        self.context.i8_type(),
2600                        arg_base,
2601                        &[self.context.i32_type().const_int(5, false)],
2602                        "status_ptr",
2603                    )
2604                    .map_err(|e| {
2605                        CodeGenError::LLVMError(format!("Failed to get status GEP: {e}"))
2606                    })?
2607            };
2608            self.builder
2609                .build_store(apl_ptr, self.context.i8_type().const_int(0, false))
2610                .map_err(|e| CodeGenError::LLVMError(format!("Failed to store status: {e}")))?;
2611
2612            // access_path_len(u8) at +4
2613            let apl_ptr2 = unsafe {
2614                self.builder
2615                    .build_gep(
2616                        self.context.i8_type(),
2617                        arg_base,
2618                        &[self.context.i32_type().const_int(4, false)],
2619                        "apl_ptr",
2620                    )
2621                    .map_err(|e| CodeGenError::LLVMError(format!("Failed to get apl GEP: {e}")))?
2622            };
2623            self.builder
2624                .build_store(
2625                    apl_ptr2,
2626                    self.context
2627                        .i8_type()
2628                        .const_int(a.access_path.len() as u64, false),
2629                )
2630                .map_err(|e| CodeGenError::LLVMError(format!("Failed to store apl: {e}")))?;
2631
2632            // access_path bytes at +6..+6+len
2633            for (i, b) in a.access_path.iter().enumerate() {
2634                let byte_ptr = unsafe {
2635                    self.builder
2636                        .build_gep(
2637                            self.context.i8_type(),
2638                            arg_base,
2639                            &[self.context.i32_type().const_int((6 + i) as u64, false)],
2640                            &format!("ap_byte_{i}"),
2641                        )
2642                        .map_err(|e| {
2643                            CodeGenError::LLVMError(format!("Failed to get ap byte GEP: {e}"))
2644                        })?
2645                };
2646                self.builder
2647                    .build_store(byte_ptr, self.context.i8_type().const_int(*b as u64, false))
2648                    .map_err(|e| {
2649                        CodeGenError::LLVMError(format!("Failed to store ap byte: {e}"))
2650                    })?;
2651            }
2652
2653            // data_len(u16) at +6+path_len (store reserved_len to keep layout consistent)
2654            let dl_ptr = unsafe {
2655                self.builder
2656                    .build_gep(
2657                        self.context.i8_type(),
2658                        arg_base,
2659                        &[self
2660                            .context
2661                            .i32_type()
2662                            .const_int((6 + a.access_path.len()) as u64, false)],
2663                        "dl_ptr",
2664                    )
2665                    .map_err(|e| CodeGenError::LLVMError(format!("Failed to get dl GEP: {e}")))?
2666            };
2667            let dl_cast = self
2668                .builder
2669                .build_pointer_cast(
2670                    dl_ptr,
2671                    self.context.ptr_type(AddressSpace::default()),
2672                    "dl_cast",
2673                )
2674                .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast dl ptr: {e}")))?;
2675            self.builder
2676                .build_store(
2677                    dl_cast,
2678                    self.context
2679                        .i16_type()
2680                        .const_int(reserved_len as u64, false),
2681                )
2682                .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_len: {e}")))?;
2683
2684            // variable data starts at +8+path_len
2685            let var_data_ptr = unsafe {
2686                self.builder
2687                    .build_gep(
2688                        self.context.i8_type(),
2689                        arg_base,
2690                        &[self
2691                            .context
2692                            .i32_type()
2693                            .const_int((8 + a.access_path.len()) as u64, false)],
2694                        "var_data_ptr",
2695                    )
2696                    .map_err(|e| {
2697                        CodeGenError::LLVMError(format!("Failed to get var_data GEP: {e}"))
2698                    })?
2699            };
2700
2701            // No dynamic cursor; we keep a compile-time offset and use reserved_len for layout
2702
2703            match &a.source {
2704                ComplexArgSource::ImmediateBytes { bytes, .. } => {
2705                    for (i, b) in bytes.iter().enumerate() {
2706                        let byte_ptr = unsafe {
2707                            self.builder
2708                                .build_gep(
2709                                    self.context.i8_type(),
2710                                    var_data_ptr,
2711                                    &[self.context.i32_type().const_int(i as u64, false)],
2712                                    &format!("var_byte_{i}"),
2713                                )
2714                                .map_err(|e| {
2715                                    CodeGenError::LLVMError(format!(
2716                                        "Failed to get var byte GEP: {e}"
2717                                    ))
2718                                })?
2719                        };
2720                        self.builder
2721                            .build_store(
2722                                byte_ptr,
2723                                self.context.i8_type().const_int(*b as u64, false),
2724                            )
2725                            .map_err(|e| {
2726                                CodeGenError::LLVMError(format!("Failed to store var byte: {e}"))
2727                            })?;
2728                    }
2729                    // data_len already set to reserved_len
2730                }
2731                ComplexArgSource::MemDump { src_addr, len } => {
2732                    // Directly probe-read into payload to avoid byte-wise copies
2733                    let ptr_ty = self.context.ptr_type(AddressSpace::default());
2734                    let i64_ty = self.context.i64_type();
2735                    let i32_ty = self.context.i32_type();
2736
2737                    // Helper: long bpf_probe_read_user(void *dst, u32 size, const void *src)
2738                    let dst_ptr = self
2739                        .builder
2740                        .build_pointer_cast(var_data_ptr, ptr_ty, "md_dst_ptr")
2741                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2742                    let base_src_ptr = self
2743                        .builder
2744                        .build_int_to_ptr(*src_addr, ptr_ty, "md_src_ptr")
2745                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2746                    let offsets_found = self.load_offsets_found_flag()?;
2747                    let not_found = self
2748                        .builder
2749                        .build_not(offsets_found, "md_offsets_miss")
2750                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2751                    let null_ptr = ptr_ty.const_null();
2752                    let src_ptr = self
2753                        .builder
2754                        .build_select::<BasicValueEnum<'ctx>, _>(
2755                            offsets_found,
2756                            base_src_ptr.into(),
2757                            null_ptr.into(),
2758                            "md_src_or_null",
2759                        )
2760                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
2761                        .into_pointer_value();
2762                    let len_const = i32_ty.const_int(*len as u64, false);
2763                    let zero_i32 = i32_ty.const_zero();
2764                    let effective_len = self
2765                        .builder
2766                        .build_select::<BasicValueEnum<'ctx>, _>(
2767                            offsets_found,
2768                            len_const.into(),
2769                            zero_i32.into(),
2770                            "md_len_or_zero",
2771                        )
2772                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
2773                        .into_int_value();
2774                    let ret = self
2775                        .create_bpf_helper_call(
2776                            aya_ebpf_bindings::bindings::bpf_func_id::BPF_FUNC_probe_read_user
2777                                as u64,
2778                            &[dst_ptr.into(), effective_len.into(), src_ptr.into()],
2779                            i64_ty.into(),
2780                            "probe_read_user_memdump",
2781                        )?
2782                        .into_int_value();
2783
2784                    // Branch on ret == 0 and offsets available
2785                    let ok_pred = self
2786                        .builder
2787                        .build_int_compare(
2788                            inkwell::IntPredicate::EQ,
2789                            ret,
2790                            i64_ty.const_zero(),
2791                            "md_ok",
2792                        )
2793                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2794                    let ok = self
2795                        .builder
2796                        .build_and(ok_pred, offsets_found, "md_ok_with_offsets")
2797                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2798                    let curr = self.builder.get_insert_block().unwrap();
2799                    let func = curr.get_parent().unwrap();
2800                    let ok_b = self.context.append_basic_block(func, "md_ok");
2801                    let err_b = self.context.append_basic_block(func, "md_err");
2802                    let cont_b = self.context.append_basic_block(func, "md_cont");
2803                    self.builder
2804                        .build_conditional_branch(ok, ok_b, err_b)
2805                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2806                    // ok: nothing extra to do
2807                    self.builder.position_at_end(ok_b);
2808                    self.builder
2809                        .build_unconditional_branch(cont_b)
2810                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2811                    // err: either offsets missing or helper failure
2812                    self.builder.position_at_end(err_b);
2813                    let offsets_err_b = self.context.append_basic_block(func, "md_offsets_err");
2814                    let helper_err_b = self.context.append_basic_block(func, "md_helper_err");
2815                    self.builder
2816                        .build_conditional_branch(not_found, offsets_err_b, helper_err_b)
2817                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2818                    self.builder.position_at_end(offsets_err_b);
2819                    self.builder
2820                        .build_store(
2821                            apl_ptr,
2822                            self.context
2823                                .i8_type()
2824                                .const_int(VariableStatus::OffsetsUnavailable as u64, false),
2825                        )
2826                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2827                    self.mark_any_fail()?;
2828                    self.builder
2829                        .build_unconditional_branch(cont_b)
2830                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2831                    self.builder.position_at_end(helper_err_b);
2832                    self.builder
2833                        .build_store(
2834                            apl_ptr,
2835                            self.context
2836                                .i8_type()
2837                                .const_int(VariableStatus::ReadError as u64, false),
2838                        )
2839                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2840                    // write errno + addr (12 bytes) to var_data_ptr; reserved sizing ensures this fits
2841                    let errno_ptr = self
2842                        .builder
2843                        .build_pointer_cast(
2844                            var_data_ptr,
2845                            self.context.ptr_type(AddressSpace::default()),
2846                            "errno_ptr",
2847                        )
2848                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2849                    let errno = self.build_errno_i32(ret, "errno_i32")?;
2850                    self.builder
2851                        .build_store(errno_ptr, errno)
2852                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2853                    let addr_ptr_i8 = unsafe {
2854                        self.builder
2855                            .build_gep(
2856                                self.context.i8_type(),
2857                                var_data_ptr,
2858                                &[self.context.i32_type().const_int(4, false)],
2859                                "addr_ptr_i8",
2860                            )
2861                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
2862                    };
2863                    let addr_ptr = self
2864                        .builder
2865                        .build_pointer_cast(
2866                            addr_ptr_i8,
2867                            self.context.ptr_type(AddressSpace::default()),
2868                            "addr_ptr",
2869                        )
2870                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2871                    self.builder
2872                        .build_store(addr_ptr, *src_addr)
2873                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2874                    self.mark_any_fail()?;
2875                    self.builder
2876                        .build_unconditional_branch(cont_b)
2877                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2878                    self.builder.position_at_end(cont_b);
2879                }
2880                ComplexArgSource::MemDumpDynamic {
2881                    src_addr,
2882                    len_value,
2883                    max_len: _,
2884                } => {
2885                    // Clamp runtime read to effective reserved length for this arg
2886                    let eff_max_len = effective_reserved[arg_index] as u32;
2887                    // Read up to rlen=min(len_value, max_len) into helper buffer, then copy bytes into payload
2888                    let i32_ty = self.context.i32_type();
2889                    let rlen_i32 = if len_value.get_type().get_bit_width() > 32 {
2890                        self.builder
2891                            .build_int_truncate(*len_value, i32_ty, "mdd_len_trunc")
2892                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
2893                    } else if len_value.get_type().get_bit_width() < 32 {
2894                        self.builder
2895                            .build_int_z_extend(*len_value, i32_ty, "mdd_len_zext")
2896                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
2897                    } else {
2898                        *len_value
2899                    };
2900                    // clamp negative to 0
2901                    let zero_i32 = i32_ty.const_zero();
2902                    let is_neg = self
2903                        .builder
2904                        .build_int_compare(
2905                            inkwell::IntPredicate::SLT,
2906                            rlen_i32,
2907                            zero_i32,
2908                            "mdd_len_neg",
2909                        )
2910                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2911                    let rlen_nn = self
2912                        .builder
2913                        .build_select(is_neg, zero_i32, rlen_i32, "mdd_len_nn")
2914                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
2915                        .into_int_value();
2916
2917                    // Bound length by the reserved space (already ensures >= 12B when possible)
2918                    let max_const = i32_ty.const_int(eff_max_len as u64, false);
2919                    let gt = self
2920                        .builder
2921                        .build_int_compare(inkwell::IntPredicate::UGT, rlen_nn, max_const, "mdd_gt")
2922                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2923                    let sel_len = self
2924                        .builder
2925                        .build_select(gt, max_const, rlen_nn, "mdd_rlen")
2926                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
2927                        .into_int_value();
2928
2929                    // If effective length is zero, mark status and skip read.
2930                    let curr = self.builder.get_insert_block().unwrap();
2931                    let func = curr.get_parent().unwrap();
2932                    let zero_b = self.context.append_basic_block(func, "mdd_len_zero");
2933                    let read_b = self.context.append_basic_block(func, "mdd_len_read");
2934                    let cont_b = self.context.append_basic_block(func, "mdd_cont");
2935                    let is_zero = self
2936                        .builder
2937                        .build_int_compare(
2938                            inkwell::IntPredicate::EQ,
2939                            sel_len,
2940                            i32_ty.const_zero(),
2941                            "mdd_len_zero",
2942                        )
2943                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2944                    self.builder
2945                        .build_conditional_branch(is_zero, zero_b, read_b)
2946                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2947
2948                    // Zero-length branch: set status=ZeroLength and continue.
2949                    self.builder.position_at_end(zero_b);
2950                    self.builder
2951                        .build_store(
2952                            apl_ptr,
2953                            self.context
2954                                .i8_type()
2955                                .const_int(VariableStatus::ZeroLength as u64, false),
2956                        )
2957                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2958                    self.builder
2959                        .build_unconditional_branch(cont_b)
2960                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2961
2962                    // Non-zero path: perform probe_read_user directly into var_data_ptr
2963                    self.builder.position_at_end(read_b);
2964                    let dst_ptr = self
2965                        .builder
2966                        .build_bit_cast(
2967                            var_data_ptr,
2968                            self.context.ptr_type(AddressSpace::default()),
2969                            "mdd_dst_ptr",
2970                        )
2971                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2972                    let ptr_ty = self.context.ptr_type(AddressSpace::default());
2973                    let base_src_ptr = self
2974                        .builder
2975                        .build_int_to_ptr(*src_addr, ptr_ty, "mdd_src_ptr")
2976                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2977                    let offsets_found = self.load_offsets_found_flag()?;
2978                    let not_found = self
2979                        .builder
2980                        .build_not(offsets_found, "mdd_dyn_offsets_miss")
2981                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2982                    let null_ptr = ptr_ty.const_null();
2983                    let src_ptr = self
2984                        .builder
2985                        .build_select::<BasicValueEnum<'ctx>, _>(
2986                            offsets_found,
2987                            base_src_ptr.into(),
2988                            null_ptr.into(),
2989                            "mdd_src_or_null",
2990                        )
2991                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
2992                        .into_pointer_value();
2993                    let zero_i32 = self.context.i32_type().const_zero();
2994                    let effective_len = self
2995                        .builder
2996                        .build_select::<BasicValueEnum<'ctx>, _>(
2997                            offsets_found,
2998                            sel_len.into(),
2999                            zero_i32.into(),
3000                            "mdd_len_or_zero",
3001                        )
3002                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
3003                        .into_int_value();
3004                    let ret = self
3005                        .create_bpf_helper_call(
3006                            BPF_FUNC_probe_read_user as u64,
3007                            &[dst_ptr, effective_len.into(), src_ptr.into()],
3008                            self.context.i64_type().into(),
3009                            "probe_read_user_dyn",
3010                        )?
3011                        .into_int_value();
3012                    let ok_pred = self
3013                        .builder
3014                        .build_int_compare(
3015                            inkwell::IntPredicate::EQ,
3016                            ret,
3017                            self.context.i64_type().const_zero(),
3018                            "mdd_ok",
3019                        )
3020                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3021                    let ok = self
3022                        .builder
3023                        .build_and(ok_pred, offsets_found, "mdd_ok_with_offsets")
3024                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3025                    let ok_b = self.context.append_basic_block(func, "mdd_ok");
3026                    let err_b = self.context.append_basic_block(func, "mdd_err");
3027                    self.builder
3028                        .build_conditional_branch(ok, ok_b, err_b)
3029                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3030                    // ok: data already in var_data_ptr
3031                    self.builder.position_at_end(ok_b);
3032                    self.builder
3033                        .build_unconditional_branch(cont_b)
3034                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3035                    // err: status+errno+addr (clamped by reserved sizing)
3036                    self.builder.position_at_end(err_b);
3037                    let offsets_err_b = self.context.append_basic_block(func, "mdd_offsets_err");
3038                    let helper_err_b = self.context.append_basic_block(func, "mdd_helper_err");
3039                    self.builder
3040                        .build_conditional_branch(not_found, offsets_err_b, helper_err_b)
3041                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3042                    self.builder.position_at_end(offsets_err_b);
3043                    self.builder
3044                        .build_store(
3045                            apl_ptr,
3046                            self.context
3047                                .i8_type()
3048                                .const_int(VariableStatus::OffsetsUnavailable as u64, false),
3049                        )
3050                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3051                    self.mark_any_fail()?;
3052                    self.builder
3053                        .build_unconditional_branch(cont_b)
3054                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3055                    self.builder.position_at_end(helper_err_b);
3056                    self.builder
3057                        .build_store(
3058                            apl_ptr,
3059                            self.context
3060                                .i8_type()
3061                                .const_int(VariableStatus::ReadError as u64, false),
3062                        )
3063                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3064                    if eff_max_len >= 4 {
3065                        let errno_ptr = self
3066                            .builder
3067                            .build_pointer_cast(
3068                                var_data_ptr,
3069                                self.context.ptr_type(AddressSpace::default()),
3070                                "mdd_errno_ptr",
3071                            )
3072                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3073                        let errno = self.build_errno_i32(ret, "mdd_errno_i32")?;
3074                        self.builder
3075                            .build_store(errno_ptr, errno)
3076                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3077                    }
3078                    if eff_max_len as usize >= DYNAMIC_READ_ERROR_PAYLOAD_LEN {
3079                        let addr_ptr_i8 = unsafe {
3080                            self.builder
3081                                .build_gep(
3082                                    self.context.i8_type(),
3083                                    var_data_ptr,
3084                                    &[self.context.i32_type().const_int(4, false)],
3085                                    "mdd_addr_ptr_i8",
3086                                )
3087                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
3088                        };
3089                        let addr_ptr = self
3090                            .builder
3091                            .build_pointer_cast(
3092                                addr_ptr_i8,
3093                                self.context.ptr_type(AddressSpace::default()),
3094                                "mdd_addr_ptr",
3095                            )
3096                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3097                        self.builder
3098                            .build_store(addr_ptr, *src_addr)
3099                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3100                    }
3101                    self.mark_any_fail()?;
3102                    self.builder
3103                        .build_unconditional_branch(cont_b)
3104                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3105                    self.builder.position_at_end(cont_b);
3106                }
3107                ComplexArgSource::ComputedInt { value, byte_len } => {
3108                    // Write computed integer into payload buffer based on requested byte_len
3109                    // Ensure the destination pointer element type matches the stored value type.
3110                    match *byte_len {
3111                        1 => {
3112                            let bitw = value.get_type().get_bit_width();
3113                            let v = if bitw == 1 {
3114                                // Bool: zero-extend to keep 0/1 in payload
3115                                self.builder
3116                                    .build_int_z_extend(
3117                                        *value,
3118                                        self.context.i8_type(),
3119                                        "expr_zext_bool_i8",
3120                                    )
3121                                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
3122                            } else if bitw < 8 {
3123                                self.builder
3124                                    .build_int_s_extend(
3125                                        *value,
3126                                        self.context.i8_type(),
3127                                        "expr_sext_i8",
3128                                    )
3129                                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
3130                            } else if bitw > 8 {
3131                                // wider than i8 -> truncate
3132                                self.builder
3133                                    .build_int_truncate(
3134                                        *value,
3135                                        self.context.i8_type(),
3136                                        "expr_trunc_i8",
3137                                    )
3138                                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
3139                            } else {
3140                                // exactly i8
3141                                *value
3142                            };
3143                            // var_data_ptr is i8* already; store directly
3144                            self.builder
3145                                .build_store(var_data_ptr, v)
3146                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3147                        }
3148                        2 => {
3149                            let bitw = value.get_type().get_bit_width();
3150                            let v = if bitw < 16 {
3151                                self.builder
3152                                    .build_int_s_extend(
3153                                        *value,
3154                                        self.context.i16_type(),
3155                                        "expr_sext_i16",
3156                                    )
3157                                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
3158                            } else if bitw > 16 {
3159                                self.builder
3160                                    .build_int_truncate(
3161                                        *value,
3162                                        self.context.i16_type(),
3163                                        "expr_trunc_i16",
3164                                    )
3165                                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
3166                            } else {
3167                                // equal width: i16
3168                                *value
3169                            };
3170                            let i16_ptr_ty = self.context.ptr_type(AddressSpace::default());
3171                            let cast_ptr = self
3172                                .builder
3173                                .build_pointer_cast(var_data_ptr, i16_ptr_ty, "expr_i16_ptr")
3174                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3175                            self.builder
3176                                .build_store(cast_ptr, v)
3177                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3178                        }
3179                        4 => {
3180                            let bitw = value.get_type().get_bit_width();
3181                            let v = if bitw < 32 {
3182                                self.builder
3183                                    .build_int_s_extend(
3184                                        *value,
3185                                        self.context.i32_type(),
3186                                        "expr_sext_i32",
3187                                    )
3188                                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
3189                            } else if bitw > 32 {
3190                                self.builder
3191                                    .build_int_truncate(
3192                                        *value,
3193                                        self.context.i32_type(),
3194                                        "expr_trunc_i32",
3195                                    )
3196                                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
3197                            } else {
3198                                // equal width: i32
3199                                *value
3200                            };
3201                            let i32_ptr_ty = self.context.ptr_type(AddressSpace::default());
3202                            let cast_ptr = self
3203                                .builder
3204                                .build_pointer_cast(var_data_ptr, i32_ptr_ty, "expr_i32_ptr")
3205                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3206                            self.builder
3207                                .build_store(cast_ptr, v)
3208                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3209                        }
3210                        8 => {
3211                            let v64 = if value.get_type().get_bit_width() < 64 {
3212                                self.builder
3213                                    .build_int_s_extend(
3214                                        *value,
3215                                        self.context.i64_type(),
3216                                        "expr_sext",
3217                                    )
3218                                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
3219                            } else {
3220                                *value
3221                            };
3222                            let i64_ptr_ty = self.context.ptr_type(AddressSpace::default());
3223                            let cast_ptr = self
3224                                .builder
3225                                .build_pointer_cast(var_data_ptr, i64_ptr_ty, "expr_i64_ptr")
3226                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3227                            self.builder
3228                                .build_store(cast_ptr, v64)
3229                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3230                        }
3231                        n => {
3232                            // Fallback: write the lowest n bytes little-endian
3233                            // Truncate/extend to 64-bit, then emit byte stores
3234                            let v64 = if value.get_type().get_bit_width() < 64 {
3235                                self.builder
3236                                    .build_int_z_extend(
3237                                        *value,
3238                                        self.context.i64_type(),
3239                                        "expr_zext_fallback",
3240                                    )
3241                                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
3242                            } else {
3243                                *value
3244                            };
3245                            for i in 0..n {
3246                                // Extract byte i
3247                                let shift =
3248                                    self.context.i64_type().const_int((i * 8) as u64, false);
3249                                let shifted = self
3250                                    .builder
3251                                    .build_right_shift(v64, shift, false, &format!("expr_shr_{i}"))
3252                                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3253                                let byte = self
3254                                    .builder
3255                                    .build_int_truncate(
3256                                        shifted,
3257                                        self.context.i8_type(),
3258                                        &format!("expr_byte_{i}"),
3259                                    )
3260                                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3261                                let byte_ptr = unsafe {
3262                                    self.builder
3263                                        .build_gep(
3264                                            self.context.i8_type(),
3265                                            var_data_ptr,
3266                                            &[self.context.i32_type().const_int(i as u64, false)],
3267                                            &format!("expr_byte_ptr_{i}"),
3268                                        )
3269                                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
3270                                };
3271                                self.builder
3272                                    .build_store(byte_ptr, byte)
3273                                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3274                            }
3275                        }
3276                    }
3277                }
3278                ComplexArgSource::RuntimeRead {
3279                    eval_result,
3280                    dwarf_type,
3281                    module_for_offsets,
3282                } => {
3283                    // Read from user memory at runtime via BPF helper
3284                    let ptr_type = self.context.ptr_type(AddressSpace::default());
3285                    let i32_type = self.context.i32_type();
3286                    let i64_type = self.context.i64_type();
3287                    let dst_ptr = self
3288                        .builder
3289                        .build_bit_cast(var_data_ptr, ptr_type, "dst_ptr")
3290                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3291                    let size_val = i32_type.const_int(a.data_len as u64, false);
3292                    // Compute source address; if link-time address, apply ASLR offsets via map
3293                    let src_addr = self.evaluation_result_to_address_with_hint(
3294                        eval_result,
3295                        Some(apl_ptr),
3296                        module_for_offsets.as_deref(),
3297                    )?;
3298                    let offsets_found = self.load_offsets_found_flag()?;
3299                    let current_block = self.builder.get_insert_block().unwrap();
3300                    let current_fn = current_block.get_parent().unwrap();
3301                    let cont2_block = self.context.append_basic_block(current_fn, "after_read");
3302                    let skip_block = self.context.append_basic_block(current_fn, "offsets_skip");
3303                    let found_block = self.context.append_basic_block(current_fn, "offsets_found");
3304                    self.builder
3305                        .build_conditional_branch(offsets_found, found_block, skip_block)
3306                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3307
3308                    // Offsets missing: record failure and continue without helper access.
3309                    self.builder.position_at_end(skip_block);
3310                    self.mark_any_fail()?;
3311                    self.builder
3312                        .build_unconditional_branch(cont2_block)
3313                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3314
3315                    // Offsets found: proceed with null check and helper call.
3316                    self.builder.position_at_end(found_block);
3317                    let src_ptr = self
3318                        .builder
3319                        .build_int_to_ptr(src_addr, ptr_type, "src_ptr")
3320                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3321
3322                    // status_ptr was stored in apl_ptr earlier (we named it status_ptr)
3323                    // Build NULL check
3324                    let zero64 = i64_type.const_zero();
3325                    let is_null = self
3326                        .builder
3327                        .build_int_compare(inkwell::IntPredicate::EQ, src_addr, zero64, "is_null")
3328                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3329                    let null_block = self.context.append_basic_block(current_fn, "null_deref");
3330                    let read_block = self.context.append_basic_block(current_fn, "read_user");
3331                    self.builder
3332                        .build_conditional_branch(is_null, null_block, read_block)
3333                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3334
3335                    // NULL path: status=1, keep reserved_len in header, no data write (buffer pre-zeroed)
3336                    self.builder.position_at_end(null_block);
3337                    self.builder
3338                        .build_store(
3339                            apl_ptr,
3340                            self.context
3341                                .i8_type()
3342                                .const_int(VariableStatus::NullDeref as u64, false),
3343                        )
3344                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3345                    self.mark_any_fail()?;
3346                    self.builder
3347                        .build_unconditional_branch(cont2_block)
3348                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3349
3350                    // Read path
3351                    self.builder.position_at_end(read_block);
3352                    let ret = self
3353                        .create_bpf_helper_call(
3354                            BPF_FUNC_probe_read_user as u64,
3355                            &[dst_ptr, size_val.into(), src_ptr.into()],
3356                            i32_type.into(),
3357                            "probe_read_user",
3358                        )?
3359                        .into_int_value();
3360                    let is_err = self
3361                        .builder
3362                        .build_int_compare(
3363                            inkwell::IntPredicate::SLT,
3364                            ret,
3365                            i32_type.const_zero(),
3366                            "ret_lt_zero",
3367                        )
3368                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3369                    let err_block = self.context.append_basic_block(current_fn, "read_err");
3370                    let ok_block = self.context.append_basic_block(current_fn, "read_ok");
3371                    self.builder
3372                        .build_conditional_branch(is_err, err_block, ok_block)
3373                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3374
3375                    // Error branch: status=2 (read_user failed); write errno+addr payload at start; header keeps reserved_len
3376                    self.builder.position_at_end(err_block);
3377                    self.builder
3378                        .build_store(
3379                            apl_ptr,
3380                            self.context
3381                                .i8_type()
3382                                .const_int(VariableStatus::ReadError as u64, false),
3383                        )
3384                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3385                    // write errno at [0..4]
3386                    let i32_ptr = self
3387                        .builder
3388                        .build_pointer_cast(
3389                            var_data_ptr,
3390                            self.context.ptr_type(AddressSpace::default()),
3391                            "errno_ptr",
3392                        )
3393                        .map_err(|e| {
3394                            CodeGenError::LLVMError(format!("Failed to cast errno ptr: {e}"))
3395                        })?;
3396                    self.builder.build_store(i32_ptr, ret).map_err(|e| {
3397                        CodeGenError::LLVMError(format!("Failed to store errno: {e}"))
3398                    })?;
3399                    // write addr at [4..12]
3400                    let addr_ptr_i8 = unsafe {
3401                        self.builder
3402                            .build_gep(
3403                                self.context.i8_type(),
3404                                var_data_ptr,
3405                                &[i32_type.const_int(4, false)],
3406                                "addr_ptr_i8",
3407                            )
3408                            .map_err(|e| {
3409                                CodeGenError::LLVMError(format!("Failed to get addr gep: {e}"))
3410                            })?
3411                    };
3412                    let addr_ptr = self
3413                        .builder
3414                        .build_pointer_cast(
3415                            addr_ptr_i8,
3416                            self.context.ptr_type(AddressSpace::default()),
3417                            "addr_ptr",
3418                        )
3419                        .map_err(|e| {
3420                            CodeGenError::LLVMError(format!("Failed to cast addr ptr: {e}"))
3421                        })?;
3422                    let src_as_i64 = src_addr;
3423                    self.builder
3424                        .build_store(addr_ptr, src_as_i64)
3425                        .map_err(|e| {
3426                            CodeGenError::LLVMError(format!("Failed to store addr: {e}"))
3427                        })?;
3428                    self.mark_any_fail()?;
3429                    self.builder
3430                        .build_unconditional_branch(cont2_block)
3431                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3432
3433                    // OK branch: success or truncated (header keeps reserved_len)
3434                    self.builder.position_at_end(ok_block);
3435                    if a.data_len < dwarf_type.size() as usize {
3436                        self.builder
3437                            .build_store(
3438                                apl_ptr,
3439                                self.context
3440                                    .i8_type()
3441                                    .const_int(VariableStatus::Truncated as u64, false),
3442                            )
3443                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3444                        self.mark_any_success()?;
3445                        self.mark_any_fail()?;
3446                    } else {
3447                        self.mark_any_success()?;
3448                    }
3449                    self.builder
3450                        .build_unconditional_branch(cont2_block)
3451                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3452
3453                    self.builder.position_at_end(cont2_block);
3454                }
3455                ComplexArgSource::AddressValue {
3456                    eval_result,
3457                    module_for_offsets,
3458                } => {
3459                    // Compute address (apply ASLR if link-time address) and store as 8 bytes
3460                    let addr = self.evaluation_result_to_address_with_hint(
3461                        eval_result,
3462                        Some(apl_ptr),
3463                        module_for_offsets.as_deref(),
3464                    )?;
3465                    let cast_ptr = self
3466                        .builder
3467                        .build_pointer_cast(
3468                            var_data_ptr,
3469                            self.context.ptr_type(AddressSpace::default()),
3470                            "addr_store_ptr",
3471                        )
3472                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3473                    self.builder
3474                        .build_store(cast_ptr, addr)
3475                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3476                    // header already set to reserved_len (8)
3477                }
3478            }
3479            // Advance compile-time offset by header_len + reserved_len
3480            offset += 2 + 2 + 1 + 1 + a.access_path.len() + 2 + reserved_len;
3481        }
3482
3483        // Already accumulated; EndInstruction will send the whole event
3484        Ok(())
3485    }
3486
3487    /// Generate eBPF code for PrintStringIndex instruction
3488    pub fn generate_print_string_index(&mut self, string_index: u16) -> Result<()> {
3489        info!(
3490            "Generating PrintStringIndex instruction: index={}",
3491            string_index
3492        );
3493
3494        // Allocate instruction structure on eBPF stack
3495        // Reserve space in accumulation buffer for this instruction
3496        let inst_buffer = self.reserve_instruction_region(
3497            (std::mem::size_of::<InstructionHeader>() + std::mem::size_of::<PrintStringIndexData>())
3498                as u64,
3499        );
3500
3501        // Clear memory with static size
3502        let _inst_size = self.context.i64_type().const_int(
3503            (std::mem::size_of::<PrintStringIndexData>()
3504                + std::mem::size_of::<ghostscope_protocol::trace_event::InstructionHeader>())
3505                as u64,
3506            false,
3507        );
3508        // Avoid memset on eBPF; global buffer is zero-initialized and we write fields explicitly.
3509
3510        // Fill instruction header using byte offsets
3511        // inst_type at offset 0 (first field of InstructionHeader)
3512        let inst_type_ptr = unsafe {
3513            self.builder
3514                .build_gep(
3515                    self.context.i8_type(),
3516                    inst_buffer,
3517                    &[self.context.i32_type().const_int(
3518                        std::mem::offset_of!(InstructionHeader, inst_type) as u64,
3519                        false,
3520                    )],
3521                    "inst_type_ptr",
3522                )
3523                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get inst_type GEP: {e}")))?
3524        };
3525        let inst_type_val = self
3526            .context
3527            .i8_type()
3528            .const_int(InstructionType::PrintStringIndex as u64, false);
3529        self.builder
3530            .build_store(inst_type_ptr, inst_type_val)
3531            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store inst_type: {e}")))?;
3532
3533        let data_length_ptr = unsafe {
3534            self.builder
3535                .build_gep(
3536                    self.context.i8_type(),
3537                    inst_buffer,
3538                    &[self.context.i32_type().const_int(
3539                        std::mem::offset_of!(InstructionHeader, data_length) as u64,
3540                        false,
3541                    )],
3542                    "data_length_ptr",
3543                )
3544                .map_err(|e| {
3545                    CodeGenError::LLVMError(format!("Failed to get data_length GEP: {e}"))
3546                })?
3547        };
3548        let data_length_i16_ptr = self
3549            .builder
3550            .build_pointer_cast(
3551                data_length_ptr,
3552                self.context.ptr_type(AddressSpace::default()),
3553                "data_length_i16_ptr",
3554            )
3555            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast data_length ptr: {e}")))?;
3556        let data_length_val = self
3557            .context
3558            .i16_type()
3559            .const_int(std::mem::size_of::<PrintStringIndexData>() as u64, false);
3560        self.builder
3561            .build_store(data_length_i16_ptr, data_length_val)
3562            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_length: {e}")))?;
3563
3564        // Fill string index data (after InstructionHeader)
3565        let string_index_ptr = unsafe {
3566            self.builder
3567                .build_gep(
3568                    self.context.i8_type(),
3569                    inst_buffer,
3570                    &[self
3571                        .context
3572                        .i32_type()
3573                        .const_int(std::mem::size_of::<InstructionHeader>() as u64, false)],
3574                    "string_index_ptr",
3575                )
3576                .map_err(|e| {
3577                    CodeGenError::LLVMError(format!("Failed to get string_index GEP: {e}"))
3578                })?
3579        };
3580        let string_index_i16_ptr = self
3581            .builder
3582            .build_pointer_cast(
3583                string_index_ptr,
3584                self.context.ptr_type(AddressSpace::default()),
3585                "string_index_i16_ptr",
3586            )
3587            .map_err(|e| {
3588                CodeGenError::LLVMError(format!("Failed to cast string_index ptr: {e}"))
3589            })?;
3590        let string_index_val = self
3591            .context
3592            .i16_type()
3593            .const_int(string_index as u64, false);
3594        self.builder
3595            .build_store(string_index_i16_ptr, string_index_val)
3596            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store string_index: {e}")))?;
3597
3598        // Already accumulated; EndInstruction will send the whole event
3599        Ok(())
3600    }
3601
3602    /// Generate ExprError instruction with expression string index and error code/flags
3603    pub fn generate_expr_error(
3604        &mut self,
3605        expr_string_index: u16,
3606        error_code_iv: inkwell::values::IntValue<'ctx>,
3607        flags_iv: inkwell::values::IntValue<'ctx>,
3608        failing_addr_iv: inkwell::values::IntValue<'ctx>,
3609    ) -> Result<()> {
3610        // Reserve space in accumulation buffer for this instruction
3611        let inst_buffer = self.reserve_instruction_region(
3612            (std::mem::size_of::<InstructionHeader>()
3613                + std::mem::size_of::<ghostscope_protocol::trace_event::ExprErrorData>())
3614                as u64,
3615        );
3616
3617        // Store instruction type at offset 0
3618        let inst_type_val = self
3619            .context
3620            .i8_type()
3621            .const_int(InstructionType::ExprError as u64, false);
3622        self.builder
3623            .build_store(inst_buffer, inst_type_val)
3624            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store inst_type: {e}")))?;
3625
3626        // data_length
3627        let data_length_ptr = unsafe {
3628            self.builder
3629                .build_gep(
3630                    self.context.i8_type(),
3631                    inst_buffer,
3632                    &[self.context.i32_type().const_int(
3633                        std::mem::offset_of!(InstructionHeader, data_length) as u64,
3634                        false,
3635                    )],
3636                    "exprerr_data_length_ptr",
3637                )
3638                .map_err(|e| {
3639                    CodeGenError::LLVMError(format!("Failed to get data_length GEP: {e}"))
3640                })?
3641        };
3642        let data_length_i16_ptr = self
3643            .builder
3644            .build_pointer_cast(
3645                data_length_ptr,
3646                self.context.ptr_type(AddressSpace::default()),
3647                "exprerr_data_length_i16_ptr",
3648            )
3649            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast data_length ptr: {e}")))?;
3650        let data_length_val = self.context.i16_type().const_int(
3651            std::mem::size_of::<ghostscope_protocol::trace_event::ExprErrorData>() as u64,
3652            false,
3653        );
3654        self.builder
3655            .build_store(data_length_i16_ptr, data_length_val)
3656            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_length: {e}")))?;
3657
3658        // Payload fields after header
3659        // string_index at offset sizeof(InstructionHeader) + 0 (u16)
3660        let si_ptr = unsafe {
3661            self.builder
3662                .build_gep(
3663                    self.context.i8_type(),
3664                    inst_buffer,
3665                    &[self
3666                        .context
3667                        .i32_type()
3668                        .const_int(std::mem::size_of::<InstructionHeader>() as u64, false)],
3669                    "exprerr_si_ptr",
3670                )
3671                .map_err(|e| {
3672                    CodeGenError::LLVMError(format!("Failed to get string_index GEP: {e}"))
3673                })?
3674        };
3675        let si_i16_ptr = self
3676            .builder
3677            .build_pointer_cast(
3678                si_ptr,
3679                self.context.ptr_type(AddressSpace::default()),
3680                "exprerr_si_i16_ptr",
3681            )
3682            .map_err(|e| {
3683                CodeGenError::LLVMError(format!("Failed to cast string_index ptr: {e}"))
3684            })?;
3685        let si_val = self
3686            .context
3687            .i16_type()
3688            .const_int(expr_string_index as u64, false);
3689        self.builder
3690            .build_store(si_i16_ptr, si_val)
3691            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store string_index: {e}")))?;
3692
3693        // error_code at +2, flags at +3
3694        let ec_ptr = unsafe {
3695            self.builder
3696                .build_gep(
3697                    self.context.i8_type(),
3698                    inst_buffer,
3699                    &[self
3700                        .context
3701                        .i32_type()
3702                        .const_int((std::mem::size_of::<InstructionHeader>() + 2) as u64, false)],
3703                    "exprerr_ec_ptr",
3704                )
3705                .map_err(|e| {
3706                    CodeGenError::LLVMError(format!("Failed to get error_code GEP: {e}"))
3707                })?
3708        };
3709        // Truncate/extend runtime error code to i8
3710        let ec_i8 = if error_code_iv.get_type().get_bit_width() == 8 {
3711            error_code_iv
3712        } else if error_code_iv.get_type().get_bit_width() > 8 {
3713            self.builder
3714                .build_int_truncate(error_code_iv, self.context.i8_type(), "ec_trunc")
3715                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
3716        } else {
3717            self.builder
3718                .build_int_z_extend(error_code_iv, self.context.i8_type(), "ec_zext")
3719                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
3720        };
3721        self.builder
3722            .build_store(ec_ptr, ec_i8)
3723            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store error_code: {e}")))?;
3724        let fl_ptr = unsafe {
3725            self.builder
3726                .build_gep(
3727                    self.context.i8_type(),
3728                    inst_buffer,
3729                    &[self
3730                        .context
3731                        .i32_type()
3732                        .const_int((std::mem::size_of::<InstructionHeader>() + 3) as u64, false)],
3733                    "exprerr_flags_ptr",
3734                )
3735                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get flags GEP: {e}")))?
3736        };
3737        // Truncate/extend runtime flags to i8
3738        let fl_i8 = if flags_iv.get_type().get_bit_width() == 8 {
3739            flags_iv
3740        } else if flags_iv.get_type().get_bit_width() > 8 {
3741            self.builder
3742                .build_int_truncate(flags_iv, self.context.i8_type(), "fl_trunc")
3743                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
3744        } else {
3745            self.builder
3746                .build_int_z_extend(flags_iv, self.context.i8_type(), "fl_zext")
3747                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
3748        };
3749        self.builder
3750            .build_store(fl_ptr, fl_i8)
3751            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store flags: {e}")))?;
3752
3753        // failing_addr at +4 (u64)
3754        let addr_ptr = unsafe {
3755            self.builder
3756                .build_gep(
3757                    self.context.i8_type(),
3758                    inst_buffer,
3759                    &[self
3760                        .context
3761                        .i32_type()
3762                        .const_int((std::mem::size_of::<InstructionHeader>() + 4) as u64, false)],
3763                    "exprerr_addr_ptr",
3764                )
3765                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get addr GEP: {e}")))?
3766        };
3767        let addr_i64 = if failing_addr_iv.get_type().get_bit_width() == 64 {
3768            failing_addr_iv
3769        } else if failing_addr_iv.get_type().get_bit_width() > 64 {
3770            self.builder
3771                .build_int_truncate(failing_addr_iv, self.context.i64_type(), "addr_trunc")
3772                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
3773        } else {
3774            self.builder
3775                .build_int_z_extend(failing_addr_iv, self.context.i64_type(), "addr_zext")
3776                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
3777        };
3778        let addr_ptr_cast = self
3779            .builder
3780            .build_pointer_cast(
3781                addr_ptr,
3782                self.context.ptr_type(AddressSpace::default()),
3783                "exprerr_addr_i64_ptr",
3784            )
3785            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
3786        self.builder
3787            .build_store(addr_ptr_cast, addr_i64)
3788            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store failing_addr: {e}")))?;
3789
3790        // Already accumulated; EndInstruction will send the whole event
3791        Ok(())
3792    }
3793
3794    /// Generate eBPF code for PrintVariableIndex instruction
3795    pub fn generate_print_variable_index(
3796        &mut self,
3797        var_name_index: u16,
3798        type_encoding: TypeKind,
3799        var_name: &str,
3800    ) -> Result<()> {
3801        info!(
3802            "Generating PrintVariableIndex instruction: var_name_index={}, type={:?}, var_name={}",
3803            var_name_index, type_encoding, var_name
3804        );
3805
3806        // Resolve type_index from DWARF if available; otherwise synthesize from TypeKind
3807        let type_index = match self.query_dwarf_for_variable(var_name)? {
3808            Some(var) => match var.dwarf_type {
3809                Some(ref t) => self.trace_context.add_type(t.clone()),
3810                None => self.add_synthesized_type_index_for_kind(type_encoding),
3811            },
3812            None => {
3813                // Variable not found via DWARF; fall back to synthesized type info based on TypeKind
3814                self.add_synthesized_type_index_for_kind(type_encoding)
3815            }
3816        };
3817
3818        self.generate_successful_variable_instruction(
3819            var_name_index,
3820            type_encoding,
3821            type_index,
3822            var_name,
3823        )
3824    }
3825
3826    /// Generate successful variable instruction with data
3827    fn generate_successful_variable_instruction(
3828        &mut self,
3829        var_name_index: u16,
3830        type_encoding: TypeKind,
3831        type_index: u16,
3832        var_name: &str,
3833    ) -> Result<()> {
3834        // Determine data size based on type
3835        let data_size = match type_encoding {
3836            TypeKind::U8 | TypeKind::I8 | TypeKind::Bool | TypeKind::Char => 1,
3837            TypeKind::U16 | TypeKind::I16 => 2,
3838            TypeKind::U32 | TypeKind::I32 | TypeKind::F32 => 4,
3839            TypeKind::U64 | TypeKind::I64 | TypeKind::F64 | TypeKind::Pointer => 8,
3840            _ => 8, // Default to 8 bytes for complex types
3841        };
3842
3843        // Reserve space directly in per-CPU accumulation buffer
3844        let inst_buffer = self.reserve_instruction_region(
3845            (std::mem::size_of::<InstructionHeader>()
3846                + std::mem::size_of::<PrintVariableIndexData>()
3847                + data_size as usize) as u64,
3848        );
3849
3850        // Avoid memset; global buffer is zero-initialized
3851
3852        // Store instruction type at offset 0
3853        let inst_type_val = self
3854            .context
3855            .i8_type()
3856            .const_int(InstructionType::PrintVariableIndex as u64, false);
3857        self.builder
3858            .build_store(inst_buffer, inst_type_val)
3859            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store inst_type: {e}")))?;
3860
3861        // Store data_length field of InstructionHeader
3862        let data_length_ptr = unsafe {
3863            self.builder
3864                .build_gep(
3865                    self.context.i8_type(),
3866                    inst_buffer,
3867                    &[self.context.i32_type().const_int(
3868                        std::mem::offset_of!(InstructionHeader, data_length) as u64,
3869                        false,
3870                    )],
3871                    "data_length_ptr",
3872                )
3873                .map_err(|e| {
3874                    CodeGenError::LLVMError(format!("Failed to get data_length GEP: {e}"))
3875                })?
3876        };
3877        let data_length_i16_ptr = self
3878            .builder
3879            .build_pointer_cast(
3880                data_length_ptr,
3881                self.context.ptr_type(AddressSpace::default()),
3882                "data_length_i16_ptr",
3883            )
3884            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast data_length ptr: {e}")))?;
3885        let total_data_length = std::mem::size_of::<PrintVariableIndexData>() + data_size as usize;
3886        let data_length_val = self
3887            .context
3888            .i16_type()
3889            .const_int(total_data_length as u64, false);
3890        self.builder
3891            .build_store(data_length_i16_ptr, data_length_val)
3892            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_length: {e}")))?;
3893
3894        // Write PrintVariableIndexData after InstructionHeader
3895        let variable_data_start = unsafe {
3896            self.builder
3897                .build_gep(
3898                    self.context.i8_type(),
3899                    inst_buffer,
3900                    &[self
3901                        .context
3902                        .i32_type()
3903                        .const_int(std::mem::size_of::<InstructionHeader>() as u64, false)],
3904                    "variable_data_start",
3905                )
3906                .map_err(|e| {
3907                    CodeGenError::LLVMError(format!("Failed to get variable_data_start GEP: {e}"))
3908                })?
3909        };
3910
3911        // Store var_name_index using correct offset
3912        let var_name_index_ptr = unsafe {
3913            self.builder
3914                .build_gep(
3915                    self.context.i8_type(),
3916                    variable_data_start,
3917                    &[self.context.i32_type().const_int(
3918                        std::mem::offset_of!(PrintVariableIndexData, var_name_index) as u64,
3919                        false,
3920                    )],
3921                    "var_name_index_ptr",
3922                )
3923                .map_err(|e| {
3924                    CodeGenError::LLVMError(format!("Failed to get var_name_index GEP: {e}"))
3925                })?
3926        };
3927        let var_name_index_i16_ptr = self
3928            .builder
3929            .build_pointer_cast(
3930                var_name_index_ptr,
3931                self.context.ptr_type(AddressSpace::default()),
3932                "var_name_index_i16_ptr",
3933            )
3934            .map_err(|e| {
3935                CodeGenError::LLVMError(format!("Failed to cast var_name_index ptr: {e}"))
3936            })?;
3937        let var_name_index_val = self
3938            .context
3939            .i16_type()
3940            .const_int(var_name_index as u64, false);
3941        self.builder
3942            .build_store(var_name_index_i16_ptr, var_name_index_val)
3943            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store var_name_index: {e}")))?;
3944
3945        // Store type_encoding using correct offset
3946        let type_encoding_ptr = unsafe {
3947            self.builder
3948                .build_gep(
3949                    self.context.i8_type(),
3950                    variable_data_start,
3951                    &[self.context.i32_type().const_int(
3952                        std::mem::offset_of!(PrintVariableIndexData, type_encoding) as u64,
3953                        false,
3954                    )],
3955                    "type_encoding_ptr",
3956                )
3957                .map_err(|e| {
3958                    CodeGenError::LLVMError(format!("Failed to get type_encoding GEP: {e}"))
3959                })?
3960        };
3961        let type_encoding_val = self
3962            .context
3963            .i8_type()
3964            .const_int(type_encoding as u8 as u64, false);
3965        self.builder
3966            .build_store(type_encoding_ptr, type_encoding_val)
3967            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store type_encoding: {e}")))?;
3968
3969        // Store data_len using correct offset
3970        let data_len_ptr = unsafe {
3971            self.builder
3972                .build_gep(
3973                    self.context.i8_type(),
3974                    variable_data_start,
3975                    &[self.context.i32_type().const_int(
3976                        std::mem::offset_of!(PrintVariableIndexData, data_len) as u64,
3977                        false,
3978                    )],
3979                    "data_len_ptr",
3980                )
3981                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get data_len GEP: {e}")))?
3982        };
3983        let data_len_i16_ptr = self
3984            .builder
3985            .build_pointer_cast(
3986                data_len_ptr,
3987                self.context.ptr_type(AddressSpace::default()),
3988                "data_len_i16_ptr",
3989            )
3990            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast data_len ptr: {e}")))?;
3991        let data_len_val = self.context.i16_type().const_int(data_size as u64, false); // Store as u16
3992        self.builder
3993            .build_store(data_len_i16_ptr, data_len_val)
3994            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_len: {e}")))?;
3995
3996        // Store type_index using correct offset
3997        let type_index_ptr = unsafe {
3998            self.builder
3999                .build_gep(
4000                    self.context.i8_type(),
4001                    variable_data_start,
4002                    &[self.context.i32_type().const_int(
4003                        std::mem::offset_of!(PrintVariableIndexData, type_index) as u64,
4004                        false,
4005                    )],
4006                    "type_index_ptr",
4007                )
4008                .map_err(|e| {
4009                    CodeGenError::LLVMError(format!("Failed to get type_index GEP: {e}"))
4010                })?
4011        };
4012        let type_index_i16_ptr = self
4013            .builder
4014            .build_pointer_cast(
4015                type_index_ptr,
4016                self.context.ptr_type(AddressSpace::default()),
4017                "type_index_i16_ptr",
4018            )
4019            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast type_index ptr: {e}")))?;
4020        let type_index_val = self.context.i16_type().const_int(type_index as u64, false);
4021        self.builder
4022            .build_store(type_index_i16_ptr, type_index_val)
4023            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store type_index: {e}")))?;
4024
4025        // Store status (set to 0)
4026        let status_ptr = unsafe {
4027            self.builder
4028                .build_gep(
4029                    self.context.i8_type(),
4030                    variable_data_start,
4031                    &[self.context.i32_type().const_int(
4032                        std::mem::offset_of!(PrintVariableIndexData, status) as u64,
4033                        false,
4034                    )],
4035                    "status_ptr",
4036                )
4037                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get status GEP: {e}")))?
4038        };
4039        let status_val = self
4040            .context
4041            .i8_type()
4042            .const_int(VariableStatus::Ok as u64, false);
4043        self.builder
4044            .build_store(status_ptr, status_val)
4045            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store status: {e}")))?;
4046
4047        let var_data = self.resolve_variable_value(var_name, type_encoding, Some(status_ptr))?;
4048
4049        // Store actual variable data after PrintVariableIndexData structure
4050        let var_data_ptr = unsafe {
4051            self.builder
4052                .build_gep(
4053                    self.context.i8_type(),
4054                    variable_data_start,
4055                    &[self
4056                        .context
4057                        .i32_type()
4058                        .const_int(std::mem::size_of::<PrintVariableIndexData>() as u64, false)],
4059                    "var_data_ptr",
4060                )
4061                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get var_data GEP: {e}")))?
4062        };
4063
4064        // Store the runtime variable value based on data size
4065        // The var_data contains the LLVM IR value (from register/memory access)
4066        match data_size {
4067            1 => {
4068                // Store as i8
4069                let truncated = match var_data {
4070                    BasicValueEnum::IntValue(int_val) => self
4071                        .builder
4072                        .build_int_truncate(int_val, self.context.i8_type(), "truncated_i8")
4073                        .map_err(|e| {
4074                            CodeGenError::LLVMError(format!("Failed to truncate to i8: {e}"))
4075                        })?,
4076                    _ => {
4077                        return Err(CodeGenError::LLVMError(
4078                            "Expected integer value for integer type".to_string(),
4079                        ));
4080                    }
4081                };
4082                self.builder
4083                    .build_store(var_data_ptr, truncated)
4084                    .map_err(|e| {
4085                        CodeGenError::LLVMError(format!("Failed to store i8 data: {e}"))
4086                    })?;
4087            }
4088            2 => {
4089                // Store as i16
4090                let truncated = match var_data {
4091                    BasicValueEnum::IntValue(int_val) => self
4092                        .builder
4093                        .build_int_truncate(int_val, self.context.i16_type(), "truncated_i16")
4094                        .map_err(|e| {
4095                            CodeGenError::LLVMError(format!("Failed to truncate to i16: {e}"))
4096                        })?,
4097                    _ => {
4098                        return Err(CodeGenError::LLVMError(
4099                            "Expected integer value for integer type".to_string(),
4100                        ));
4101                    }
4102                };
4103                let i16_ptr = self
4104                    .builder
4105                    .build_pointer_cast(
4106                        var_data_ptr,
4107                        self.context.ptr_type(AddressSpace::default()),
4108                        "i16_ptr",
4109                    )
4110                    .map_err(|e| {
4111                        CodeGenError::LLVMError(format!("Failed to cast to i16 ptr: {e}"))
4112                    })?;
4113                self.builder.build_store(i16_ptr, truncated).map_err(|e| {
4114                    CodeGenError::LLVMError(format!("Failed to store i16 data: {e}"))
4115                })?;
4116            }
4117            4 => {
4118                // Store as i32 or f32
4119                match var_data {
4120                    BasicValueEnum::IntValue(int_val) => {
4121                        let truncated = self
4122                            .builder
4123                            .build_int_truncate(int_val, self.context.i32_type(), "truncated_i32")
4124                            .map_err(|e| {
4125                                CodeGenError::LLVMError(format!("Failed to truncate to i32: {e}"))
4126                            })?;
4127                        let i32_ptr = self
4128                            .builder
4129                            .build_pointer_cast(
4130                                var_data_ptr,
4131                                self.context.ptr_type(AddressSpace::default()),
4132                                "i32_ptr",
4133                            )
4134                            .map_err(|e| {
4135                                CodeGenError::LLVMError(format!("Failed to cast to i32 ptr: {e}"))
4136                            })?;
4137                        self.builder.build_store(i32_ptr, truncated).map_err(|e| {
4138                            CodeGenError::LLVMError(format!("Failed to store i32 data: {e}"))
4139                        })?;
4140                    }
4141                    BasicValueEnum::FloatValue(float_val) => {
4142                        let f32_ptr = self
4143                            .builder
4144                            .build_pointer_cast(
4145                                var_data_ptr,
4146                                self.context.ptr_type(AddressSpace::default()),
4147                                "f32_ptr",
4148                            )
4149                            .map_err(|e| {
4150                                CodeGenError::LLVMError(format!("Failed to cast to f32 ptr: {e}"))
4151                            })?;
4152                        self.builder.build_store(f32_ptr, float_val).map_err(|e| {
4153                            CodeGenError::LLVMError(format!("Failed to store f32 data: {e}"))
4154                        })?;
4155                    }
4156                    _ => {
4157                        return Err(CodeGenError::LLVMError(
4158                            "Expected integer or float value for 4-byte type".to_string(),
4159                        ));
4160                    }
4161                }
4162            }
4163            8 => {
4164                // Store as i64, f64, or pointer
4165                match var_data {
4166                    BasicValueEnum::IntValue(int_val) => {
4167                        let i64_ptr = self
4168                            .builder
4169                            .build_pointer_cast(
4170                                var_data_ptr,
4171                                self.context.ptr_type(AddressSpace::default()),
4172                                "i64_ptr",
4173                            )
4174                            .map_err(|e| {
4175                                CodeGenError::LLVMError(format!("Failed to cast to i64 ptr: {e}"))
4176                            })?;
4177                        self.builder.build_store(i64_ptr, int_val).map_err(|e| {
4178                            CodeGenError::LLVMError(format!("Failed to store i64 data: {e}"))
4179                        })?;
4180                    }
4181                    BasicValueEnum::FloatValue(float_val) => {
4182                        let f64_ptr = self
4183                            .builder
4184                            .build_pointer_cast(
4185                                var_data_ptr,
4186                                self.context.ptr_type(AddressSpace::default()),
4187                                "f64_ptr",
4188                            )
4189                            .map_err(|e| {
4190                                CodeGenError::LLVMError(format!("Failed to cast to f64 ptr: {e}"))
4191                            })?;
4192                        self.builder.build_store(f64_ptr, float_val).map_err(|e| {
4193                            CodeGenError::LLVMError(format!("Failed to store f64 data: {e}"))
4194                        })?;
4195                    }
4196                    BasicValueEnum::PointerValue(ptr_val) => {
4197                        // Store pointer as u64
4198                        let ptr_int = self
4199                            .builder
4200                            .build_ptr_to_int(ptr_val, self.context.i64_type(), "ptr_as_int")
4201                            .map_err(|e| {
4202                                CodeGenError::LLVMError(format!(
4203                                    "Failed to convert ptr to int: {e}"
4204                                ))
4205                            })?;
4206                        let i64_ptr = self
4207                            .builder
4208                            .build_pointer_cast(
4209                                var_data_ptr,
4210                                self.context.ptr_type(AddressSpace::default()),
4211                                "i64_ptr",
4212                            )
4213                            .map_err(|e| {
4214                                CodeGenError::LLVMError(format!("Failed to cast to i64 ptr: {e}"))
4215                            })?;
4216                        self.builder.build_store(i64_ptr, ptr_int).map_err(|e| {
4217                            CodeGenError::LLVMError(format!("Failed to store pointer data: {e}"))
4218                        })?;
4219                    }
4220                    _ => {
4221                        return Err(CodeGenError::LLVMError(
4222                            "Expected integer, float, or pointer value for 8-byte type".to_string(),
4223                        ));
4224                    }
4225                }
4226            }
4227            _ => {
4228                return Err(CodeGenError::LLVMError(format!(
4229                    "Unsupported data size: {data_size}"
4230                )));
4231            }
4232        }
4233
4234        // Already accumulated; EndInstruction will send the whole event
4235        Ok(())
4236    }
4237
4238    // PrintVariableError instruction has been removed; compile-time errors are returned as Err,
4239    // runtime errors are carried via per-variable status in Print* instructions.
4240
4241    /// Generate Backtrace instruction
4242    pub fn generate_backtrace_instruction(&mut self, depth: u8) -> Result<()> {
4243        info!("Generating Backtrace instruction: depth={}", depth);
4244
4245        // Reserve space directly for Backtrace instruction
4246        let inst_buffer = self.reserve_instruction_region(
4247            (std::mem::size_of::<InstructionHeader>() + std::mem::size_of::<BacktraceData>())
4248                as u64,
4249        );
4250
4251        // Write InstructionHeader.inst_type
4252        let inst_type_ptr = unsafe {
4253            self.builder
4254                .build_gep(
4255                    self.context.i8_type(),
4256                    inst_buffer,
4257                    &[self.context.i32_type().const_int(
4258                        std::mem::offset_of!(InstructionHeader, inst_type) as u64,
4259                        false,
4260                    )],
4261                    "bt_inst_type_ptr",
4262                )
4263                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get inst_type GEP: {e}")))?
4264        };
4265        let inst_type_val = self
4266            .context
4267            .i8_type()
4268            .const_int(InstructionType::Backtrace as u64, false);
4269        self.builder
4270            .build_store(inst_type_ptr, inst_type_val)
4271            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store inst_type: {e}")))?;
4272
4273        // Write InstructionHeader.data_length (u16)
4274        let data_length_ptr = unsafe {
4275            self.builder
4276                .build_gep(
4277                    self.context.i8_type(),
4278                    inst_buffer,
4279                    &[self.context.i32_type().const_int(
4280                        std::mem::offset_of!(InstructionHeader, data_length) as u64,
4281                        false,
4282                    )],
4283                    "bt_data_length_ptr",
4284                )
4285                .map_err(|e| {
4286                    CodeGenError::LLVMError(format!("Failed to get data_length GEP: {e}"))
4287                })?
4288        };
4289        let data_length_i16_ptr = self
4290            .builder
4291            .build_pointer_cast(
4292                data_length_ptr,
4293                self.context.ptr_type(AddressSpace::default()),
4294                "bt_data_length_i16_ptr",
4295            )
4296            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast data_length ptr: {e}")))?;
4297        let dl_val = self
4298            .context
4299            .i16_type()
4300            .const_int(std::mem::size_of::<BacktraceData>() as u64, false);
4301        self.builder
4302            .build_store(data_length_i16_ptr, dl_val)
4303            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_length: {e}")))?;
4304
4305        // Already accumulated; EndInstruction will send the whole event. Depth currently unused at BPF level.
4306        Ok(())
4307    }
4308
4309    /// Resolve variable value from script variables first, then DWARF
4310    fn resolve_variable_value(
4311        &mut self,
4312        var_name: &str,
4313        type_encoding: TypeKind,
4314        status_ptr: Option<inkwell::values::PointerValue<'ctx>>,
4315    ) -> Result<BasicValueEnum<'ctx>> {
4316        info!(
4317            "Resolving variable value: {} ({:?})",
4318            var_name, type_encoding
4319        );
4320
4321        // 1) Script variable first
4322        if self.variable_exists(var_name) {
4323            info!("Found script variable for '{}', loading value", var_name);
4324            return self.load_variable(var_name);
4325        }
4326
4327        // 2) DWARF variable as fallback
4328        match self.query_dwarf_for_variable(var_name)? {
4329            Some(var_info) => {
4330                info!(
4331                    "Found DWARF variable: {} = {:?}",
4332                    var_name, var_info.evaluation_result
4333                );
4334
4335                // Require DWARF type information
4336                let dwarf_type = var_info.dwarf_type.as_ref().ok_or_else(|| {
4337                    CodeGenError::DwarfError(format!(
4338                        "Variable '{var_name}' has no type information in DWARF"
4339                    ))
4340                })?;
4341
4342                let compile_context = self.get_compile_time_context()?;
4343                self.evaluate_result_to_llvm_value(
4344                    &var_info.evaluation_result,
4345                    dwarf_type,
4346                    var_name,
4347                    compile_context.pc_address,
4348                    status_ptr,
4349                )
4350            }
4351            None => {
4352                let compile_context = self.get_compile_time_context()?;
4353                warn!(
4354                    "Variable '{}' not found in DWARF at address 0x{:x}",
4355                    var_name, compile_context.pc_address
4356                );
4357                Err(CodeGenError::VariableNotFound(var_name.to_string()))
4358            }
4359        }
4360    }
4361
4362    /// Generate PrintComplexVariable instruction and copy data at runtime using probe_read_user
4363    fn generate_print_complex_variable_runtime(
4364        &mut self,
4365        meta: PrintVarRuntimeMeta,
4366        eval_result: &ghostscope_dwarf::EvaluationResult,
4367        dwarf_type: &ghostscope_dwarf::TypeInfo,
4368        module_hint: Option<&str>,
4369    ) -> Result<()> {
4370        tracing::trace!(
4371            var_name_index = meta.var_name_index,
4372            type_index = meta.type_index,
4373            access_path = %meta.access_path,
4374            type_size = dwarf_type.size(),
4375            data_len_limit = meta.data_len_limit,
4376            eval = ?eval_result,
4377            "generate_print_complex_variable_runtime: begin"
4378        );
4379        // Compute sizes first, then reserve instruction region directly in accumulation buffer
4380
4381        // Compute sizes
4382        let access_path_bytes = meta.access_path.as_bytes();
4383        let access_path_len = std::cmp::min(access_path_bytes.len(), 255); // u8 max
4384        let type_size = dwarf_type.size() as usize;
4385        let mut data_len = std::cmp::min(type_size, meta.data_len_limit);
4386        if data_len > u16::MAX as usize {
4387            data_len = u16::MAX as usize;
4388        }
4389
4390        let header_size = std::mem::size_of::<InstructionHeader>();
4391        let data_struct_size = std::mem::size_of::<PrintComplexVariableData>();
4392        // Reserve enough space to hold either the value (read_len) or an error payload (12 bytes)
4393        let reserved_payload = std::cmp::max(data_len, 12);
4394        let total_data_length = data_struct_size + access_path_len + reserved_payload;
4395        let total_size = header_size + total_data_length;
4396        tracing::trace!(
4397            header_size,
4398            data_struct_size,
4399            access_path_len,
4400            data_len,
4401            total_data_length,
4402            total_size,
4403            "generate_print_complex_variable_runtime: sizes computed"
4404        );
4405
4406        // Reserve space now that sizes are known
4407        let inst_buffer = self.reserve_instruction_region(total_size as u64);
4408
4409        // Avoid memset; reserved map value bytes are zero-initialized
4410
4411        // Write InstructionHeader.inst_type at offset 0
4412        let inst_type_val = self
4413            .context
4414            .i8_type()
4415            .const_int(InstructionType::PrintComplexVariable as u64, false);
4416        self.builder
4417            .build_store(inst_buffer, inst_type_val)
4418            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store inst_type: {e}")))?;
4419        tracing::trace!(
4420            "generate_print_complex_variable_runtime: wrote inst_type=PrintComplexVariable"
4421        );
4422
4423        // Write InstructionHeader
4424        // data_length field (u16) at offset 1
4425        let data_length_ptr = unsafe {
4426            self.builder
4427                .build_gep(
4428                    self.context.i8_type(),
4429                    inst_buffer,
4430                    &[self.context.i32_type().const_int(1, false)],
4431                    "data_length_ptr",
4432                )
4433                .map_err(|e| {
4434                    CodeGenError::LLVMError(format!("Failed to get data_length GEP: {e}"))
4435                })?
4436        };
4437        let data_length_ptr_cast = self
4438            .builder
4439            .build_pointer_cast(
4440                data_length_ptr,
4441                self.context.ptr_type(AddressSpace::default()),
4442                "data_length_ptr_cast",
4443            )
4444            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast data_length ptr: {e}")))?;
4445        self.builder
4446            .build_store(
4447                data_length_ptr_cast,
4448                self.context
4449                    .i16_type()
4450                    .const_int(total_data_length as u64, false),
4451            )
4452            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_length: {e}")))?;
4453        tracing::trace!(
4454            data_length = total_data_length,
4455            "generate_print_complex_variable_runtime: wrote data_length"
4456        );
4457
4458        // Data pointer (after header)
4459        let data_ptr = unsafe {
4460            self.builder
4461                .build_gep(
4462                    self.context.i8_type(),
4463                    inst_buffer,
4464                    &[self.context.i32_type().const_int(header_size as u64, false)],
4465                    "data_ptr",
4466                )
4467                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get data GEP: {e}")))?
4468        };
4469
4470        // var_name_index (u16)
4471        let var_name_index_val = self
4472            .context
4473            .i16_type()
4474            .const_int(meta.var_name_index as u64, false);
4475        // Store var_name_index at offset offsetof(PrintComplexVariableData, var_name_index)
4476        let var_name_index_off =
4477            std::mem::offset_of!(PrintComplexVariableData, var_name_index) as u64;
4478        let var_name_index_ptr_i8 = unsafe {
4479            self.builder
4480                .build_gep(
4481                    self.context.i8_type(),
4482                    data_ptr,
4483                    &[self.context.i32_type().const_int(var_name_index_off, false)],
4484                    "var_name_index_ptr_i8",
4485                )
4486                .map_err(|e| {
4487                    CodeGenError::LLVMError(format!("Failed to get var_name_index GEP: {e}"))
4488                })?
4489        };
4490        let var_name_index_ptr_i16 = self
4491            .builder
4492            .build_pointer_cast(
4493                var_name_index_ptr_i8,
4494                self.context.ptr_type(AddressSpace::default()),
4495                "var_name_index_ptr_i16",
4496            )
4497            .map_err(|e| {
4498                CodeGenError::LLVMError(format!("Failed to cast var_name_index ptr: {e}"))
4499            })?;
4500        self.builder
4501            .build_store(var_name_index_ptr_i16, var_name_index_val)
4502            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store var_name_index: {e}")))?;
4503        tracing::trace!(
4504            var_name_index = meta.var_name_index,
4505            "generate_print_complex_variable_runtime: wrote var_name_index"
4506        );
4507
4508        // type_index (u16) right after var_name_index
4509        // type_index at offset offsetof(PrintComplexVariableData, type_index) = 2
4510        let type_index_offset = std::mem::offset_of!(PrintComplexVariableData, type_index) as u64;
4511        let type_index_ptr_i8 = unsafe {
4512            self.builder
4513                .build_gep(
4514                    self.context.i8_type(),
4515                    data_ptr,
4516                    &[self.context.i32_type().const_int(type_index_offset, false)],
4517                    "type_index_ptr_i8",
4518                )
4519                .map_err(|e| {
4520                    CodeGenError::LLVMError(format!("Failed to get type_index GEP: {e}"))
4521                })?
4522        };
4523        let type_index_ptr = self
4524            .builder
4525            .build_pointer_cast(
4526                type_index_ptr_i8,
4527                self.context.ptr_type(AddressSpace::default()),
4528                "type_index_ptr_i16",
4529            )
4530            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast type_index ptr: {e}")))?;
4531        let type_index_val = self
4532            .context
4533            .i16_type()
4534            .const_int(meta.type_index as u64, false);
4535        self.builder
4536            .build_store(type_index_ptr, type_index_val)
4537            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store type_index: {e}")))?;
4538        tracing::trace!(
4539            type_index = meta.type_index,
4540            "generate_print_complex_variable_runtime: wrote type_index"
4541        );
4542
4543        // access_path_len (u8)
4544        // access_path_len at offset offsetof(..., access_path_len)
4545        let access_path_len_off =
4546            std::mem::offset_of!(PrintComplexVariableData, access_path_len) as u64;
4547        let access_path_len_ptr = unsafe {
4548            self.builder
4549                .build_gep(
4550                    self.context.i8_type(),
4551                    data_ptr,
4552                    &[self
4553                        .context
4554                        .i32_type()
4555                        .const_int(access_path_len_off, false)],
4556                    "access_path_len_ptr",
4557                )
4558                .map_err(|e| {
4559                    CodeGenError::LLVMError(format!("Failed to get access_path_len GEP: {e}"))
4560                })?
4561        };
4562        self.builder
4563            .build_store(
4564                access_path_len_ptr,
4565                self.context
4566                    .i8_type()
4567                    .const_int(access_path_len as u64, false),
4568            )
4569            .map_err(|e| {
4570                CodeGenError::LLVMError(format!("Failed to store access_path_len: {e}"))
4571            })?;
4572        tracing::trace!(
4573            access_path_len,
4574            "generate_print_complex_variable_runtime: wrote access_path_len"
4575        );
4576
4577        // status (u8) at offset offsetof(..., status)
4578        let status_off = std::mem::offset_of!(PrintComplexVariableData, status) as u64;
4579        let status_ptr = unsafe {
4580            self.builder
4581                .build_gep(
4582                    self.context.i8_type(),
4583                    data_ptr,
4584                    &[self.context.i32_type().const_int(status_off, false)],
4585                    "status_ptr",
4586                )
4587                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get status GEP: {e}")))?
4588        };
4589        self.builder
4590            .build_store(
4591                status_ptr,
4592                self.context
4593                    .i8_type()
4594                    .const_int(VariableStatus::Ok as u64, false),
4595            )
4596            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store status: {e}")))?;
4597
4598        // (Optimized-out handling moved below after data_len pointer is available)
4599
4600        // data_len (u16)
4601        let data_len_off = std::mem::offset_of!(PrintComplexVariableData, data_len) as u64;
4602        let data_len_ptr = unsafe {
4603            self.builder
4604                .build_gep(
4605                    self.context.i8_type(),
4606                    data_ptr,
4607                    &[self.context.i32_type().const_int(data_len_off, false)],
4608                    "data_len_ptr",
4609                )
4610                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get data_len GEP: {e}")))?
4611        };
4612        let data_len_ptr_cast = self
4613            .builder
4614            .build_pointer_cast(
4615                data_len_ptr,
4616                self.context.ptr_type(AddressSpace::default()),
4617                "data_len_ptr_i16",
4618            )
4619            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast data_len ptr: {e}")))?;
4620        self.builder
4621            .build_store(
4622                data_len_ptr_cast,
4623                self.context.i16_type().const_int(data_len as u64, false),
4624            )
4625            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_len: {e}")))?;
4626        tracing::trace!(
4627            data_len,
4628            "generate_print_complex_variable_runtime: wrote data_len"
4629        );
4630
4631        // Optimized-out case is handled earlier by resolving to an OptimizedOut type and ImmediateBytes path.
4632
4633        // access_path bytes start after PrintComplexVariableData
4634        let access_path_ptr = unsafe {
4635            self.builder
4636                .build_gep(
4637                    self.context.i8_type(),
4638                    data_ptr,
4639                    &[self.context.i32_type().const_int(
4640                        std::mem::size_of::<PrintComplexVariableData>() as u64,
4641                        false,
4642                    )],
4643                    "access_path_ptr",
4644                )
4645                .map_err(|e| {
4646                    CodeGenError::LLVMError(format!("Failed to get access_path GEP: {e}"))
4647                })?
4648        };
4649
4650        // Copy access path bytes
4651        for (i, &byte) in access_path_bytes.iter().enumerate().take(access_path_len) {
4652            let byte_ptr = unsafe {
4653                self.builder
4654                    .build_gep(
4655                        self.context.i8_type(),
4656                        access_path_ptr,
4657                        &[self.context.i32_type().const_int(i as u64, false)],
4658                        &format!("access_path_byte_{i}"),
4659                    )
4660                    .map_err(|e| {
4661                        CodeGenError::LLVMError(format!("Failed to get access_path byte GEP: {e}"))
4662                    })?
4663            };
4664            let byte_val = self.context.i8_type().const_int(byte as u64, false);
4665            self.builder.build_store(byte_ptr, byte_val).map_err(|e| {
4666                CodeGenError::LLVMError(format!("Failed to store access_path byte: {e}"))
4667            })?;
4668        }
4669        if access_path_len > 0 {
4670            tracing::trace!("generate_print_complex_variable_runtime: wrote access_path bytes");
4671        }
4672
4673        // Variable data starts after access_path
4674        let variable_data_ptr = unsafe {
4675            self.builder
4676                .build_gep(
4677                    self.context.i8_type(),
4678                    access_path_ptr,
4679                    &[self
4680                        .context
4681                        .i32_type()
4682                        .const_int(access_path_len as u64, false)],
4683                    "variable_data_ptr",
4684                )
4685                .map_err(|e| {
4686                    CodeGenError::LLVMError(format!("Failed to get variable_data GEP: {e}"))
4687                })?
4688        };
4689
4690        // Compute source address with ASLR-aware helper, honoring module hint
4691        // Prefer a previously recorded module path for offsets; fall back handled in helper
4692        let src_addr = self.evaluation_result_to_address_with_hint(
4693            eval_result,
4694            Some(status_ptr),
4695            module_hint,
4696        )?;
4697        tracing::trace!(src_addr = %{src_addr}, "generate_print_complex_variable_runtime: computed src_addr");
4698
4699        // Setup common types and casts
4700        let ptr_type = self.context.ptr_type(AddressSpace::default());
4701        let i32_type = self.context.i32_type();
4702        let i64_type = self.context.i64_type();
4703        let dst_ptr = self
4704            .builder
4705            .build_bit_cast(variable_data_ptr, ptr_type, "dst_ptr")
4706            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4707        let size_val = i32_type.const_int(data_len as u64, false);
4708        let src_ptr = self
4709            .builder
4710            .build_int_to_ptr(src_addr, ptr_type, "src_ptr")
4711            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4712        let offsets_found = self.load_offsets_found_flag()?;
4713        let current_block = self.builder.get_insert_block().unwrap();
4714        let current_fn = current_block.get_parent().unwrap();
4715        let cont_block = self.context.append_basic_block(current_fn, "after_read");
4716        let skip_block = self.context.append_basic_block(current_fn, "offsets_skip");
4717        let found_block = self.context.append_basic_block(current_fn, "offsets_found");
4718        self.builder
4719            .build_conditional_branch(offsets_found, found_block, skip_block)
4720            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4721        self.builder.position_at_end(skip_block);
4722        self.mark_any_fail()?;
4723        self.builder
4724            .build_store(data_len_ptr_cast, self.context.i16_type().const_zero())
4725            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4726        self.builder
4727            .build_unconditional_branch(cont_block)
4728            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4729        self.builder.position_at_end(found_block);
4730
4731        // Branch: NULL deref if src_addr == 0
4732        let zero64 = i64_type.const_zero();
4733        let is_null = self
4734            .builder
4735            .build_int_compare(inkwell::IntPredicate::EQ, src_addr, zero64, "is_null")
4736            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4737        let null_block = self.context.append_basic_block(current_fn, "null_deref");
4738        let read_block = self.context.append_basic_block(current_fn, "read_user");
4739        self.builder
4740            .build_conditional_branch(is_null, null_block, read_block)
4741            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4742
4743        // NULL path
4744        self.builder.position_at_end(null_block);
4745        self.builder
4746            .build_store(
4747                status_ptr,
4748                self.context
4749                    .i8_type()
4750                    .const_int(VariableStatus::NullDeref as u64, false),
4751            )
4752            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4753        // data_len = 0
4754        self.builder
4755            .build_store(data_len_ptr_cast, self.context.i16_type().const_zero())
4756            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4757        // mark fail
4758        self.mark_any_fail()?;
4759        self.builder
4760            .build_unconditional_branch(cont_block)
4761            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4762
4763        // Read path
4764        self.builder.position_at_end(read_block);
4765        let ret = self
4766            .create_bpf_helper_call(
4767                BPF_FUNC_probe_read_user as u64,
4768                &[dst_ptr, size_val.into(), src_ptr.into()],
4769                i32_type.into(),
4770                "probe_read_user",
4771            )?
4772            .into_int_value();
4773        let is_err = self
4774            .builder
4775            .build_int_compare(
4776                inkwell::IntPredicate::SLT,
4777                ret,
4778                i32_type.const_zero(),
4779                "ret_lt_zero",
4780            )
4781            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4782        let err_block = self.context.append_basic_block(current_fn, "read_err");
4783        let ok_block = self.context.append_basic_block(current_fn, "read_ok");
4784        self.builder
4785            .build_conditional_branch(is_err, err_block, ok_block)
4786            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4787
4788        // Error: status=2 (read_user failed); attach errno+addr payload and set data_len=12
4789        self.builder.position_at_end(err_block);
4790        // Only set ReadError if status is still Ok (preserve OffsetsUnavailable etc.)
4791        let cur_status1 = self
4792            .builder
4793            .build_load(self.context.i8_type(), status_ptr, "cur_status1")
4794            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4795        let is_ok1 = self
4796            .builder
4797            .build_int_compare(
4798                inkwell::IntPredicate::EQ,
4799                cur_status1.into_int_value(),
4800                self.context.i8_type().const_zero(),
4801                "status_is_ok1",
4802            )
4803            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4804        let readerr_val = self
4805            .context
4806            .i8_type()
4807            .const_int(VariableStatus::ReadError as u64, false)
4808            .into();
4809        let new_status1 = self
4810            .builder
4811            .build_select(is_ok1, readerr_val, cur_status1, "status_after_readerr")
4812            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4813        self.builder
4814            .build_store(status_ptr, new_status1)
4815            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4816        // data_len = 12 (errno:i32 + addr:u64)
4817        self.builder
4818            .build_store(
4819                data_len_ptr_cast,
4820                self.context.i16_type().const_int(12, false),
4821            )
4822            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4823        // write errno at [0..4]
4824        let errno_ptr = self
4825            .builder
4826            .build_pointer_cast(
4827                variable_data_ptr,
4828                self.context.ptr_type(AddressSpace::default()),
4829                "errno_ptr",
4830            )
4831            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast errno ptr: {e}")))?;
4832        let errno = self.build_errno_i32(ret, "readerr_errno_i32")?;
4833        self.builder
4834            .build_store(errno_ptr, errno)
4835            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store errno: {e}")))?;
4836        // write addr at [4..12]
4837        let addr_ptr_i8 = unsafe {
4838            self.builder
4839                .build_gep(
4840                    self.context.i8_type(),
4841                    variable_data_ptr,
4842                    &[self.context.i32_type().const_int(4, false)],
4843                    "addr_ptr_i8",
4844                )
4845                .map_err(|e| CodeGenError::LLVMError(format!("Failed to get addr GEP: {e}")))?
4846        };
4847        let addr_ptr = self
4848            .builder
4849            .build_pointer_cast(
4850                addr_ptr_i8,
4851                self.context.ptr_type(AddressSpace::default()),
4852                "addr_ptr",
4853            )
4854            .map_err(|e| CodeGenError::LLVMError(format!("Failed to cast addr ptr: {e}")))?;
4855        self.builder
4856            .build_store(addr_ptr, src_addr)
4857            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store addr: {e}")))?;
4858        // mark fail
4859        self.mark_any_fail()?;
4860        self.builder
4861            .build_unconditional_branch(cont_block)
4862            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4863
4864        // OK path: status=0; optional truncated if data_len_limit < dwarf_type.size()
4865        self.builder.position_at_end(ok_block);
4866        if data_len < dwarf_type.size() as usize {
4867            // truncated
4868            self.builder
4869                .build_store(
4870                    status_ptr,
4871                    self.context
4872                        .i8_type()
4873                        .const_int(VariableStatus::Truncated as u64, false),
4874                )
4875                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4876            // mark both success and fail
4877            self.mark_any_success()?;
4878            self.mark_any_fail()?;
4879        } else {
4880            // success
4881            self.mark_any_success()?;
4882        }
4883        self.builder
4884            .build_unconditional_branch(cont_block)
4885            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
4886
4887        // Continue
4888        self.builder.position_at_end(cont_block);
4889
4890        // Already accumulated; EndInstruction will send the whole event
4891        Ok(())
4892    }
4893}
4894
4895#[cfg(test)]
4896mod tests {
4897    use super::*;
4898    use crate::CompileOptions;
4899    use ghostscope_protocol::trace_event::{TraceEventHeader, TraceEventMessage};
4900
4901    #[test]
4902    fn print_complex_format_budget_tracks_event_size() {
4903        let bytes_reserved_so_far =
4904            std::mem::size_of::<TraceEventHeader>() + std::mem::size_of::<TraceEventMessage>();
4905        let expected = 32768
4906            - (bytes_reserved_so_far
4907                + std::mem::size_of::<InstructionHeader>()
4908                + std::mem::size_of::<EndInstructionData>());
4909        assert_eq!(
4910            print_complex_format_instruction_budget(32768, bytes_reserved_so_far),
4911            expected
4912        );
4913        assert!(print_complex_format_instruction_budget(32768, bytes_reserved_so_far) > 4096);
4914    }
4915
4916    #[test]
4917    fn print_complex_format_budget_shrinks_after_prior_instructions() {
4918        let bytes_reserved_so_far = std::mem::size_of::<TraceEventHeader>()
4919            + std::mem::size_of::<TraceEventMessage>()
4920            + 2048;
4921        let base_budget = print_complex_format_instruction_budget(
4922            32768,
4923            std::mem::size_of::<TraceEventHeader>() + std::mem::size_of::<TraceEventMessage>(),
4924        );
4925        assert_eq!(
4926            print_complex_format_instruction_budget(32768, bytes_reserved_so_far),
4927            base_budget - 2048
4928        );
4929    }
4930
4931    #[test]
4932    fn dynamic_payload_reservations_share_budget_fairly() {
4933        let reservations = allocate_dynamic_payload_reservations(&[256, 256, 256, 256], 512);
4934        assert_eq!(reservations, vec![128, 128, 128, 128]);
4935    }
4936
4937    #[test]
4938    fn dynamic_payload_reservations_keep_error_headroom_when_possible() {
4939        let reservations = allocate_dynamic_payload_reservations(&[256, 256, 256], 36);
4940        assert_eq!(reservations, vec![12, 12, 12]);
4941    }
4942
4943    #[test]
4944    fn build_errno_i32_truncates_i64_errors() {
4945        let context = inkwell::context::Context::create();
4946        let opts = CompileOptions::default();
4947        let ctx =
4948            EbpfContext::new(&context, "test_mod", Some(0), &opts).expect("create EbpfContext");
4949        let fn_type = context.i32_type().fn_type(&[], false);
4950        let function = ctx.module.add_function("errno_test", fn_type, None);
4951        let block = context.append_basic_block(function, "entry");
4952        ctx.builder.position_at_end(block);
4953
4954        let errno = ctx
4955            .build_errno_i32(
4956                context.i64_type().const_int((-14i64) as u64, true),
4957                "errno_i32",
4958            )
4959            .expect("truncate errno");
4960        assert_eq!(errno.get_type().get_bit_width(), 32);
4961    }
4962
4963    #[test]
4964    fn computed_int_store_i64_compiles() {
4965        let context = inkwell::context::Context::create();
4966        let opts = CompileOptions::default();
4967        let mut ctx =
4968            EbpfContext::new(&context, "test_mod", Some(0), &opts).expect("create EbpfContext");
4969        // print {} with a pure script integer expression triggers ComputedInt path
4970        let expr = crate::script::Expr::BinaryOp {
4971            left: Box::new(crate::script::Expr::Int(41)),
4972            op: crate::script::BinaryOp::Add,
4973            right: Box::new(crate::script::Expr::Int(1)),
4974        };
4975        let stmt =
4976            crate::script::Statement::Print(crate::script::PrintStatement::ComplexVariable(expr));
4977        let program = crate::script::Program::new();
4978        let res = ctx.compile_program(&program, "test_func", &[stmt], None, None, None);
4979        assert!(res.is_ok(), "Compilation failed: {:?}", res.err());
4980    }
4981
4982    #[test]
4983    fn computed_int_in_format_compiles() {
4984        let context = inkwell::context::Context::create();
4985        let opts = CompileOptions::default();
4986        let mut ctx =
4987            EbpfContext::new(&context, "test_mod", Some(0), &opts).expect("create EbpfContext");
4988        // formatted print with expression argument should also route into ComputedInt path
4989        let expr = crate::script::Expr::BinaryOp {
4990            left: Box::new(crate::script::Expr::Int(1)),
4991            op: crate::script::BinaryOp::Add,
4992            right: Box::new(crate::script::Expr::Int(2)),
4993        };
4994        let stmt = crate::script::Statement::Print(crate::script::PrintStatement::Formatted {
4995            format: "sum:{}".to_string(),
4996            args: vec![expr],
4997        });
4998        let program = crate::script::Program::new();
4999        let res = ctx.compile_program(&program, "test_fmt", &[stmt], None, None, None);
5000        assert!(res.is_ok(), "Compilation failed: {:?}", res.err());
5001    }
5002
5003    #[test]
5004    fn memcmp_rejects_script_pointer_variable_now() {
5005        let context = inkwell::context::Context::create();
5006        let opts = CompileOptions::default();
5007        let mut ctx =
5008            EbpfContext::new(&context, "test_mod", Some(0), &opts).expect("create EbpfContext");
5009
5010        // let p = "A";  // script pointer to const string (no longer accepted as memcmp arg)
5011        let decl = crate::script::Statement::VarDeclaration {
5012            name: "p".to_string(),
5013            value: crate::script::Expr::String("A".to_string()),
5014        };
5015
5016        // if memcmp(p, hex("41"), 1) { print "OK"; }
5017        let if_stmt = crate::script::Statement::If {
5018            condition: crate::script::Expr::BuiltinCall {
5019                name: "memcmp".to_string(),
5020                args: vec![
5021                    crate::script::Expr::Variable("p".to_string()),
5022                    crate::script::Expr::BuiltinCall {
5023                        name: "hex".to_string(),
5024                        args: vec![crate::script::Expr::String("41".to_string())],
5025                    },
5026                    crate::script::Expr::Int(1),
5027                ],
5028            },
5029            then_body: vec![crate::script::Statement::Print(
5030                crate::script::PrintStatement::String("OK".to_string()),
5031            )],
5032            else_body: None,
5033        };
5034
5035        let program = crate::script::Program::new();
5036        let res = ctx.compile_program(
5037            &program,
5038            "test_memcmp_ptr",
5039            &[decl, if_stmt],
5040            None,
5041            None,
5042            None,
5043        );
5044        assert!(
5045            res.is_err(),
5046            "Expected type error for script pointer variable in memcmp"
5047        );
5048    }
5049
5050    #[test]
5051    fn strncmp_requires_string_on_one_side_error_message() {
5052        let context = inkwell::context::Context::create();
5053        let opts = CompileOptions::default();
5054        let mut ctx = EbpfContext::new(&context, "test_mod", Some(0), &opts).expect("ctx");
5055
5056        // strncmp(42, 43, 2) -> neither side is string (literal/var); expect type error
5057        let stmt = crate::script::Statement::If {
5058            condition: crate::script::Expr::BuiltinCall {
5059                name: "strncmp".to_string(),
5060                args: vec![
5061                    crate::script::Expr::Int(42),
5062                    crate::script::Expr::Int(43),
5063                    crate::script::Expr::Int(2),
5064                ],
5065            },
5066            then_body: vec![crate::script::Statement::Print(
5067                crate::script::PrintStatement::String("OK".to_string()),
5068            )],
5069            else_body: None,
5070        };
5071        let program = crate::script::Program::new();
5072        let res = ctx.compile_program(&program, "test_strncmp_err", &[stmt], None, None, None);
5073        assert!(
5074            res.is_err(),
5075            "expected error when neither side is string (got {res:?})",
5076        );
5077        let msg = format!("{:?}", res.err());
5078        assert!(msg.contains("strncmp requires at least one string argument"));
5079    }
5080
5081    // No test needed here for string var copy rejection; current semantics allow
5082    // let s = "A"; let p = s; as a string-to-string assignment.
5083
5084    #[test]
5085    fn immutable_variable_redeclaration_rejected() {
5086        let context = inkwell::context::Context::create();
5087        let opts = CompileOptions::default();
5088        let mut ctx = EbpfContext::new(&context, "test_mod", Some(0), &opts).expect("ctx");
5089
5090        // let x = 1; let x = 2;  (same trace block)
5091        let d1 = crate::script::Statement::VarDeclaration {
5092            name: "x".to_string(),
5093            value: crate::script::Expr::Int(1),
5094        };
5095        let d2 = crate::script::Statement::VarDeclaration {
5096            name: "x".to_string(),
5097            value: crate::script::Expr::Int(2),
5098        };
5099        let program = crate::script::Program::new();
5100        let res = ctx.compile_program(&program, "immut", &[d1, d2], None, None, None);
5101        assert!(res.is_err(), "expected immutability error, got {res:?}");
5102        let msg = format!("{:?}", res.err());
5103        assert!(
5104            msg.contains("Redeclaration in the same scope") || msg.contains("immutable variable"),
5105            "unexpected error msg: {msg}"
5106        );
5107    }
5108
5109    #[test]
5110    fn immutable_alias_rebinding_rejected() {
5111        let context = inkwell::context::Context::create();
5112        let opts = CompileOptions::default();
5113        let mut ctx = EbpfContext::new(&context, "test_mod", Some(0), &opts).expect("ctx");
5114
5115        // let p = &arr[0]; let p = &arr[0];
5116        let a1 = crate::script::Statement::AliasDeclaration {
5117            name: "p".to_string(),
5118            target: crate::script::Expr::AddressOf(Box::new(crate::script::Expr::Variable(
5119                "arr".to_string(),
5120            ))),
5121        };
5122        let a2 = crate::script::Statement::AliasDeclaration {
5123            name: "p".to_string(),
5124            target: crate::script::Expr::AddressOf(Box::new(crate::script::Expr::Variable(
5125                "arr".to_string(),
5126            ))),
5127        };
5128        let program = crate::script::Program::new();
5129        let res = ctx.compile_program(&program, "immut_alias", &[a1, a2], None, None, None);
5130        assert!(
5131            res.is_err(),
5132            "expected immutability error for alias, got {res:?}"
5133        );
5134    }
5135
5136    #[test]
5137    fn alias_to_alias_with_const_offset_is_alias_variable() {
5138        let context = inkwell::context::Context::create();
5139        let opts = CompileOptions::default();
5140        let mut ctx = EbpfContext::new(&context, "test_mod", Some(0), &opts).expect("ctx");
5141        // let base = &buf[0]; let tail = base + 16;
5142        let s1 = crate::script::Statement::AliasDeclaration {
5143            name: "base".to_string(),
5144            target: crate::script::Expr::AddressOf(Box::new(crate::script::Expr::ArrayAccess(
5145                Box::new(crate::script::Expr::Variable("buf".to_string())),
5146                Box::new(crate::script::Expr::Int(0)),
5147            ))),
5148        };
5149        let s2 = crate::script::Statement::VarDeclaration {
5150            name: "tail".to_string(),
5151            value: crate::script::Expr::BinaryOp {
5152                left: Box::new(crate::script::Expr::Variable("base".to_string())),
5153                op: crate::script::BinaryOp::Add,
5154                right: Box::new(crate::script::Expr::Int(16)),
5155            },
5156        };
5157        let program = crate::script::Program::new();
5158        // Should treat tail as alias (not as value), thus compile_program succeeds
5159        let res = ctx.compile_program(&program, "alias_stage", &[s1, s2], None, None, None);
5160        assert!(res.is_ok(), "expected alias-to-alias staging to compile");
5161    }
5162
5163    #[test]
5164    fn alias_to_alias_copy_is_alias_variable() {
5165        let context = inkwell::context::Context::create();
5166        let opts = CompileOptions::default();
5167        let mut ctx = EbpfContext::new(&context, "test_mod", Some(0), &opts).expect("ctx");
5168        // let a = &G_STATE.lib; let b = a;
5169        let a = crate::script::Statement::AliasDeclaration {
5170            name: "a".to_string(),
5171            target: crate::script::Expr::AddressOf(Box::new(crate::script::Expr::MemberAccess(
5172                Box::new(crate::script::Expr::Variable("G_STATE".to_string())),
5173                "lib".to_string(),
5174            ))),
5175        };
5176        let b = crate::script::Statement::VarDeclaration {
5177            name: "b".to_string(),
5178            value: crate::script::Expr::Variable("a".to_string()),
5179        };
5180        let program = crate::script::Program::new();
5181        let res = ctx.compile_program(&program, "alias_copy", &[a, b], None, None, None);
5182        assert!(res.is_ok(), "expected alias-to-alias copy to compile");
5183    }
5184
5185    #[test]
5186    fn alias_self_reference_is_rejected_with_cycle_error() {
5187        let context = inkwell::context::Context::create();
5188        let opts = CompileOptions::default();
5189        let mut ctx = EbpfContext::new(&context, "test_mod", Some(0), &opts).expect("ctx");
5190
5191        // let a = &a; print a;
5192        let a = crate::script::Statement::AliasDeclaration {
5193            name: "a".to_string(),
5194            target: crate::script::Expr::AddressOf(Box::new(crate::script::Expr::Variable(
5195                "a".to_string(),
5196            ))),
5197        };
5198        let p = crate::script::Statement::Print(crate::script::PrintStatement::ComplexVariable(
5199            crate::script::Expr::Variable("a".to_string()),
5200        ));
5201        let program = crate::script::Program::new();
5202        let res = ctx.compile_program(&program, "alias_self", &[a, p], None, None, None);
5203        assert!(res.is_err(), "expected cycle error, got {res:?}");
5204        let msg = format!("{:?}", res.err());
5205        assert!(
5206            msg.contains("alias cycle") || msg.contains("depth exceeded"),
5207            "unexpected error: {msg}"
5208        );
5209    }
5210
5211    #[test]
5212    fn alias_mutual_cycle_is_rejected_with_cycle_error() {
5213        let context = inkwell::context::Context::create();
5214        let opts = CompileOptions::default();
5215        let mut ctx = EbpfContext::new(&context, "test_mod", Some(0), &opts).expect("ctx");
5216
5217        // let a = &b; let b = &a; print a;
5218        let a = crate::script::Statement::AliasDeclaration {
5219            name: "a".to_string(),
5220            target: crate::script::Expr::AddressOf(Box::new(crate::script::Expr::Variable(
5221                "b".to_string(),
5222            ))),
5223        };
5224        let b = crate::script::Statement::AliasDeclaration {
5225            name: "b".to_string(),
5226            target: crate::script::Expr::AddressOf(Box::new(crate::script::Expr::Variable(
5227                "a".to_string(),
5228            ))),
5229        };
5230        let p = crate::script::Statement::Print(crate::script::PrintStatement::ComplexVariable(
5231            crate::script::Expr::Variable("a".to_string()),
5232        ));
5233        let program = crate::script::Program::new();
5234        let res = ctx.compile_program(&program, "alias_cycle", &[a, b, p], None, None, None);
5235        assert!(res.is_err(), "expected cycle error, got {res:?}");
5236        let msg = format!("{:?}", res.err());
5237        assert!(
5238            msg.contains("alias cycle") || msg.contains("depth exceeded"),
5239            "unexpected error: {msg}"
5240        );
5241    }
5242
5243    #[test]
5244    fn strncmp_folds_with_script_string_and_literal_true() {
5245        let context = inkwell::context::Context::create();
5246        let opts = CompileOptions::default();
5247        let mut ctx = EbpfContext::new(&context, "test_mod", Some(0), &opts).expect("ctx");
5248
5249        // Prepare: let s = "ABC";
5250        let decl = crate::script::Statement::VarDeclaration {
5251            name: "s".to_string(),
5252            value: crate::script::Expr::String("ABC".to_string()),
5253        };
5254        let program = crate::script::Program::new();
5255        let res = ctx.compile_program(&program, "decl", &[decl], None, None, None);
5256        assert!(res.is_ok());
5257
5258        // Expression: strncmp(s, "ABD", 2) -> true
5259        let expr = crate::script::Expr::BuiltinCall {
5260            name: "strncmp".to_string(),
5261            args: vec![
5262                crate::script::Expr::Variable("s".to_string()),
5263                crate::script::Expr::String("ABD".to_string()),
5264                crate::script::Expr::Int(2),
5265            ],
5266        };
5267        let v = ctx.compile_expr(&expr).expect("compile expr");
5268        match v {
5269            inkwell::values::BasicValueEnum::IntValue(iv) => {
5270                assert_eq!(iv.get_type().get_bit_width(), 1);
5271                // true expected (string repr may vary across LLVM versions, check both forms)
5272                let s = format!("{iv}");
5273                assert!(s.contains("i1 true") || s.contains("true"));
5274            }
5275            other => panic!("expected IntValue i1, got {other:?}"),
5276        }
5277    }
5278
5279    #[test]
5280    fn starts_with_folds_with_two_literals() {
5281        let context = inkwell::context::Context::create();
5282        let opts = CompileOptions::default();
5283        let mut ctx = EbpfContext::new(&context, "test_mod", Some(0), &opts).expect("ctx");
5284
5285        // Expression: starts_with("abcdef", "abc") -> true
5286        let expr = crate::script::Expr::BuiltinCall {
5287            name: "starts_with".to_string(),
5288            args: vec![
5289                crate::script::Expr::String("abcdef".to_string()),
5290                crate::script::Expr::String("abc".to_string()),
5291            ],
5292        };
5293        let v = ctx.compile_expr(&expr).expect("compile expr");
5294        match v {
5295            inkwell::values::BasicValueEnum::IntValue(iv) => {
5296                assert_eq!(iv.get_type().get_bit_width(), 1);
5297                let s = format!("{iv}");
5298                assert!(s.contains("i1 true") || s.contains("true"));
5299            }
5300            _ => panic!("expected i1"),
5301        }
5302    }
5303
5304    #[test]
5305    fn starts_with_requires_one_string_side_error() {
5306        let context = inkwell::context::Context::create();
5307        let opts = CompileOptions::default();
5308        let mut ctx = EbpfContext::new(&context, "test_mod", Some(0), &opts).expect("ctx");
5309
5310        // Neither side is string
5311        let expr = crate::script::Expr::BuiltinCall {
5312            name: "starts_with".to_string(),
5313            args: vec![crate::script::Expr::Int(1), crate::script::Expr::Int(2)],
5314        };
5315        let res = ctx.compile_expr(&expr);
5316        assert!(res.is_err(), "expected error");
5317        let msg = format!("{:?}", res.err());
5318        assert!(msg.contains("starts_with requires at least one string argument"));
5319    }
5320
5321    #[test]
5322    fn shadowing_rejected_in_inner_scope() {
5323        let context = inkwell::context::Context::create();
5324        let opts = CompileOptions::default();
5325        let mut ctx = EbpfContext::new(&context, "test_mod", Some(0), &opts).expect("ctx");
5326
5327        // let x = 1; { let x = 2; }
5328        let d1 = crate::script::Statement::VarDeclaration {
5329            name: "x".to_string(),
5330            value: crate::script::Expr::Int(1),
5331        };
5332        let inner =
5333            crate::script::Statement::Block(vec![crate::script::Statement::VarDeclaration {
5334                name: "x".to_string(),
5335                value: crate::script::Expr::Int(2),
5336            }]);
5337        let program = crate::script::Program::new();
5338        let res = ctx.compile_program(&program, "shadow", &[d1, inner], None, None, None);
5339        assert!(res.is_err(), "expected shadowing error");
5340        let msg = format!("{:?}", res.err());
5341        assert!(
5342            msg.contains("Shadowing is not allowed") || msg.contains("shadow"),
5343            "unexpected: {msg}"
5344        );
5345    }
5346
5347    #[test]
5348    fn out_of_scope_use_is_rejected() {
5349        let context = inkwell::context::Context::create();
5350        let opts = CompileOptions::default();
5351        let mut ctx = EbpfContext::new(&context, "test_mod", Some(0), &opts).expect("ctx");
5352
5353        // { let y = 2; } print y;  -> y is out of scope
5354        let block =
5355            crate::script::Statement::Block(vec![crate::script::Statement::VarDeclaration {
5356                name: "y".to_string(),
5357                value: crate::script::Expr::Int(2),
5358            }]);
5359        let print_y = crate::script::Statement::Print(crate::script::PrintStatement::Variable(
5360            "y".to_string(),
5361        ));
5362        let program = crate::script::Program::new();
5363        let res = ctx.compile_program(
5364            &program,
5365            "out_of_scope",
5366            &[block, print_y],
5367            None,
5368            None,
5369            None,
5370        );
5371        assert!(
5372            res.is_err(),
5373            "expected out-of-scope or missing analyzer error"
5374        );
5375    }
5376
5377    #[test]
5378    fn memcmp_rejects_bare_integer_pointer_argument() {
5379        let context = inkwell::context::Context::create();
5380        let opts = CompileOptions::default();
5381        let mut ctx =
5382            EbpfContext::new(&context, "test_mod", Some(0), &opts).expect("create EbpfContext");
5383
5384        // let q = 0xdeadbeef;  // integer, not a pointer value
5385        let decl = crate::script::Statement::VarDeclaration {
5386            name: "q".to_string(),
5387            value: crate::script::Expr::Int(0xdeadbeef),
5388        };
5389
5390        // if memcmp(q, hex("00"), 1) { print "X"; }
5391        let if_stmt = crate::script::Statement::If {
5392            condition: crate::script::Expr::BuiltinCall {
5393                name: "memcmp".to_string(),
5394                args: vec![
5395                    crate::script::Expr::Variable("q".to_string()),
5396                    crate::script::Expr::BuiltinCall {
5397                        name: "hex".to_string(),
5398                        args: vec![crate::script::Expr::String("00".to_string())],
5399                    },
5400                    crate::script::Expr::Int(1),
5401                ],
5402            },
5403            then_body: vec![crate::script::Statement::Print(
5404                crate::script::PrintStatement::String("X".to_string()),
5405            )],
5406            else_body: None,
5407        };
5408
5409        let program = crate::script::Program::new();
5410        let res = ctx.compile_program(
5411            &program,
5412            "test_memcmp_int_ptr",
5413            &[decl, if_stmt],
5414            None,
5415            None,
5416            None,
5417        );
5418        assert!(res.is_err(), "Expected compilation error but got Ok");
5419    }
5420
5421    #[test]
5422    fn expr_to_name_truncates_utf8_safely() {
5423        let context = inkwell::context::Context::create();
5424        let opts = CompileOptions::default();
5425        let ctx = EbpfContext::new(&context, "test_mod", Some(0), &opts).expect("create ctx");
5426        // Build a long expression comprised of multibyte chars to exceed 96 chars
5427        let mut chain: Vec<String> = Vec::new();
5428        for _ in 0..50 {
5429            // each "错误" is 6 bytes, 2 chars -> quickly exceeds 96 chars
5430            chain.push("错误".to_string());
5431        }
5432        let expr = crate::script::Expr::ChainAccess(chain);
5433        let s = ctx.expr_to_name(&expr);
5434        // Ensure we got a trailing ellipsis and no panic on multibyte boundary
5435        assert!(s.ends_with("..."));
5436        assert!(s.chars().count() <= 96);
5437    }
5438
5439    #[test]
5440    fn pointer_int_arithmetic_is_rejected_with_friendly_error() {
5441        let context = inkwell::context::Context::create();
5442        let opts = CompileOptions::default();
5443        let mut ctx = EbpfContext::new(&context, "ptr_arith", Some(0), &opts).expect("ctx");
5444        ctx.create_basic_ebpf_function("f").expect("fn");
5445
5446        // Create a script variable 'p' of pointer type (null pointer)
5447        let ptr_ty = ctx.context.ptr_type(inkwell::AddressSpace::default());
5448        let null_ptr = ptr_ty.const_null();
5449        ctx.store_variable("p", null_ptr.into()).expect("store ptr");
5450
5451        // Expression: p + 1
5452        let expr = crate::script::Expr::BinaryOp {
5453            left: Box::new(crate::script::Expr::Variable("p".to_string())),
5454            op: crate::script::BinaryOp::Add,
5455            right: Box::new(crate::script::Expr::Int(1)),
5456        };
5457        let res = ctx.compile_expr(&expr);
5458        assert!(res.is_err(), "expected pointer-int arithmetic error");
5459        let msg = format!("{:?}", res.err());
5460        assert!(
5461            msg.contains("pointer and integer")
5462                || msg.contains("Unsupported operation between pointer and integer"),
5463            "unexpected error message: {msg}"
5464        );
5465    }
5466}