Skip to main content

ghostscope_compiler/ebpf/
dwarf_bridge.rs

1//! DWARF debugging information bridge
2//!
3//! This module handles integration with DWARF debug information for
4//! variable type resolution and read-plan lowering.
5
6use super::context::{CodeGenError, EbpfContext, Result, RuntimeAddress};
7use ghostscope_dwarf::{
8    AddressOrigin, Availability, EntryValueCase, LvalueAddressPlan, MemoryAccessSize, PlanExprOp,
9    PlannedAddress, PlannedAddressKind, RuntimeComputedExpr, SectionType, TypeInfo,
10    VariableAccessPath, VariableAccessSegment, VariableMaterializationPlan, VariableReadPlan,
11};
12use ghostscope_process::module_probe;
13use inkwell::values::{BasicValueEnum, IntValue, PointerValue};
14use tracing::{debug, warn};
15
16impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> {
17    pub(super) fn module_path_for_offsets(module_path: Option<&std::path::Path>) -> Option<String> {
18        module_path.map(|path| path.to_string_lossy().into_owned())
19    }
20
21    /// Compute a stable cookie for a module when per-PID offsets are unavailable (via coordinator).
22    fn fallback_cookie_from_module_path(&self, module_path: &str) -> u64 {
23        module_probe::cookie_for_path(module_path)
24    }
25
26    /// Compute section code for an address within a module (text=0, rodata=1, data=2, bss=3).
27    fn section_code_for_address(&mut self, module_path: &str, link_addr: u64) -> u8 {
28        if let Some(analyzer) = self.process_analyzer {
29            if let Some(st) = analyzer.classify_section_for_address(module_path, link_addr) {
30                return match st {
31                    SectionType::Text => 0,
32                    SectionType::Rodata => 1,
33                    SectionType::Data => 2,
34                    SectionType::Bss => 3,
35                    _ => 2,
36                };
37            }
38        }
39        2
40    }
41
42    /// Compute cookie for module using coordinator policy.
43    pub(crate) fn cookie_for_module_or_fallback(&mut self, module_path: &str) -> u64 {
44        self.fallback_cookie_from_module_path(module_path)
45    }
46    fn planned_value_to_llvm_value(
47        &mut self,
48        value: &ghostscope_dwarf::PlannedValue,
49        var_name: &str,
50        status_ptr: Option<PointerValue<'ctx>>,
51        module_hint: Option<&str>,
52    ) -> Result<BasicValueEnum<'ctx>> {
53        let pt_regs_ptr = self.get_pt_regs_parameter()?;
54        match value {
55            ghostscope_dwarf::PlannedValue::Constant { value, .. } => Ok(self
56                .context
57                .i64_type()
58                .const_int(*value as u64, true)
59                .into()),
60            ghostscope_dwarf::PlannedValue::RegisterValue { dwarf_reg, .. } => {
61                debug!("Generating register value: {dwarf_reg}");
62                self.load_register_value(*dwarf_reg, pt_regs_ptr)
63            }
64            ghostscope_dwarf::PlannedValue::RuntimeComputed { expr, result_size } => {
65                debug!(
66                    "Generating runtime-computed value: {} steps",
67                    expr.ops().len()
68                );
69                let runtime_status_ptr = if self.condition_context_active {
70                    Some(self.get_or_create_cond_error_global())
71                } else {
72                    status_ptr
73                };
74                self.generate_runtime_expr_ops(
75                    expr.ops(),
76                    pt_regs_ptr,
77                    Some(*result_size),
78                    runtime_status_ptr,
79                    None,
80                    module_hint,
81                )
82                .map(|value| value.value.into())
83            }
84            ghostscope_dwarf::PlannedValue::ImplicitBytes(bytes) => {
85                debug!("Generating implicit value: {} bytes", bytes.len());
86                let mut value: u64 = 0;
87                for (i, &byte) in bytes.iter().enumerate().take(8) {
88                    value |= (byte as u64) << (i * 8);
89                }
90                Ok(self.context.i64_type().const_int(value, false).into())
91            }
92            ghostscope_dwarf::PlannedValue::AddressValue { address, .. } => {
93                debug!("Generating address direct value for variable: {var_name}");
94                let runtime_status_ptr = if self.condition_context_active {
95                    Some(self.get_or_create_cond_error_global())
96                } else {
97                    status_ptr
98                };
99                self.resolve_planned_address(address, runtime_status_ptr, module_hint)
100                    .map(|address| address.value.into())
101            }
102        }
103    }
104
105    pub(crate) fn resolve_planned_address(
106        &mut self,
107        address: &PlannedAddress,
108        status_ptr: Option<PointerValue<'ctx>>,
109        module_hint: Option<&str>,
110    ) -> Result<RuntimeAddress<'ctx>> {
111        let pt_regs_ptr = self.get_pt_regs_parameter()?;
112
113        match address.origin {
114            AddressOrigin::LinkTime => {
115                let link_addr = address.constant_link_time_address().ok_or_else(|| {
116                    CodeGenError::DwarfError(
117                        "read plan marked address as link-time without a constant address"
118                            .to_string(),
119                    )
120                })?;
121                self.runtime_address_from_link_time_address(link_addr, status_ptr, module_hint)
122            }
123            AddressOrigin::LinkTimeBase => {
124                let (link_addr, tail_steps) =
125                    address.link_time_base_and_runtime_tail().ok_or_else(|| {
126                        CodeGenError::DwarfError(
127                            "read plan marked address as link-time-base without a base address"
128                                .to_string(),
129                        )
130                    })?;
131                let runtime_base = self.runtime_address_from_link_time_address(
132                    link_addr,
133                    status_ptr,
134                    module_hint,
135                )?;
136                let value = self.generate_runtime_expr_ops(
137                    tail_steps,
138                    pt_regs_ptr,
139                    None,
140                    status_ptr,
141                    Some(runtime_base),
142                    module_hint,
143                )?;
144                Ok(value)
145            }
146            AddressOrigin::RuntimeDerived | AddressOrigin::Unknown => {
147                self.planned_address_without_rebase(address, pt_regs_ptr, status_ptr, module_hint)
148            }
149        }
150    }
151
152    fn planned_address_without_rebase(
153        &mut self,
154        address: &PlannedAddress,
155        pt_regs_ptr: PointerValue<'ctx>,
156        status_ptr: Option<PointerValue<'ctx>>,
157        module_hint: Option<&str>,
158    ) -> Result<RuntimeAddress<'ctx>> {
159        match &address.kind {
160            PlannedAddressKind::Constant { address } => Ok(RuntimeAddress::available(
161                self.context.i64_type().const_int(*address, false),
162                self.context,
163            )),
164            PlannedAddressKind::RegisterOffset { dwarf_reg, offset } => {
165                let reg_val = self.load_register_value(*dwarf_reg, pt_regs_ptr)?;
166                if let BasicValueEnum::IntValue(reg_i) = reg_val {
167                    let value = if *offset != 0 {
168                        let ofs_val = self.context.i64_type().const_int(*offset as u64, true);
169                        self.builder
170                            .build_int_add(reg_i, ofs_val, "addr_with_offset")
171                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))
172                    } else {
173                        Ok(reg_i)
174                    }?;
175                    Ok(RuntimeAddress::available(value, self.context))
176                } else {
177                    Err(CodeGenError::RegisterMappingError(
178                        "Register value is not integer".to_string(),
179                    ))
180                }
181            }
182            PlannedAddressKind::RuntimeComputed { expr } => {
183                self.runtime_expr_to_unrebased_address(expr, pt_regs_ptr, status_ptr, module_hint)
184            }
185            PlannedAddressKind::FrameBaseRelative { .. } => Err(CodeGenError::NotImplemented(
186                "Frame-base-relative planned address requires resolved frame base".to_string(),
187            )),
188        }
189    }
190
191    fn runtime_expr_to_unrebased_address(
192        &mut self,
193        expr: &RuntimeComputedExpr,
194        pt_regs_ptr: PointerValue<'ctx>,
195        status_ptr: Option<PointerValue<'ctx>>,
196        module_hint: Option<&str>,
197    ) -> Result<RuntimeAddress<'ctx>> {
198        self.generate_runtime_expr_ops(expr.ops(), pt_regs_ptr, None, status_ptr, None, module_hint)
199    }
200
201    fn runtime_address_from_link_time_address(
202        &mut self,
203        link_addr: u64,
204        status_ptr: Option<PointerValue<'ctx>>,
205        module_hint: Option<&str>,
206    ) -> Result<RuntimeAddress<'ctx>> {
207        let ctx = self.get_compile_time_context()?;
208        let module_for_offsets = module_hint
209            .map(|s| s.to_string())
210            .unwrap_or_else(|| ctx.module_path.clone());
211        let st_code = self.section_code_for_address(&module_for_offsets, link_addr);
212        let cookie = self.cookie_for_module_or_fallback(&module_for_offsets);
213        let link_val = self.context.i64_type().const_int(link_addr, false);
214        let (rt_addr, found_flag) =
215            self.generate_runtime_address_from_offsets(link_val, st_code, cookie)?;
216        self.store_offsets_unavailable_status(status_ptr, found_flag)?;
217        Ok(RuntimeAddress::with_offsets_found(rt_addr, found_flag))
218    }
219
220    fn store_offsets_unavailable_status(
221        &self,
222        status_ptr: Option<PointerValue<'ctx>>,
223        found_flag: IntValue<'ctx>,
224    ) -> Result<()> {
225        let Some(sp) = status_ptr else {
226            return Ok(());
227        };
228
229        let is_miss = self
230            .builder
231            .build_int_compare(
232                inkwell::IntPredicate::EQ,
233                found_flag,
234                self.context.bool_type().const_zero(),
235                "is_off_miss",
236            )
237            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
238        let cur_status = self
239            .builder
240            .build_load(self.context.i8_type(), sp, "cur_status")
241            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
242        let is_ok = self
243            .builder
244            .build_int_compare(
245                inkwell::IntPredicate::EQ,
246                cur_status.into_int_value(),
247                self.context.i8_type().const_zero(),
248                "status_is_ok",
249            )
250            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
251        let should_store = self
252            .builder
253            .build_and(is_miss, is_ok, "store_offsets_unavail")
254            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
255        let new_status = self
256            .builder
257            .build_select(
258                should_store,
259                self.context
260                    .i8_type()
261                    .const_int(
262                        ghostscope_protocol::VariableStatus::OffsetsUnavailable as u64,
263                        false,
264                    )
265                    .into(),
266                cur_status,
267                "new_status",
268            )
269            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
270        self.builder
271            .build_store(sp, new_status)
272            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
273        Ok(())
274    }
275
276    /// Convert DWARF type size to MemoryAccessSize
277    pub(super) fn dwarf_type_to_memory_access_size(
278        &self,
279        dwarf_type: &TypeInfo,
280    ) -> MemoryAccessSize {
281        MemoryAccessSize::from_size(dwarf_type.size())
282    }
283
284    pub(super) fn sign_extend_memory_read_if_needed(
285        &self,
286        value: BasicValueEnum<'ctx>,
287        dwarf_type: &TypeInfo,
288        access_size: MemoryAccessSize,
289    ) -> Result<BasicValueEnum<'ctx>> {
290        if !ghostscope_dwarf::is_c_signed_integer_type(dwarf_type)
291            || matches!(access_size, MemoryAccessSize::U64)
292        {
293            return Ok(value);
294        }
295
296        let int_value = value.into_int_value();
297        let narrow_type = match access_size {
298            MemoryAccessSize::U8 => self.context.i8_type(),
299            MemoryAccessSize::U16 => self.context.i16_type(),
300            MemoryAccessSize::U32 => self.context.i32_type(),
301            MemoryAccessSize::U64 => unreachable!("U64 values do not need sign extension"),
302        };
303        let narrowed = self
304            .builder
305            .build_int_truncate(int_value, narrow_type, "signed_mem_trunc")
306            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
307        let extended = self
308            .builder
309            .build_int_s_extend(narrowed, self.context.i64_type(), "signed_mem_sext")
310            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
311        Ok(extended.into())
312    }
313
314    fn normalize_direct_integer_value_if_needed(
315        &mut self,
316        value: BasicValueEnum<'ctx>,
317        dwarf_type: Option<&TypeInfo>,
318    ) -> Result<BasicValueEnum<'ctx>> {
319        let Some(c_type) = dwarf_type.and_then(ghostscope_dwarf::c_integer_comparison_type) else {
320            return Ok(value);
321        };
322        let BasicValueEnum::IntValue(int_value) = value else {
323            return Ok(value);
324        };
325
326        let bit_width = c_type.size.saturating_mul(8).clamp(1, 64) as u32;
327        let current_width = int_value.get_type().get_bit_width();
328        let narrow_type = self.context.custom_width_int_type(bit_width);
329        let narrowed = if current_width > bit_width {
330            self.builder
331                .build_int_truncate(int_value, narrow_type, "direct_int_trunc")
332                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
333        } else if current_width < bit_width {
334            if c_type.is_unsigned {
335                self.builder
336                    .build_int_z_extend(int_value, narrow_type, "direct_int_zext_to_type")
337                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
338            } else {
339                self.builder
340                    .build_int_s_extend(int_value, narrow_type, "direct_int_sext_to_type")
341                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
342            }
343        } else {
344            int_value
345        };
346
347        if bit_width == 64 {
348            return Ok(narrowed.into());
349        }
350
351        let normalized = if c_type.is_unsigned {
352            self.builder
353                .build_int_z_extend(narrowed, self.context.i64_type(), "direct_int_zext")
354                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
355        } else {
356            self.builder
357                .build_int_s_extend(narrowed, self.context.i64_type(), "direct_int_sext")
358                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
359        };
360        Ok(normalized.into())
361    }
362
363    pub(super) fn variable_read_plan_to_materialization(
364        &self,
365        plan: VariableReadPlan,
366        pc_address: u64,
367    ) -> Result<VariableMaterializationPlan> {
368        let materialization = plan.materialization_plan(&self.compile_options.runtime_capabilities);
369        if !materialization.availability.is_available()
370            && materialization.availability != Availability::OptimizedOut
371        {
372            return Err(Self::dwarf_expression_unavailable_error(
373                &materialization.name,
374                &materialization.availability,
375                pc_address,
376            ));
377        }
378
379        if materialization.availability != Availability::OptimizedOut
380            && matches!(
381                materialization.materialization,
382                ghostscope_dwarf::VariableMaterialization::UserMemoryRead { .. }
383            )
384        {
385            materialization.dwarf_type.as_ref().ok_or_else(|| {
386                CodeGenError::DwarfError("Expression has no DWARF type information".to_string())
387            })?;
388        }
389
390        Ok(materialization)
391    }
392
393    pub fn variable_materialization_to_llvm_value(
394        &mut self,
395        materialization: &VariableMaterializationPlan,
396        pc_address: u64,
397        status_ptr: Option<PointerValue<'ctx>>,
398    ) -> Result<BasicValueEnum<'ctx>> {
399        match &materialization.materialization {
400            ghostscope_dwarf::VariableMaterialization::DirectValue { value } => {
401                let module_hint =
402                    Self::module_path_for_offsets(materialization.module_path.as_deref());
403                let value = self.planned_value_to_llvm_value(
404                    value,
405                    &materialization.name,
406                    status_ptr,
407                    module_hint.as_deref(),
408                )?;
409                self.normalize_direct_integer_value_if_needed(
410                    value,
411                    materialization.dwarf_type.as_ref(),
412                )
413            }
414            ghostscope_dwarf::VariableMaterialization::UserMemoryRead { address } => {
415                let dwarf_type = materialization.dwarf_type.as_ref().ok_or_else(|| {
416                    CodeGenError::DwarfError(
417                        "Expression has no DWARF type information".to_string(),
418                    )
419                })?;
420                let module_hint =
421                    Self::module_path_for_offsets(materialization.module_path.as_deref());
422                self.generate_memory_location_from_planned_address(
423                    address,
424                    dwarf_type,
425                    status_ptr,
426                    module_hint.as_deref(),
427                )
428            }
429            ghostscope_dwarf::VariableMaterialization::Unavailable { availability } => {
430                Err(Self::dwarf_expression_unavailable_error(
431                    &materialization.name,
432                    availability,
433                    pc_address,
434                ))
435            }
436            ghostscope_dwarf::VariableMaterialization::Composite { .. } => {
437                Err(CodeGenError::DwarfError(format!(
438                    "DWARF variable '{}' is split across pieces; piece reconstruction is not implemented",
439                    materialization.name
440                )))
441            }
442        }
443    }
444
445    pub(super) fn variable_read_plan_to_llvm_value(
446        &mut self,
447        plan: &VariableReadPlan,
448        pc_address: u64,
449        status_ptr: Option<PointerValue<'ctx>>,
450    ) -> Result<BasicValueEnum<'ctx>> {
451        let materialized = self.variable_read_plan_to_materialization(plan.clone(), pc_address)?;
452        self.variable_materialization_to_llvm_value(&materialized, pc_address, status_ptr)
453    }
454
455    pub(super) fn variable_read_plan_to_runtime_address(
456        &mut self,
457        plan: &VariableReadPlan,
458        pc_address: u64,
459        status_ptr: Option<PointerValue<'ctx>>,
460    ) -> Result<RuntimeAddress<'ctx>> {
461        let module_hint = Self::module_path_for_offsets(plan.module_path.as_deref());
462        match plan.lvalue_address_plan() {
463            LvalueAddressPlan::Address { address } => {
464                self.resolve_planned_address(&address, status_ptr, module_hint.as_deref())
465            }
466            LvalueAddressPlan::Unavailable { availability } => Err(
467                Self::dwarf_lvalue_address_unavailable_error(&plan.name, &availability, pc_address),
468            ),
469        }
470    }
471
472    fn generate_memory_location_from_planned_address(
473        &mut self,
474        address: &PlannedAddress,
475        dwarf_type: &TypeInfo,
476        status_ptr: Option<PointerValue<'ctx>>,
477        module_hint: Option<&str>,
478    ) -> Result<BasicValueEnum<'ctx>> {
479        let runtime_status_ptr = if self.condition_context_active {
480            Some(self.get_or_create_cond_error_global())
481        } else {
482            status_ptr
483        };
484        let addr = self.resolve_planned_address(address, runtime_status_ptr, module_hint)?;
485
486        if ghostscope_dwarf::is_c_aggregate_type(dwarf_type) {
487            let ptr_ty = self.context.ptr_type(inkwell::AddressSpace::default());
488            let as_ptr = self
489                .builder
490                .build_int_to_ptr(addr.value, ptr_ty, "aggregate_addr_as_ptr")
491                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
492            return Ok(as_ptr.into());
493        }
494
495        let access_size = self.dwarf_type_to_memory_access_size(dwarf_type);
496        let read_value = if self.condition_context_active {
497            self.generate_memory_read_with_status(addr, access_size)
498        } else {
499            self.generate_memory_read(addr, access_size, status_ptr)
500        }?;
501
502        if let Some(bitfield_value) =
503            self.extract_bitfield_memory_read_if_needed(read_value, dwarf_type)?
504        {
505            return Ok(bitfield_value);
506        }
507
508        self.sign_extend_memory_read_if_needed(read_value, dwarf_type, access_size)
509    }
510
511    fn extract_bitfield_memory_read_if_needed(
512        &self,
513        value: BasicValueEnum<'ctx>,
514        dwarf_type: &TypeInfo,
515    ) -> Result<Option<BasicValueEnum<'ctx>>> {
516        let TypeInfo::BitfieldType {
517            underlying_type,
518            bit_offset,
519            bit_size,
520        } = ghostscope_dwarf::strip_type_aliases(dwarf_type)
521        else {
522            return Ok(None);
523        };
524
525        let bit_size = u32::from(*bit_size).min(64);
526        if bit_size == 0 {
527            return Ok(Some(self.context.i64_type().const_zero().into()));
528        }
529
530        let int_value = value.into_int_value();
531        let current_width = int_value.get_type().get_bit_width();
532        let int64 = if current_width < 64 {
533            self.builder
534                .build_int_z_extend(int_value, self.context.i64_type(), "bitfield_raw_zext")
535                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
536        } else if current_width > 64 {
537            self.builder
538                .build_int_truncate(int_value, self.context.i64_type(), "bitfield_raw_trunc")
539                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
540        } else {
541            int_value
542        };
543
544        let bit_offset = u32::from(*bit_offset);
545        if bit_offset >= 64 {
546            return Ok(Some(self.context.i64_type().const_zero().into()));
547        }
548
549        let shifted = if bit_offset == 0 {
550            int64
551        } else {
552            self.builder
553                .build_right_shift(
554                    int64,
555                    self.context
556                        .i64_type()
557                        .const_int(u64::from(bit_offset), false),
558                    false,
559                    "bitfield_shift",
560                )
561                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
562        };
563
564        let masked = if bit_size == 64 {
565            shifted
566        } else {
567            let mask = (1u64 << bit_size) - 1;
568            self.builder
569                .build_and(
570                    shifted,
571                    self.context.i64_type().const_int(mask, false),
572                    "bitfield_mask",
573                )
574                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
575        };
576
577        if bit_size < 64 && ghostscope_dwarf::is_c_signed_integer_type(underlying_type) {
578            let sign_shift = 64 - bit_size;
579            let shifted_left = self
580                .builder
581                .build_left_shift(
582                    masked,
583                    self.context
584                        .i64_type()
585                        .const_int(u64::from(sign_shift), false),
586                    "bitfield_sign_shift_left",
587                )
588                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
589            let extended = self
590                .builder
591                .build_right_shift(
592                    shifted_left,
593                    self.context
594                        .i64_type()
595                        .const_int(u64::from(sign_shift), false),
596                    true,
597                    "bitfield_sign_extend",
598                )
599                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
600            return Ok(Some(extended.into()));
601        }
602
603        Ok(Some(masked.into()))
604    }
605
606    /// Execute a semantic runtime expression selected by DWARF read planning.
607    fn generate_runtime_expr_ops(
608        &mut self,
609        ops: &[PlanExprOp],
610        pt_regs_ptr: PointerValue<'ctx>,
611        _result_size: Option<MemoryAccessSize>,
612        status_ptr: Option<PointerValue<'ctx>>,
613        initial_top: Option<RuntimeAddress<'ctx>>,
614        module_hint: Option<&str>,
615    ) -> Result<RuntimeAddress<'ctx>> {
616        // Implement stack-based computation
617        let mut stack: Vec<RuntimeAddress<'ctx>> = Vec::new();
618        // Track a runtime null-pointer flag from dereference steps; when true, subsequent
619        // arithmetic will be masked to zero to avoid reads at small offsets from NULL.
620        let mut deref_null_flag: Option<inkwell::values::IntValue> = None;
621        if let Some(top) = initial_top {
622            stack.push(top);
623        }
624
625        for op in ops {
626            match op {
627                PlanExprOp::LoadRegister(dwarf_reg) => {
628                    let reg_value = self.load_register_value(*dwarf_reg, pt_regs_ptr)?;
629                    if let BasicValueEnum::IntValue(int_val) = reg_value {
630                        stack.push(RuntimeAddress::available(int_val, self.context));
631                    } else {
632                        return Err(CodeGenError::RegisterMappingError(format!(
633                            "Register {dwarf_reg} did not return integer value"
634                        )));
635                    }
636                }
637
638                PlanExprOp::PushConstant(value) => {
639                    let const_val = self.context.i64_type().const_int(*value as u64, true);
640                    stack.push(RuntimeAddress::available(const_val, self.context));
641                }
642
643                PlanExprOp::Add => {
644                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
645                        let sum_val = self
646                            .builder
647                            .build_int_add(a.value, b.value, "add")
648                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
649                        let guard = self
650                            .builder
651                            .build_and(a.offsets_found, b.offsets_found, "add_guard")
652                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
653                        if let Some(nf) = deref_null_flag {
654                            let masked_bv = self
655                                .builder
656                                .build_select::<inkwell::values::BasicValueEnum<'ctx>, _>(
657                                    nf,
658                                    self.context.i64_type().const_zero().into(),
659                                    sum_val.into(),
660                                    "add_masked",
661                                )
662                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
663                            stack.push(RuntimeAddress::with_offsets_found(
664                                masked_bv.into_int_value(),
665                                guard,
666                            ));
667                        } else {
668                            stack.push(RuntimeAddress::with_offsets_found(sum_val, guard));
669                        }
670                    } else {
671                        return Err(CodeGenError::LLVMError(
672                            "Stack underflow in Add".to_string(),
673                        ));
674                    }
675                }
676
677                PlanExprOp::Sub => {
678                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
679                        let result = self
680                            .builder
681                            .build_int_sub(a.value, b.value, "sub")
682                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
683                        let guard = self
684                            .builder
685                            .build_and(a.offsets_found, b.offsets_found, "sub_guard")
686                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
687                        stack.push(RuntimeAddress::with_offsets_found(result, guard));
688                    } else {
689                        return Err(CodeGenError::LLVMError(
690                            "Stack underflow in Sub".to_string(),
691                        ));
692                    }
693                }
694
695                PlanExprOp::Mul => {
696                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
697                        let result = self
698                            .builder
699                            .build_int_mul(a.value, b.value, "mul")
700                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
701                        let guard = self
702                            .builder
703                            .build_and(a.offsets_found, b.offsets_found, "mul_guard")
704                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
705                        stack.push(RuntimeAddress::with_offsets_found(result, guard));
706                    } else {
707                        return Err(CodeGenError::LLVMError(
708                            "Stack underflow in Mul".to_string(),
709                        ));
710                    }
711                }
712
713                PlanExprOp::Div => {
714                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
715                        let result = self.build_signed_int_div_via_udiv(a.value, b.value, "div")?;
716                        let guard = self
717                            .builder
718                            .build_and(a.offsets_found, b.offsets_found, "div_guard")
719                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
720                        stack.push(RuntimeAddress::with_offsets_found(result, guard));
721                    } else {
722                        return Err(CodeGenError::LLVMError(
723                            "Stack underflow in Div".to_string(),
724                        ));
725                    }
726                }
727
728                PlanExprOp::Mod => {
729                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
730                        let result = self.build_signed_int_rem_via_urem(a.value, b.value, "mod")?;
731                        let guard = self
732                            .builder
733                            .build_and(a.offsets_found, b.offsets_found, "mod_guard")
734                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
735                        stack.push(RuntimeAddress::with_offsets_found(result, guard));
736                    } else {
737                        return Err(CodeGenError::LLVMError(
738                            "Stack underflow in Mod".to_string(),
739                        ));
740                    }
741                }
742
743                PlanExprOp::And => {
744                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
745                        let result = self
746                            .builder
747                            .build_and(a.value, b.value, "and")
748                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
749                        let guard = self
750                            .builder
751                            .build_and(a.offsets_found, b.offsets_found, "and_guard")
752                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
753                        stack.push(RuntimeAddress::with_offsets_found(result, guard));
754                    } else {
755                        return Err(CodeGenError::LLVMError(
756                            "Stack underflow in BitwiseAnd".to_string(),
757                        ));
758                    }
759                }
760
761                PlanExprOp::Or => {
762                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
763                        let result = self
764                            .builder
765                            .build_or(a.value, b.value, "or")
766                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
767                        let guard = self
768                            .builder
769                            .build_and(a.offsets_found, b.offsets_found, "or_guard")
770                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
771                        stack.push(RuntimeAddress::with_offsets_found(result, guard));
772                    } else {
773                        return Err(CodeGenError::LLVMError(
774                            "Stack underflow in BitwiseOr".to_string(),
775                        ));
776                    }
777                }
778
779                PlanExprOp::Xor => {
780                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
781                        let result = self
782                            .builder
783                            .build_xor(a.value, b.value, "xor")
784                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
785                        let guard = self
786                            .builder
787                            .build_and(a.offsets_found, b.offsets_found, "xor_guard")
788                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
789                        stack.push(RuntimeAddress::with_offsets_found(result, guard));
790                    } else {
791                        return Err(CodeGenError::LLVMError(
792                            "Stack underflow in BitwiseXor".to_string(),
793                        ));
794                    }
795                }
796
797                PlanExprOp::Shl => {
798                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
799                        let result = self
800                            .builder
801                            .build_left_shift(a.value, b.value, "shl")
802                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
803                        let guard = self
804                            .builder
805                            .build_and(a.offsets_found, b.offsets_found, "shl_guard")
806                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
807                        stack.push(RuntimeAddress::with_offsets_found(result, guard));
808                    } else {
809                        return Err(CodeGenError::LLVMError(
810                            "Stack underflow in ShiftLeft".to_string(),
811                        ));
812                    }
813                }
814
815                PlanExprOp::Shr => {
816                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
817                        let result = self
818                            .builder
819                            .build_right_shift(a.value, b.value, false, "shr")
820                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
821                        let guard = self
822                            .builder
823                            .build_and(a.offsets_found, b.offsets_found, "shr_guard")
824                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
825                        stack.push(RuntimeAddress::with_offsets_found(result, guard));
826                    } else {
827                        return Err(CodeGenError::LLVMError(
828                            "Stack underflow in ShiftRight".to_string(),
829                        ));
830                    }
831                }
832
833                PlanExprOp::Shra => {
834                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
835                        let result = self
836                            .builder
837                            .build_right_shift(a.value, b.value, true, "shra")
838                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
839                        let guard = self
840                            .builder
841                            .build_and(a.offsets_found, b.offsets_found, "shra_guard")
842                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
843                        stack.push(RuntimeAddress::with_offsets_found(result, guard));
844                    } else {
845                        return Err(CodeGenError::LLVMError(
846                            "Stack underflow in ShiftRightArithmetic".to_string(),
847                        ));
848                    }
849                }
850
851                PlanExprOp::Not => {
852                    if let Some(a) = stack.pop() {
853                        let result = self
854                            .builder
855                            .build_not(a.value, "not")
856                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
857                        stack.push(a.with_value(result));
858                    } else {
859                        return Err(CodeGenError::LLVMError(
860                            "Stack underflow in Not".to_string(),
861                        ));
862                    }
863                }
864
865                PlanExprOp::Neg => {
866                    if let Some(a) = stack.pop() {
867                        let result = self
868                            .builder
869                            .build_int_sub(self.context.i64_type().const_zero(), a.value, "neg")
870                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
871                        stack.push(a.with_value(result));
872                    } else {
873                        return Err(CodeGenError::LLVMError(
874                            "Stack underflow in Neg".to_string(),
875                        ));
876                    }
877                }
878
879                PlanExprOp::Abs => {
880                    if let Some(a) = stack.pop() {
881                        let zero = self.context.i64_type().const_zero();
882                        let is_neg = self
883                            .builder
884                            .build_int_compare(inkwell::IntPredicate::SLT, a.value, zero, "abs_neg")
885                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
886                        let negated = self
887                            .builder
888                            .build_int_sub(zero, a.value, "abs_negated")
889                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
890                        let result = self
891                            .builder
892                            .build_select::<BasicValueEnum<'ctx>, _>(
893                                is_neg,
894                                negated.into(),
895                                a.value.into(),
896                                "abs",
897                            )
898                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
899                            .into_int_value();
900                        stack.push(a.with_value(result));
901                    } else {
902                        return Err(CodeGenError::LLVMError(
903                            "Stack underflow in Abs".to_string(),
904                        ));
905                    }
906                }
907
908                PlanExprOp::Eq
909                | PlanExprOp::Ne
910                | PlanExprOp::Lt
911                | PlanExprOp::Le
912                | PlanExprOp::Gt
913                | PlanExprOp::Ge => {
914                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
915                        let predicate = match op {
916                            PlanExprOp::Eq => inkwell::IntPredicate::EQ,
917                            PlanExprOp::Ne => inkwell::IntPredicate::NE,
918                            PlanExprOp::Lt => inkwell::IntPredicate::SLT,
919                            PlanExprOp::Le => inkwell::IntPredicate::SLE,
920                            PlanExprOp::Gt => inkwell::IntPredicate::SGT,
921                            PlanExprOp::Ge => inkwell::IntPredicate::SGE,
922                            _ => unreachable!(),
923                        };
924                        let cmp = self
925                            .builder
926                            .build_int_compare(predicate, a.value, b.value, "cmp")
927                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
928                        let result = self
929                            .builder
930                            .build_int_z_extend(cmp, self.context.i64_type(), "cmp_i64")
931                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
932                        let guard = self
933                            .builder
934                            .build_and(a.offsets_found, b.offsets_found, "cmp_guard")
935                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
936                        stack.push(RuntimeAddress::with_offsets_found(result, guard));
937                    } else {
938                        return Err(CodeGenError::LLVMError(
939                            "Stack underflow in comparison".to_string(),
940                        ));
941                    }
942                }
943
944                PlanExprOp::Dereference { size } => {
945                    if let Some(addr) = stack.pop() {
946                        // Null guard: if addr == 0, set NullDeref (if status_ptr provided and current is Ok)
947                        let zero64 = self.context.i64_type().const_zero();
948                        let is_null = self
949                            .builder
950                            .build_int_compare(
951                                inkwell::IntPredicate::EQ,
952                                addr.value,
953                                zero64,
954                                "is_null_deref",
955                            )
956                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
957
958                        let cur_fn = self.current_function("generate dereference runtime check")?;
959                        let null_bb = self.context.append_basic_block(cur_fn, "deref_null");
960                        let read_bb = self.context.append_basic_block(cur_fn, "deref_read");
961                        let cont_bb = self.context.append_basic_block(cur_fn, "deref_cont");
962                        self.builder
963                            .build_conditional_branch(is_null, null_bb, read_bb)
964                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
965
966                        // Null path: optionally set status=NullDeref if currently Ok, branch to cont
967                        self.builder.position_at_end(null_bb);
968                        let null_val = self.context.i64_type().const_zero();
969                        if let Some(sp) = status_ptr {
970                            let cur_status = self
971                                .builder
972                                .build_load(self.context.i8_type(), sp, "cur_status")
973                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
974                                .into_int_value();
975                            let is_ok = self
976                                .builder
977                                .build_int_compare(
978                                    inkwell::IntPredicate::EQ,
979                                    cur_status,
980                                    self.context.i8_type().const_zero(),
981                                    "status_is_ok",
982                                )
983                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
984                            let then_val = self.context.i8_type().const_int(
985                                ghostscope_protocol::VariableStatus::NullDeref as u64,
986                                false,
987                            );
988                            let new_status_bv = self
989                                .builder
990                                .build_select::<inkwell::values::BasicValueEnum<'ctx>, _>(
991                                    is_ok,
992                                    then_val.into(),
993                                    cur_status.into(),
994                                    "new_status",
995                                )
996                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
997                            self.builder
998                                .build_store(sp, new_status_bv)
999                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1000                        }
1001                        self.builder
1002                            .build_unconditional_branch(cont_bb)
1003                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1004
1005                        // Read path: load pointer-sized value into tmp then branch to cont
1006                        self.builder.position_at_end(read_bb);
1007                        let access_size = *size;
1008                        let loaded_bv = if self.condition_context_active {
1009                            self.generate_memory_read_with_status(addr, access_size)?
1010                        } else {
1011                            self.generate_memory_read(addr, access_size, status_ptr)?
1012                        };
1013                        let loaded_int = if let BasicValueEnum::IntValue(int_val) = loaded_bv {
1014                            int_val
1015                        } else {
1016                            return Err(CodeGenError::LLVMError(
1017                                "Memory load did not return integer".to_string(),
1018                            ));
1019                        };
1020                        let value_block = self.builder.get_insert_block().ok_or_else(|| {
1021                            CodeGenError::LLVMError(
1022                                "No insertion block after dereference read".to_string(),
1023                            )
1024                        })?;
1025                        self.builder
1026                            .build_unconditional_branch(cont_bb)
1027                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1028
1029                        // Continue at cont: create PHI to merge null/read values, push once
1030                        self.builder.position_at_end(cont_bb);
1031                        let phi = self
1032                            .builder
1033                            .build_phi(self.context.i64_type(), "deref_phi")
1034                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1035                        phi.add_incoming(&[(&null_val, null_bb), (&loaded_int, value_block)]);
1036                        let merged = phi.as_basic_value().into_int_value();
1037                        // Update null flag based on loaded pointer value being zero
1038                        let is_zero_ptr = self
1039                            .builder
1040                            .build_int_compare(
1041                                inkwell::IntPredicate::EQ,
1042                                merged,
1043                                self.context.i64_type().const_zero(),
1044                                "is_zero_ptr",
1045                            )
1046                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1047                        deref_null_flag = Some(match deref_null_flag {
1048                            Some(prev) => self
1049                                .builder
1050                                .build_or(prev, is_zero_ptr, "null_or")
1051                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?,
1052                            None => is_zero_ptr,
1053                        });
1054                        if let (Some(sp), Some(nf)) = (status_ptr, deref_null_flag) {
1055                            // Only store NullDeref if currently OK and nf is true
1056                            let cur_status = self
1057                                .builder
1058                                .build_load(self.context.i8_type(), sp, "cur_status")
1059                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
1060                                .into_int_value();
1061                            let is_ok = self
1062                                .builder
1063                                .build_int_compare(
1064                                    inkwell::IntPredicate::EQ,
1065                                    cur_status,
1066                                    self.context.i8_type().const_zero(),
1067                                    "status_is_ok2",
1068                                )
1069                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1070                            let should_store = self
1071                                .builder
1072                                .build_and(is_ok, nf, "store_null_deref_from_ptr")
1073                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1074                            let then_val = self.context.i8_type().const_int(
1075                                ghostscope_protocol::VariableStatus::NullDeref as u64,
1076                                false,
1077                            );
1078                            let new_status_bv = self
1079                                .builder
1080                                .build_select::<inkwell::values::BasicValueEnum<'ctx>, _>(
1081                                    should_store,
1082                                    then_val.into(),
1083                                    cur_status.into(),
1084                                    "new_status2",
1085                                )
1086                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1087                            self.builder
1088                                .build_store(sp, new_status_bv)
1089                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1090                        }
1091                        stack.push(RuntimeAddress::with_offsets_found(
1092                            merged,
1093                            addr.offsets_found,
1094                        ));
1095                    } else {
1096                        return Err(CodeGenError::LLVMError(
1097                            "Stack underflow in LoadMemory".to_string(),
1098                        ));
1099                    }
1100                }
1101
1102                PlanExprOp::FormTlsAddress => {
1103                    if let Some(tls_offset) = stack.pop() {
1104                        let tls_address =
1105                            self.generate_static_tls_address(tls_offset, module_hint)?;
1106                        stack.push(tls_address);
1107                    } else {
1108                        return Err(CodeGenError::LLVMError(
1109                            "Stack underflow in FormTlsAddress".to_string(),
1110                        ));
1111                    }
1112                }
1113
1114                PlanExprOp::EntryValueLookup {
1115                    caller_pc_steps,
1116                    cases,
1117                } => {
1118                    let value = self.generate_entry_value_lookup(
1119                        caller_pc_steps,
1120                        cases,
1121                        pt_regs_ptr,
1122                        _result_size,
1123                        status_ptr,
1124                        module_hint,
1125                    )?;
1126                    stack.push(RuntimeAddress::available(value, self.context));
1127                }
1128
1129                // Add catch-all for unimplemented operations
1130                _ => {
1131                    warn!("Unimplemented runtime expression op: {:?}", op);
1132                    return Err(CodeGenError::NotImplemented(format!(
1133                        "runtime expression op {op:?} not yet implemented"
1134                    )));
1135                }
1136            }
1137        }
1138
1139        if stack.len() == 1 {
1140            let value = stack.pop().ok_or_else(|| {
1141                CodeGenError::LLVMError("Stack underflow after runtime computation".to_string())
1142            })?;
1143            Ok(value)
1144        } else {
1145            Err(CodeGenError::LLVMError(format!(
1146                "Invalid stack state after computation: {} elements remaining",
1147                stack.len()
1148            )))
1149        }
1150    }
1151
1152    fn generate_entry_value_lookup(
1153        &mut self,
1154        caller_pc_ops: &[PlanExprOp],
1155        cases: &[EntryValueCase],
1156        pt_regs_ptr: PointerValue<'ctx>,
1157        result_size: Option<MemoryAccessSize>,
1158        status_ptr: Option<PointerValue<'ctx>>,
1159        module_hint: Option<&str>,
1160    ) -> Result<IntValue<'ctx>> {
1161        if cases.is_empty() {
1162            return Err(CodeGenError::LLVMError(
1163                "EntryValueLookup requires at least one case".to_string(),
1164            ));
1165        }
1166
1167        let caller_pc = self
1168            .generate_runtime_expr_ops(
1169                caller_pc_ops,
1170                pt_regs_ptr,
1171                Some(MemoryAccessSize::U64),
1172                status_ptr,
1173                None,
1174                module_hint,
1175            )?
1176            .value;
1177
1178        let current_block = self.builder.get_insert_block().ok_or_else(|| {
1179            CodeGenError::LLVMError("No insertion block for EntryValueLookup".to_string())
1180        })?;
1181        let current_fn = current_block.get_parent().ok_or_else(|| {
1182            CodeGenError::LLVMError("No parent function for EntryValueLookup".to_string())
1183        })?;
1184        let merge_bb = self
1185            .context
1186            .append_basic_block(current_fn, "entry_value_merge");
1187        let default_bb = self
1188            .context
1189            .append_basic_block(current_fn, "entry_value_default");
1190
1191        let module_for_offsets = {
1192            let ctx = self.get_compile_time_context()?;
1193            module_hint
1194                .map(|module| module.to_string())
1195                .unwrap_or_else(|| ctx.module_path.clone())
1196        };
1197        let module_cookie = self.cookie_for_module_or_fallback(&module_for_offsets);
1198        let mut incoming_values = Vec::with_capacity(cases.len() + 1);
1199        let mut any_missing_offsets = None;
1200
1201        for (index, case) in cases.iter().enumerate() {
1202            let st_code = self.section_code_for_address(&module_for_offsets, case.caller_return_pc);
1203            let link_pc = self
1204                .context
1205                .i64_type()
1206                .const_int(case.caller_return_pc, false);
1207            let (runtime_return_pc, found_flag) =
1208                self.generate_runtime_address_from_offsets(link_pc, st_code, module_cookie)?;
1209            let missing_offsets = self
1210                .builder
1211                .build_not(found_flag, &format!("entry_value_missing_{index}"))
1212                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1213            any_missing_offsets = Some(match any_missing_offsets {
1214                Some(prev) => self
1215                    .builder
1216                    .build_or(
1217                        prev,
1218                        missing_offsets,
1219                        &format!("entry_value_missing_or_{index}"),
1220                    )
1221                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?,
1222                None => missing_offsets,
1223            });
1224
1225            let is_match = self
1226                .builder
1227                .build_int_compare(
1228                    inkwell::IntPredicate::EQ,
1229                    caller_pc,
1230                    runtime_return_pc,
1231                    &format!("entry_value_match_{index}"),
1232                )
1233                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1234            let is_match = self
1235                .builder
1236                .build_and(
1237                    is_match,
1238                    found_flag,
1239                    &format!("entry_value_match_ready_{index}"),
1240                )
1241                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1242            let case_bb = self
1243                .context
1244                .append_basic_block(current_fn, &format!("entry_value_case_{index}"));
1245            let next_bb = if index + 1 == cases.len() {
1246                default_bb
1247            } else {
1248                self.context
1249                    .append_basic_block(current_fn, &format!("entry_value_check_{}", index + 1))
1250            };
1251            self.builder
1252                .build_conditional_branch(is_match, case_bb, next_bb)
1253                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1254
1255            self.builder.position_at_end(case_bb);
1256            let case_value = self
1257                .generate_runtime_expr_ops(
1258                    &case.value_steps,
1259                    pt_regs_ptr,
1260                    result_size,
1261                    status_ptr,
1262                    None,
1263                    module_hint,
1264                )?
1265                .value;
1266            let case_value_block = self.builder.get_insert_block().ok_or_else(|| {
1267                CodeGenError::LLVMError(
1268                    "No insertion block after EntryValueLookup case".to_string(),
1269                )
1270            })?;
1271            self.builder
1272                .build_unconditional_branch(merge_bb)
1273                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1274            incoming_values.push((case_value, case_value_block));
1275
1276            self.builder.position_at_end(next_bb);
1277        }
1278
1279        self.builder.position_at_end(default_bb);
1280        if let Some(sp) = status_ptr {
1281            self.store_variable_read_status(
1282                sp,
1283                self.context.bool_type().const_int(1, false),
1284                any_missing_offsets.unwrap_or_else(|| self.context.bool_type().const_zero()),
1285                "entry_value_default",
1286            )?;
1287        }
1288        let default_value = self.context.i64_type().const_zero();
1289        let default_value_block = self.builder.get_insert_block().ok_or_else(|| {
1290            CodeGenError::LLVMError("No default block for EntryValueLookup".to_string())
1291        })?;
1292        self.builder
1293            .build_unconditional_branch(merge_bb)
1294            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1295        incoming_values.push((default_value, default_value_block));
1296
1297        self.builder.position_at_end(merge_bb);
1298        let phi = self
1299            .builder
1300            .build_phi(self.context.i64_type(), "entry_value_phi")
1301            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1302        let incoming_refs: Vec<(&dyn inkwell::values::BasicValue<'ctx>, _)> = incoming_values
1303            .iter()
1304            .map(|(value, block)| (value as &dyn inkwell::values::BasicValue<'ctx>, *block))
1305            .collect();
1306        phi.add_incoming(&incoming_refs);
1307
1308        Ok(phi.as_basic_value().into_int_value())
1309    }
1310
1311    fn expand_dwarf_aliases(&self, expr: &crate::script::Expr) -> Result<crate::script::Expr> {
1312        fn expand_aliases(
1313            ctx: &crate::ebpf::context::EbpfContext<'_, '_>,
1314            e: &crate::script::Expr,
1315            visited: &mut std::collections::HashSet<String>,
1316            depth: usize,
1317        ) -> std::result::Result<crate::script::Expr, super::context::CodeGenError> {
1318            use crate::script::Expr as E;
1319            const MAX_DEPTH: usize = 64;
1320            if depth > MAX_DEPTH {
1321                return Err(super::context::CodeGenError::TypeError(
1322                    "alias expansion depth exceeded (cycle?)".to_string(),
1323                ));
1324            }
1325            Ok(match e {
1326                E::Variable(name) => {
1327                    if ctx.alias_variable_exists(name) {
1328                        if !visited.insert(name.clone()) {
1329                            return Err(super::context::CodeGenError::TypeError(format!(
1330                                "alias cycle detected for '{name}'"
1331                            )));
1332                        }
1333                        if let Some(t) = ctx.get_alias_variable(name) {
1334                            let res = expand_aliases(ctx, &t, visited, depth + 1)?;
1335                            visited.remove(name);
1336                            res
1337                        } else {
1338                            e.clone()
1339                        }
1340                    } else {
1341                        e.clone()
1342                    }
1343                }
1344                E::MemberAccess(obj, field) => {
1345                    let base = expand_aliases(ctx, obj, visited, depth + 1)?;
1346                    E::MemberAccess(Box::new(base), field.clone())
1347                }
1348                E::ArrayAccess(arr, idx) => {
1349                    let base = expand_aliases(ctx, arr, visited, depth + 1)?;
1350                    let idx2 = expand_aliases(ctx, idx, visited, depth + 1)?;
1351                    E::ArrayAccess(Box::new(base), Box::new(idx2))
1352                }
1353                E::PointerDeref(inner) => {
1354                    let in2 = expand_aliases(ctx, inner, visited, depth + 1)?;
1355                    E::PointerDeref(Box::new(in2))
1356                }
1357                E::AddressOf(inner) => {
1358                    let in2 = expand_aliases(ctx, inner, visited, depth + 1)?;
1359                    E::AddressOf(Box::new(in2))
1360                }
1361                E::Cast { expr, target_type } => {
1362                    let expr = expand_aliases(ctx, expr, visited, depth + 1)?;
1363                    E::Cast {
1364                        expr: Box::new(expr),
1365                        target_type: target_type.clone(),
1366                    }
1367                }
1368                E::ChainAccess(chain) => {
1369                    if chain.is_empty() {
1370                        return Ok(e.clone());
1371                    }
1372                    let head = &chain[0];
1373                    if ctx.alias_variable_exists(head) {
1374                        if !visited.insert(head.clone()) {
1375                            return Err(super::context::CodeGenError::TypeError(format!(
1376                                "alias cycle detected for '{head}'"
1377                            )));
1378                        }
1379                        if let Some(alias_expr) = ctx.get_alias_variable(head) {
1380                            let mut acc = expand_aliases(ctx, &alias_expr, visited, depth + 1)?;
1381                            for seg in &chain[1..] {
1382                                acc = E::MemberAccess(Box::new(acc), seg.clone());
1383                            }
1384                            visited.remove(head);
1385                            acc
1386                        } else {
1387                            e.clone()
1388                        }
1389                    } else {
1390                        e.clone()
1391                    }
1392                }
1393                E::BuiltinCall { name, args } => E::BuiltinCall {
1394                    name: name.clone(),
1395                    args: args
1396                        .iter()
1397                        .map(|a| expand_aliases(ctx, a, visited, depth + 1))
1398                        .collect::<std::result::Result<Vec<_>, _>>()?,
1399                },
1400                E::UnaryNot(inner) => {
1401                    E::UnaryNot(Box::new(expand_aliases(ctx, inner, visited, depth + 1)?))
1402                }
1403                E::UnaryBitNot(inner) => {
1404                    E::UnaryBitNot(Box::new(expand_aliases(ctx, inner, visited, depth + 1)?))
1405                }
1406                E::BinaryOp { left, op, right } => E::BinaryOp {
1407                    left: Box::new(expand_aliases(ctx, left, visited, depth + 1)?),
1408                    op: op.clone(),
1409                    right: Box::new(expand_aliases(ctx, right, visited, depth + 1)?),
1410                },
1411                _ => e.clone(),
1412            })
1413        }
1414
1415        let mut visited = std::collections::HashSet::new();
1416        expand_aliases(self, expr, &mut visited, 0)
1417    }
1418
1419    pub(super) fn query_dwarf_for_complex_expr_plan(
1420        &mut self,
1421        expr: &crate::script::Expr,
1422    ) -> Result<Option<VariableReadPlan>> {
1423        use crate::script::Expr;
1424
1425        let expanded = self.expand_dwarf_aliases(expr)?;
1426        match &expanded {
1427            Expr::Variable(var_name) => self.query_dwarf_for_variable_plan(var_name),
1428            Expr::MemberAccess(_, _)
1429            | Expr::ArrayAccess(_, _)
1430            | Expr::ChainAccess(_)
1431            | Expr::PointerDeref(_) => {
1432                if let Some((base, access_path)) = Self::access_path_from_expr(&expanded)? {
1433                    self.query_dwarf_for_pc_access_plan(&base, &access_path)
1434                } else {
1435                    Ok(None)
1436                }
1437            }
1438            _ => Ok(None),
1439        }
1440    }
1441
1442    /// Query DWARF for complex expression (supports member access, array access, etc.)
1443    pub fn query_dwarf_for_complex_expr(
1444        &mut self,
1445        expr: &crate::script::Expr,
1446    ) -> Result<Option<VariableReadPlan>> {
1447        self.query_dwarf_for_complex_expr_plan(expr)
1448    }
1449
1450    /// Query DWARF for a PC-sensitive local variable read plan.
1451    fn query_dwarf_for_variable_plan(
1452        &mut self,
1453        var_name: &str,
1454    ) -> Result<Option<VariableReadPlan>> {
1455        let context = self.get_compile_time_context()?;
1456        let pc_address = context.pc_address;
1457        let module_path = context.module_path.clone();
1458
1459        debug!(
1460            "Querying DWARF variable plan for '{}' at PC 0x{:x} in module '{}'",
1461            var_name, pc_address, module_path
1462        );
1463
1464        let analyzer = self
1465            .process_analyzer
1466            .ok_or_else(|| CodeGenError::DwarfError("No DWARF analyzer available".to_string()))?;
1467        let prefer_module = std::path::PathBuf::from(module_path);
1468        let module_address =
1469            ghostscope_dwarf::ModuleAddress::new(prefer_module.clone(), pc_address);
1470
1471        let pc_plan = match analyzer.resolve_pc(&module_address) {
1472            Ok(pc_context) => match analyzer.plan_variable_by_name(&pc_context, var_name) {
1473                Ok(Some(plan)) => {
1474                    debug!("Found DWARF variable '{}' via PC variable plan", var_name);
1475                    Some(plan)
1476                }
1477                Ok(None) => {
1478                    debug!(
1479                        "Variable '{}' not found in PC variable plan; trying global read plan",
1480                        var_name
1481                    );
1482                    None
1483                }
1484                Err(err) => {
1485                    let message = err.to_string();
1486                    if message.starts_with("Ambiguous variable")
1487                        || message.starts_with("Unavailable variable")
1488                    {
1489                        return Err(CodeGenError::DwarfError(message));
1490                    }
1491                    debug!(
1492                        "PC variable plan lookup error for '{}': {message}; trying global read plan",
1493                        var_name
1494                    );
1495                    None
1496                }
1497            },
1498            Err(err) => {
1499                debug!(
1500                    "PC context resolution failed for '{}': {err}; trying global read plan",
1501                    var_name
1502                );
1503                None
1504            }
1505        };
1506
1507        if pc_plan.is_some() {
1508            return Ok(pc_plan);
1509        }
1510
1511        if let Some((_global_module, plan)) = analyzer
1512            .plan_global_access_read_plan(&prefer_module, var_name, &VariableAccessPath::default())
1513            .map_err(|err| CodeGenError::DwarfError(err.to_string()))?
1514        {
1515            debug!("Found DWARF global '{}' via variable read plan", var_name);
1516            return Ok(Some(plan));
1517        }
1518
1519        debug!("Variable '{var_name}' not found in read plans");
1520        Ok(None)
1521    }
1522
1523    /// Query DWARF for variable information
1524    pub fn query_dwarf_for_variable(&mut self, var_name: &str) -> Result<Option<VariableReadPlan>> {
1525        let context = self.get_compile_time_context()?;
1526        let pc_address = context.pc_address;
1527
1528        debug!(
1529            "Querying DWARF for variable '{}' at PC 0x{:x} in module '{}'",
1530            var_name, pc_address, context.module_path
1531        );
1532
1533        self.query_dwarf_for_variable_plan(var_name)
1534    }
1535
1536    fn query_dwarf_for_pc_access_plan(
1537        &mut self,
1538        base_name: &str,
1539        access_path: &VariableAccessPath,
1540    ) -> Result<Option<VariableReadPlan>> {
1541        if access_path.segments.is_empty() {
1542            return self.query_dwarf_for_variable_plan(base_name);
1543        }
1544
1545        let path_text = Self::access_path_to_string(base_name, access_path);
1546        let context = self.get_compile_time_context()?;
1547        let pc_address = context.pc_address;
1548        let module_path = context.module_path.clone();
1549        let prefer_module = std::path::PathBuf::from(module_path.clone());
1550        let analyzer = self
1551            .process_analyzer
1552            .ok_or_else(|| CodeGenError::DwarfError("No DWARF analyzer available".to_string()))?;
1553        let module_address =
1554            ghostscope_dwarf::ModuleAddress::new(prefer_module.clone(), pc_address);
1555
1556        match analyzer.resolve_pc(&module_address) {
1557            Ok(pc_context) => {
1558                match analyzer.plan_variable_access_by_name(&pc_context, base_name, access_path) {
1559                    Ok(Some(plan)) => {
1560                        debug!("Found DWARF access '{path_text}' via PC variable access plan");
1561                        return Ok(Some(plan));
1562                    }
1563                    Ok(None) => {}
1564                    Err(err) => {
1565                        let message = err.to_string();
1566                        debug!(
1567                            "PC variable access plan lookup failed for '{path_text}': {message}"
1568                        );
1569                        return Err(CodeGenError::DwarfError(message));
1570                    }
1571                }
1572            }
1573            Err(err) => {
1574                debug!(
1575                    "PC context resolution failed for '{path_text}': {err}; trying global read plan"
1576                );
1577            }
1578        }
1579
1580        if let Some((_module_path, plan)) = analyzer
1581            .plan_global_access_read_plan(&prefer_module, base_name, access_path)
1582            .map_err(|err| CodeGenError::DwarfError(err.to_string()))?
1583        {
1584            debug!("Found DWARF global access '{path_text}' via variable read plan");
1585            return Ok(Some(plan));
1586        }
1587
1588        Ok(None)
1589    }
1590
1591    fn access_path_to_string(base_name: &str, access_path: &VariableAccessPath) -> String {
1592        let mut out = base_name.to_string();
1593        for segment in &access_path.segments {
1594            match segment {
1595                VariableAccessSegment::Field(field) => {
1596                    out.push('.');
1597                    out.push_str(field);
1598                }
1599                VariableAccessSegment::ArrayIndex(index) => {
1600                    out.push('[');
1601                    out.push_str(&index.to_string());
1602                    out.push(']');
1603                }
1604                VariableAccessSegment::Dereference => {
1605                    out.push_str(".*");
1606                }
1607            }
1608        }
1609        out
1610    }
1611
1612    fn access_path_from_expr(
1613        expr: &crate::script::Expr,
1614    ) -> Result<Option<(String, VariableAccessPath)>> {
1615        fn append_segments(
1616            expr: &crate::script::Expr,
1617            segments: &mut Vec<VariableAccessSegment>,
1618        ) -> Result<Option<String>> {
1619            match expr {
1620                crate::script::Expr::Variable(name) => Ok(Some(name.clone())),
1621                crate::script::Expr::ChainAccess(chain) => {
1622                    let Some(base) = chain.first() else {
1623                        return Ok(None);
1624                    };
1625                    segments.extend(chain[1..].iter().cloned().map(VariableAccessSegment::Field));
1626                    Ok(Some(base.clone()))
1627                }
1628                crate::script::Expr::MemberAccess(obj, field) => {
1629                    let Some(base) = append_segments(obj, segments)? else {
1630                        return Ok(None);
1631                    };
1632                    segments.push(VariableAccessSegment::Field(field.clone()));
1633                    Ok(Some(base))
1634                }
1635                crate::script::Expr::ArrayAccess(array, index) => {
1636                    let crate::script::Expr::Int(index) = index.as_ref() else {
1637                        return Err(CodeGenError::NotImplemented(
1638                            "Only literal integer array indices are supported (TODO)".to_string(),
1639                        ));
1640                    };
1641
1642                    if let Some((array_base, base_index)) =
1643                        EbpfContext::<'static, 'static>::pointer_arithmetic_parts(array)
1644                    {
1645                        let Some(base) = append_segments(array_base, segments)? else {
1646                            return Ok(None);
1647                        };
1648                        let index = base_index.checked_add(*index).ok_or_else(|| {
1649                            CodeGenError::TypeError(
1650                                "array index offset overflow after pointer arithmetic".to_string(),
1651                            )
1652                        })?;
1653                        segments.push(VariableAccessSegment::ArrayIndex(index));
1654                        return Ok(Some(base));
1655                    }
1656
1657                    let Some(base) = append_segments(array, segments)? else {
1658                        return Ok(None);
1659                    };
1660                    segments.push(VariableAccessSegment::ArrayIndex(*index));
1661                    Ok(Some(base))
1662                }
1663                crate::script::Expr::PointerDeref(inner) => {
1664                    if let Some((pointer_base, index)) =
1665                        EbpfContext::<'static, 'static>::pointer_arithmetic_parts(inner)
1666                    {
1667                        let Some(base) = append_segments(pointer_base, segments)? else {
1668                            return Ok(None);
1669                        };
1670                        segments.push(VariableAccessSegment::ArrayIndex(index));
1671                        return Ok(Some(base));
1672                    }
1673
1674                    let Some(base) = append_segments(inner, segments)? else {
1675                        return Ok(None);
1676                    };
1677                    segments.push(VariableAccessSegment::Dereference);
1678                    Ok(Some(base))
1679                }
1680                _ => Ok(None),
1681            }
1682        }
1683
1684        let mut segments = Vec::new();
1685        let Some(base) = append_segments(expr, &mut segments)? else {
1686            return Ok(None);
1687        };
1688        Ok(Some((base, VariableAccessPath::new(segments))))
1689    }
1690}
1691
1692#[cfg(test)]
1693mod tests {
1694    use super::*;
1695    use crate::script::BinaryOp;
1696    use crate::script::Expr;
1697    use ghostscope_dwarf::AddressExpr;
1698    use ghostscope_dwarf::PlanExprOp;
1699    use ghostscope_dwarf::Provenance;
1700    use ghostscope_dwarf::VariableLocation;
1701    use inkwell::context::Context as LlvmContext;
1702
1703    fn read_plan(
1704        name: &str,
1705        type_name: &str,
1706        dwarf_type: Option<TypeInfo>,
1707        location: VariableLocation,
1708        availability: Availability,
1709    ) -> VariableReadPlan {
1710        VariableReadPlan {
1711            name: name.to_string(),
1712            type_name: type_name.to_string(),
1713            access_path: VariableAccessPath::default(),
1714            module_path: None,
1715            dwarf_type,
1716            declaration: None,
1717            type_id: None,
1718            location,
1719            availability,
1720            scope_depth: 0,
1721            is_parameter: false,
1722            is_artificial: false,
1723            pc_range: None,
1724            inline_context: None,
1725            provenance: Provenance::DirectDie,
1726        }
1727    }
1728
1729    #[test]
1730    fn access_path_from_expr_flattens_member_array_member_paths() {
1731        let expr = Expr::MemberAccess(
1732            Box::new(Expr::ArrayAccess(
1733                Box::new(Expr::MemberAccess(
1734                    Box::new(Expr::Variable("request".to_string())),
1735                    "headers".to_string(),
1736                )),
1737                Box::new(Expr::Int(2)),
1738            )),
1739            "len".to_string(),
1740        );
1741
1742        let (base, path) = EbpfContext::<'static, 'static>::access_path_from_expr(&expr)
1743            .expect("access path should parse")
1744            .expect("expression should be flattenable");
1745
1746        assert_eq!(base, "request");
1747        assert_eq!(
1748            path.segments,
1749            vec![
1750                VariableAccessSegment::Field("headers".to_string()),
1751                VariableAccessSegment::ArrayIndex(2),
1752                VariableAccessSegment::Field("len".to_string()),
1753            ]
1754        );
1755        assert_eq!(
1756            EbpfContext::<'static, 'static>::access_path_to_string(&base, &path),
1757            "request.headers[2].len"
1758        );
1759    }
1760
1761    #[test]
1762    fn access_path_from_expr_rejects_dynamic_array_index() {
1763        let expr = Expr::ArrayAccess(
1764            Box::new(Expr::Variable("items".to_string())),
1765            Box::new(Expr::Variable("idx".to_string())),
1766        );
1767
1768        let err = EbpfContext::<'static, 'static>::access_path_from_expr(&expr)
1769            .expect_err("dynamic array index should be rejected");
1770
1771        assert!(matches!(err, CodeGenError::NotImplemented(_)));
1772        assert!(err.to_string().contains("literal integer array indices"));
1773    }
1774
1775    #[test]
1776    fn access_path_from_expr_folds_pointer_arithmetic_array_base() {
1777        let expr = Expr::ArrayAccess(
1778            Box::new(Expr::BinaryOp {
1779                left: Box::new(Expr::BinaryOp {
1780                    left: Box::new(Expr::Variable("numbers".to_string())),
1781                    op: BinaryOp::Add,
1782                    right: Box::new(Expr::Int(3)),
1783                }),
1784                op: BinaryOp::Subtract,
1785                right: Box::new(Expr::Int(1)),
1786            }),
1787            Box::new(Expr::Int(2)),
1788        );
1789
1790        let (base, path) = EbpfContext::<'static, 'static>::access_path_from_expr(&expr)
1791            .expect("access path should parse")
1792            .expect("expression should be flattenable");
1793
1794        assert_eq!(base, "numbers");
1795        assert_eq!(path.segments, vec![VariableAccessSegment::ArrayIndex(4)]);
1796        assert_eq!(
1797            EbpfContext::<'static, 'static>::access_path_to_string(&base, &path),
1798            "numbers[4]"
1799        );
1800    }
1801
1802    #[test]
1803    fn access_path_from_expr_folds_pointer_arithmetic_deref() {
1804        let expr = Expr::PointerDeref(Box::new(Expr::BinaryOp {
1805            left: Box::new(Expr::BinaryOp {
1806                left: Box::new(Expr::Variable("numbers".to_string())),
1807                op: BinaryOp::Add,
1808                right: Box::new(Expr::Int(3)),
1809            }),
1810            op: BinaryOp::Subtract,
1811            right: Box::new(Expr::Int(1)),
1812        }));
1813
1814        let (base, path) = EbpfContext::<'static, 'static>::access_path_from_expr(&expr)
1815            .expect("access path should parse")
1816            .expect("expression should be flattenable");
1817
1818        assert_eq!(base, "numbers");
1819        assert_eq!(path.segments, vec![VariableAccessSegment::ArrayIndex(2)]);
1820        assert_eq!(
1821            EbpfContext::<'static, 'static>::access_path_to_string(&base, &path),
1822            "numbers[2]"
1823        );
1824    }
1825
1826    #[test]
1827    fn access_path_from_expr_flattens_pointer_deref_segments() {
1828        let expr = Expr::MemberAccess(
1829            Box::new(Expr::PointerDeref(Box::new(Expr::MemberAccess(
1830                Box::new(Expr::Variable("request".to_string())),
1831                "current".to_string(),
1832            )))),
1833            "state".to_string(),
1834        );
1835
1836        let (base, path) = EbpfContext::<'static, 'static>::access_path_from_expr(&expr)
1837            .expect("access path should parse")
1838            .expect("expression should be flattenable");
1839
1840        assert_eq!(base, "request");
1841        assert_eq!(
1842            path.segments,
1843            vec![
1844                VariableAccessSegment::Field("current".to_string()),
1845                VariableAccessSegment::Dereference,
1846                VariableAccessSegment::Field("state".to_string()),
1847            ]
1848        );
1849        assert_eq!(
1850            EbpfContext::<'static, 'static>::access_path_to_string(&base, &path),
1851            "request.current.*.state"
1852        );
1853    }
1854
1855    #[test]
1856    fn aggregate_address_returns_pointer_for_struct_and_array() {
1857        let llctx = LlvmContext::create();
1858        let opts = crate::CompileOptions::default();
1859        let mut ctx = EbpfContext::new(&llctx, "agg_ptr", Some(0), &opts).expect("ctx");
1860        // Ensure we have a function/pt_regs to satisfy builders
1861        ctx.create_basic_ebpf_function("f").expect("fn");
1862        // Ensure the ASLR offsets map exists in the module for unified codegen path
1863        ctx.__test_ensure_proc_offsets_map().expect("map");
1864        // Allocate per-invocation pm_key on the stack
1865        ctx.__test_alloc_pm_key().expect("pm_key");
1866        // Provide a minimal compile-time context so address rebasing has a module path
1867        ctx.set_compile_time_context(0, "/nonexistent/module".to_string());
1868
1869        // Struct type
1870        let st = ghostscope_protocol::TypeInfo::StructType {
1871            name: "S".to_string(),
1872            size: 80,
1873            members: vec![],
1874        };
1875        let location = VariableLocation::Address(AddressExpr::constant(0x1000));
1876        let plan = read_plan(
1877            "S",
1878            "S",
1879            Some(st),
1880            location.clone(),
1881            Availability::Available,
1882        );
1883        let v = ctx
1884            .variable_read_plan_to_llvm_value(&plan, 0, None)
1885            .expect("eval");
1886        match v {
1887            BasicValueEnum::PointerValue(_) => {}
1888            other => panic!("expected PointerValue for struct, got {other:?}"),
1889        }
1890
1891        // Array type
1892        let arr = ghostscope_protocol::TypeInfo::ArrayType {
1893            element_type: Box::new(ghostscope_protocol::TypeInfo::BaseType {
1894                name: "int".to_string(),
1895                size: 4,
1896                encoding: ghostscope_dwarf::constants::DW_ATE_signed.0 as u16,
1897            }),
1898            element_count: Some(4),
1899            total_size: Some(16),
1900        };
1901        let plan = read_plan("A", "int[4]", Some(arr), location, Availability::Available);
1902        let v2 = ctx
1903            .variable_read_plan_to_llvm_value(&plan, 0, None)
1904            .expect("eval2");
1905        match v2 {
1906            BasicValueEnum::PointerValue(_) => {}
1907            other => panic!("expected PointerValue for array, got {other:?}"),
1908        }
1909    }
1910
1911    #[test]
1912    fn scalar_address_reads_value() {
1913        let llctx = LlvmContext::create();
1914        let opts = crate::CompileOptions::default();
1915        let mut ctx = EbpfContext::new(&llctx, "scalar_val", Some(0), &opts).expect("ctx");
1916        ctx.create_basic_ebpf_function("f").expect("fn");
1917        // Ensure the ASLR offsets map exists in the module for unified codegen path
1918        ctx.__test_ensure_proc_offsets_map().expect("map");
1919        // Allocate per-invocation pm_key on the stack
1920        ctx.__test_alloc_pm_key().expect("pm_key");
1921        // Provide a minimal compile-time context so address rebasing has a module path
1922        ctx.set_compile_time_context(0, "/nonexistent/module".to_string());
1923
1924        // Base int type
1925        let bt = ghostscope_protocol::TypeInfo::BaseType {
1926            name: "int".to_string(),
1927            size: 4,
1928            encoding: ghostscope_dwarf::constants::DW_ATE_signed.0 as u16,
1929        };
1930        let location = VariableLocation::Address(AddressExpr::constant(0x2000));
1931        let plan = read_plan("x", "int", Some(bt), location, Availability::Available);
1932        let v = ctx
1933            .variable_read_plan_to_llvm_value(&plan, 0, None)
1934            .expect("eval");
1935        match v {
1936            BasicValueEnum::IntValue(_) => {}
1937            other => panic!("expected IntValue for scalar, got {other:?}"),
1938        }
1939        assert!(
1940            ctx.module.get_global("_temp_read_buffer_4").is_none(),
1941            "scalar reads should use per-invocation scratch, not shared temp globals"
1942        );
1943    }
1944
1945    #[test]
1946    fn absolute_address_value_lowers_as_rebased_direct_value() {
1947        let llctx = LlvmContext::create();
1948        let opts = crate::CompileOptions::default();
1949        let mut ctx = EbpfContext::new(&llctx, "abs_addr_value", Some(0), &opts).expect("ctx");
1950        ctx.create_basic_ebpf_function("f").expect("fn");
1951        ctx.__test_ensure_proc_offsets_map().expect("map");
1952        ctx.__test_alloc_pm_key().expect("pm_key");
1953        ctx.set_compile_time_context(0, "/nonexistent/module".to_string());
1954
1955        let ptr_ty = ghostscope_protocol::TypeInfo::PointerType {
1956            target_type: Box::new(ghostscope_protocol::TypeInfo::BaseType {
1957                name: "int".to_string(),
1958                size: 4,
1959                encoding: ghostscope_dwarf::constants::DW_ATE_signed.0 as u16,
1960            }),
1961            size: 8,
1962        };
1963        let location = VariableLocation::AbsoluteAddressValue(AddressExpr::constant(0x2000));
1964        let plan = read_plan(
1965            "ptr",
1966            "int*",
1967            Some(ptr_ty),
1968            location,
1969            Availability::Available,
1970        );
1971
1972        let value = ctx
1973            .variable_read_plan_to_llvm_value(&plan, 0, None)
1974            .expect("absolute address value should lower");
1975        assert!(matches!(value, BasicValueEnum::IntValue(_)));
1976    }
1977
1978    #[test]
1979    fn runtime_computed_div_and_mod_lower_without_signed_ir_ops() {
1980        let llctx = LlvmContext::create();
1981        let opts = crate::CompileOptions::default();
1982        let mut ctx = EbpfContext::new(&llctx, "runtime_div_mod", Some(0), &opts).expect("ctx");
1983        ctx.create_basic_ebpf_function("f").expect("fn");
1984
1985        let ty = ghostscope_protocol::TypeInfo::BaseType {
1986            name: "long".to_string(),
1987            size: 8,
1988            encoding: ghostscope_dwarf::constants::DW_ATE_signed.0 as u16,
1989        };
1990        let div_plan = read_plan(
1991            "div_value",
1992            "long",
1993            Some(ty.clone()),
1994            VariableLocation::ComputedValue(vec![
1995                PlanExprOp::LoadRegister(0),
1996                PlanExprOp::PushConstant(-3),
1997                PlanExprOp::Div,
1998            ]),
1999            Availability::Available,
2000        );
2001        let mod_plan = read_plan(
2002            "mod_value",
2003            "long",
2004            Some(ty),
2005            VariableLocation::ComputedValue(vec![
2006                PlanExprOp::LoadRegister(0),
2007                PlanExprOp::PushConstant(-3),
2008                PlanExprOp::Mod,
2009            ]),
2010            Availability::Available,
2011        );
2012
2013        let div_value = ctx
2014            .variable_read_plan_to_llvm_value(&div_plan, 0, None)
2015            .expect("DW_OP_div-style plan should lower");
2016        let mod_value = ctx
2017            .variable_read_plan_to_llvm_value(&mod_plan, 0, None)
2018            .expect("DW_OP_mod-style plan should lower");
2019        assert!(matches!(div_value, BasicValueEnum::IntValue(_)));
2020        assert!(matches!(mod_value, BasicValueEnum::IntValue(_)));
2021
2022        let ir = ctx.module.print_to_string().to_string();
2023        assert!(
2024            !ir.contains(" sdiv "),
2025            "runtime DWARF div should not emit LLVM signed division:\n{ir}"
2026        );
2027        assert!(
2028            !ir.contains(" srem "),
2029            "runtime DWARF mod should not emit LLVM signed remainder:\n{ir}"
2030        );
2031        assert!(
2032            ir.contains(" udiv "),
2033            "runtime DWARF div should lower through unsigned division:\n{ir}"
2034        );
2035        assert!(
2036            ir.contains(" urem "),
2037            "runtime DWARF mod should lower through unsigned remainder:\n{ir}"
2038        );
2039    }
2040
2041    #[test]
2042    fn runtime_computed_common_dwarf_ops_lower() {
2043        let llctx = LlvmContext::create();
2044        let opts = crate::CompileOptions::default();
2045        let mut ctx = EbpfContext::new(&llctx, "runtime_common_ops", Some(0), &opts).expect("ctx");
2046        ctx.create_basic_ebpf_function("f").expect("fn");
2047
2048        let ty = ghostscope_protocol::TypeInfo::BaseType {
2049            name: "long".to_string(),
2050            size: 8,
2051            encoding: ghostscope_dwarf::constants::DW_ATE_signed.0 as u16,
2052        };
2053        let cases = [
2054            (
2055                "shra_value",
2056                vec![
2057                    PlanExprOp::LoadRegister(0),
2058                    PlanExprOp::PushConstant(1),
2059                    PlanExprOp::Shra,
2060                ],
2061            ),
2062            (
2063                "not_value",
2064                vec![PlanExprOp::LoadRegister(0), PlanExprOp::Not],
2065            ),
2066            (
2067                "neg_value",
2068                vec![PlanExprOp::LoadRegister(0), PlanExprOp::Neg],
2069            ),
2070            (
2071                "abs_value",
2072                vec![PlanExprOp::LoadRegister(0), PlanExprOp::Abs],
2073            ),
2074            (
2075                "eq_value",
2076                vec![
2077                    PlanExprOp::LoadRegister(0),
2078                    PlanExprOp::PushConstant(0),
2079                    PlanExprOp::Eq,
2080                ],
2081            ),
2082            (
2083                "ne_value",
2084                vec![
2085                    PlanExprOp::LoadRegister(0),
2086                    PlanExprOp::PushConstant(0),
2087                    PlanExprOp::Ne,
2088                ],
2089            ),
2090            (
2091                "lt_value",
2092                vec![
2093                    PlanExprOp::LoadRegister(0),
2094                    PlanExprOp::PushConstant(0),
2095                    PlanExprOp::Lt,
2096                ],
2097            ),
2098            (
2099                "le_value",
2100                vec![
2101                    PlanExprOp::LoadRegister(0),
2102                    PlanExprOp::PushConstant(0),
2103                    PlanExprOp::Le,
2104                ],
2105            ),
2106            (
2107                "gt_value",
2108                vec![
2109                    PlanExprOp::LoadRegister(0),
2110                    PlanExprOp::PushConstant(0),
2111                    PlanExprOp::Gt,
2112                ],
2113            ),
2114            (
2115                "ge_value",
2116                vec![
2117                    PlanExprOp::LoadRegister(0),
2118                    PlanExprOp::PushConstant(0),
2119                    PlanExprOp::Ge,
2120                ],
2121            ),
2122        ];
2123
2124        for (name, ops) in cases {
2125            let plan = read_plan(
2126                name,
2127                "long",
2128                Some(ty.clone()),
2129                VariableLocation::ComputedValue(ops),
2130                Availability::Available,
2131            );
2132            let value = ctx
2133                .variable_read_plan_to_llvm_value(&plan, 0, None)
2134                .unwrap_or_else(|err| panic!("{name} should lower: {err:?}"));
2135            assert!(matches!(value, BasicValueEnum::IntValue(_)));
2136        }
2137
2138        let ir = ctx.module.print_to_string().to_string();
2139        assert!(
2140            ir.contains(" ashr "),
2141            "DW_OP_shra-style plan should emit arithmetic shift right:\n{ir}"
2142        );
2143        assert!(
2144            ir.contains(" icmp "),
2145            "comparison-style DWARF plans should emit integer compares:\n{ir}"
2146        );
2147    }
2148
2149    #[test]
2150    fn optimized_result_is_rejected_as_unavailable_value() {
2151        let llctx = LlvmContext::create();
2152        let opts = crate::CompileOptions::default();
2153        let mut ctx = EbpfContext::new(&llctx, "optimized_value", Some(0), &opts).expect("ctx");
2154        ctx.create_basic_ebpf_function("f").expect("fn");
2155
2156        let ty = ghostscope_protocol::TypeInfo::BaseType {
2157            name: "int".to_string(),
2158            size: 4,
2159            encoding: ghostscope_dwarf::constants::DW_ATE_signed.0 as u16,
2160        };
2161        let plan = read_plan(
2162            "x",
2163            "int",
2164            Some(ty),
2165            VariableLocation::OptimizedOut,
2166            Availability::OptimizedOut,
2167        );
2168
2169        let err = ctx
2170            .variable_read_plan_to_llvm_value(&plan, 0x1234, None)
2171            .expect_err("optimized value should not lower to a placeholder");
2172
2173        assert!(
2174            matches!(err, CodeGenError::VariableUnavailable(_)),
2175            "unexpected error: {err:?}"
2176        );
2177        assert!(err.to_string().contains("optimized out"));
2178        assert!(err.to_string().contains("0x1234"));
2179    }
2180
2181    #[test]
2182    fn piece_locations_are_rejected_instead_of_using_first_piece() {
2183        let llctx = LlvmContext::create();
2184        let opts = crate::CompileOptions::default();
2185        let mut ctx = EbpfContext::new(&llctx, "piece_value", Some(0), &opts).expect("ctx");
2186        ctx.create_basic_ebpf_function("f").expect("fn");
2187
2188        let ty = ghostscope_protocol::TypeInfo::BaseType {
2189            name: "int".to_string(),
2190            size: 4,
2191            encoding: ghostscope_dwarf::constants::DW_ATE_signed.0 as u16,
2192        };
2193        let location = VariableLocation::Pieces(vec![ghostscope_dwarf::PieceLocation {
2194            bit_offset: 0,
2195            bit_size: 32,
2196            location: Box::new(VariableLocation::RegisterValue { dwarf_reg: 0 }),
2197        }]);
2198        let plan = read_plan("split", "int", Some(ty), location, Availability::Available);
2199
2200        let err = ctx
2201            .variable_read_plan_to_llvm_value(&plan, 0x1234, None)
2202            .expect_err("split pieces should not silently use the first piece");
2203
2204        assert!(matches!(err, CodeGenError::DwarfError(_)));
2205        assert!(err.to_string().contains("split across pieces"));
2206    }
2207
2208    #[test]
2209    fn unavailable_error_formats_structured_dwarf_reason() {
2210        let err = EbpfContext::dwarf_expression_unavailable_error(
2211            "x",
2212            &Availability::Unsupported(ghostscope_dwarf::UnsupportedReason::ExpressionShape {
2213                detail: "estimated BPF stack use 64 bytes exceeds capability limit 16".to_string(),
2214            }),
2215            0xbeef,
2216        );
2217        let message = err.to_string();
2218
2219        assert!(matches!(err, CodeGenError::VariableUnavailable(_)));
2220        assert!(message.contains("unsupported DWARF expression shape"));
2221        assert!(message.contains("estimated BPF stack use 64 bytes"));
2222        assert!(!message.contains("ExpressionShape"));
2223    }
2224
2225    #[test]
2226    fn unavailable_error_formats_runtime_requirement() {
2227        let err = EbpfContext::dwarf_expression_unavailable_error(
2228            "ptr",
2229            &Availability::Requires(ghostscope_dwarf::RuntimeRequirement::UserMemoryRead),
2230            0xcafe,
2231        );
2232        let message = err.to_string();
2233
2234        assert!(matches!(err, CodeGenError::VariableUnavailable(_)));
2235        assert!(message.contains("user-memory read support"));
2236        assert!(!message.contains("UserMemoryRead"));
2237    }
2238
2239    #[test]
2240    fn read_plan_lowering_uses_compile_option_runtime_capabilities() {
2241        let llctx = LlvmContext::create();
2242        let mut opts = crate::CompileOptions::default();
2243        opts.runtime_capabilities.max_bpf_stack_bytes = 0;
2244        let ctx = EbpfContext::new(&llctx, "runtime_caps", Some(0), &opts).expect("ctx");
2245        let dwarf_type = ghostscope_protocol::TypeInfo::BaseType {
2246            name: "int".to_string(),
2247            size: 4,
2248            encoding: ghostscope_dwarf::constants::DW_ATE_signed.0 as u16,
2249        };
2250        let plan = VariableReadPlan {
2251            name: "x".to_string(),
2252            type_name: "int".to_string(),
2253            access_path: VariableAccessPath::default(),
2254            module_path: None,
2255            dwarf_type: Some(dwarf_type),
2256            declaration: None,
2257            type_id: None,
2258            location: VariableLocation::Address(AddressExpr::constant(0x1000)),
2259            availability: Availability::Available,
2260            scope_depth: 0,
2261            is_parameter: false,
2262            is_artificial: false,
2263            pc_range: None,
2264            inline_context: None,
2265            provenance: Provenance::DirectDie,
2266        };
2267
2268        let err = ctx
2269            .variable_read_plan_to_materialization(plan, 0x1234)
2270            .expect_err("zero stack capability should reject the read plan");
2271
2272        assert!(matches!(err, CodeGenError::VariableUnavailable(_)));
2273        assert!(err.to_string().contains("capability limit 0"));
2274    }
2275
2276    #[test]
2277    fn optimized_out_read_plan_preserves_marker_conversion() {
2278        let llctx = LlvmContext::create();
2279        let opts = crate::CompileOptions::default();
2280        let ctx = EbpfContext::new(&llctx, "optimized_marker", Some(0), &opts).expect("ctx");
2281        let dwarf_type = ghostscope_protocol::TypeInfo::BaseType {
2282            name: "int".to_string(),
2283            size: 4,
2284            encoding: ghostscope_dwarf::constants::DW_ATE_signed.0 as u16,
2285        };
2286        let plan = VariableReadPlan {
2287            name: "x".to_string(),
2288            type_name: "int".to_string(),
2289            access_path: VariableAccessPath::default(),
2290            module_path: None,
2291            dwarf_type: Some(dwarf_type),
2292            declaration: None,
2293            type_id: None,
2294            location: VariableLocation::OptimizedOut,
2295            availability: Availability::OptimizedOut,
2296            scope_depth: 0,
2297            is_parameter: false,
2298            is_artificial: false,
2299            pc_range: None,
2300            inline_context: None,
2301            provenance: Provenance::DirectDie,
2302        };
2303
2304        let materialized = ctx
2305            .variable_read_plan_to_materialization(plan, 0x1234)
2306            .expect("optimized-out runtime metadata should remain printable");
2307        assert_eq!(materialized.availability, Availability::OptimizedOut);
2308        assert!(matches!(
2309            materialized.materialization,
2310            ghostscope_dwarf::VariableMaterialization::Unavailable {
2311                availability: Availability::OptimizedOut
2312            }
2313        ));
2314    }
2315
2316    #[test]
2317    fn computed_location_supports_dereference_before_trailing_arithmetic() {
2318        let llctx = LlvmContext::create();
2319        let opts = crate::CompileOptions::default();
2320        let mut ctx = EbpfContext::new(&llctx, "computed_addr", Some(0), &opts).expect("ctx");
2321        ctx.create_basic_ebpf_function("f").expect("fn");
2322        ctx.__test_ensure_proc_offsets_map().expect("map");
2323        ctx.__test_alloc_pm_key().expect("pm_key");
2324        ctx.set_compile_time_context(0, "/nonexistent/module".to_string());
2325
2326        let location = VariableLocation::ComputedAddress(vec![
2327            PlanExprOp::PushConstant(0x3000),
2328            PlanExprOp::Dereference {
2329                size: MemoryAccessSize::U64,
2330            },
2331            PlanExprOp::PushConstant(16),
2332            PlanExprOp::Add,
2333        ]);
2334
2335        let address = PlannedAddress::from_location(location)
2336            .expect("computed location should materialize as a planned address");
2337        let addr = ctx
2338            .resolve_planned_address(&address, None, None)
2339            .expect("computed address with mid-stream dereference should compile");
2340        assert_eq!(addr.value.get_type().get_bit_width(), 64);
2341        assert_eq!(addr.offsets_found.get_type().get_bit_width(), 1);
2342        assert!(
2343            ctx.module
2344                .print_to_string()
2345                .to_string()
2346                .contains("add_guard"),
2347            "trailing arithmetic should preserve the address availability guard"
2348        );
2349    }
2350
2351    #[test]
2352    fn planned_address_lowering_does_not_emit_offsets_global() {
2353        let llctx = LlvmContext::create();
2354        let opts = crate::CompileOptions::default();
2355        let mut ctx = EbpfContext::new(&llctx, "explicit_addr_guard", Some(0), &opts).expect("ctx");
2356        ctx.create_basic_ebpf_function("f").expect("fn");
2357        ctx.__test_ensure_proc_offsets_map().expect("map");
2358        ctx.__test_alloc_pm_key().expect("pm_key");
2359        ctx.set_compile_time_context(0x1234, "/nonexistent/module".to_string());
2360
2361        let address =
2362            PlannedAddress::from_location(VariableLocation::Address(AddressExpr::constant(0x1000)))
2363                .expect("constant address should materialize as a planned address");
2364
2365        let addr = ctx
2366            .resolve_planned_address(&address, None, None)
2367            .expect("link-time address should lower with an explicit guard");
2368
2369        assert_eq!(addr.value.get_type().get_bit_width(), 64);
2370        assert_eq!(addr.offsets_found.get_type().get_bit_width(), 1);
2371        assert!(
2372            !ctx.module
2373                .print_to_string()
2374                .to_string()
2375                .contains("_gs_offsets_found"),
2376            "address availability should be threaded explicitly instead of using a module global"
2377        );
2378    }
2379
2380    #[test]
2381    fn lvalue_address_read_plan_does_not_require_dwarf_type() {
2382        let llctx = LlvmContext::create();
2383        let opts = crate::CompileOptions::default();
2384        let mut ctx = EbpfContext::new(&llctx, "untyped_lvalue_addr", Some(0), &opts).expect("ctx");
2385        ctx.create_basic_ebpf_function("f").expect("fn");
2386        ctx.__test_ensure_proc_offsets_map().expect("map");
2387        ctx.__test_alloc_pm_key().expect("pm_key");
2388        ctx.set_compile_time_context(0x1234, "/nonexistent/module".to_string());
2389
2390        let plan = read_plan(
2391            "untyped",
2392            "<unknown>",
2393            None,
2394            VariableLocation::Address(AddressExpr::constant(0x1000)),
2395            Availability::Available,
2396        );
2397
2398        let addr = ctx
2399            .variable_read_plan_to_runtime_address(&plan, 0x1234, None)
2400            .expect("address-only read plan should not require DWARF type info");
2401
2402        assert_eq!(addr.value.get_type().get_bit_width(), 64);
2403    }
2404
2405    #[test]
2406    fn unavailable_lvalue_address_plan_formats_error() {
2407        let llctx = LlvmContext::create();
2408        let opts = crate::CompileOptions::default();
2409        let mut ctx = EbpfContext::new(&llctx, "unavailable_lvalue", Some(0), &opts).expect("ctx");
2410        ctx.create_basic_ebpf_function("f").expect("fn");
2411
2412        let plan = read_plan(
2413            "x",
2414            "int",
2415            None,
2416            VariableLocation::OptimizedOut,
2417            Availability::OptimizedOut,
2418        );
2419
2420        let err = ctx
2421            .variable_read_plan_to_runtime_address(&plan, 0x1234, None)
2422            .expect_err("unavailable lvalue plans should be rejected");
2423
2424        assert!(matches!(err, CodeGenError::VariableUnavailable(_)));
2425        assert!(err.to_string().contains("cannot take its address"));
2426        assert!(err.to_string().contains("optimized out"));
2427    }
2428}