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 evaluation result processing.
5
6use super::context::{CodeGenError, EbpfContext, Result};
7use ghostscope_dwarf::{
8    ComputeStep, DirectValueResult, EvaluationResult, LocationResult, MemoryAccessSize, TypeInfo,
9    VariableWithEvaluation,
10};
11use ghostscope_process::module_probe;
12use inkwell::values::{BasicValueEnum, IntValue, PointerValue};
13use tracing::{debug, warn};
14
15impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> {
16    /// Compute a stable cookie for a module when per-PID offsets are unavailable (via coordinator).
17    fn fallback_cookie_from_module_path(&self, module_path: &str) -> u64 {
18        module_probe::cookie_for_path(module_path)
19    }
20
21    /// Compute section code for an address within a module (text=0, rodata=1, data=2, bss=3).
22    fn section_code_for_address(&mut self, module_path: &str, link_addr: u64) -> u8 {
23        if let Some(analyzer) = self.process_analyzer {
24            if let Some(st) = analyzer.classify_section_for_address(module_path, link_addr) {
25                return match st {
26                    ghostscope_dwarf::core::SectionType::Text => 0,
27                    ghostscope_dwarf::core::SectionType::Rodata => 1,
28                    ghostscope_dwarf::core::SectionType::Data => 2,
29                    ghostscope_dwarf::core::SectionType::Bss => 3,
30                    _ => 2,
31                };
32            }
33        }
34        2
35    }
36
37    /// Compute cookie for module using coordinator policy.
38    fn cookie_for_module_or_fallback(&mut self, module_path: &str) -> u64 {
39        self.fallback_cookie_from_module_path(module_path)
40    }
41    /// Helper: unwrap typedef/qualified wrappers to the underlying type
42    fn unwrap_type_aliases(mut t: &TypeInfo) -> &TypeInfo {
43        loop {
44            match t {
45                TypeInfo::TypedefType {
46                    underlying_type, ..
47                } => t = underlying_type.as_ref(),
48                TypeInfo::QualifiedType {
49                    underlying_type, ..
50                } => t = underlying_type.as_ref(),
51                _ => break,
52            }
53        }
54        t
55    }
56
57    /// Helper: determine if a DWARF type represents an aggregate (struct/union/array)
58    fn is_aggregate_type(&self, t: &TypeInfo) -> bool {
59        matches!(
60            Self::unwrap_type_aliases(t),
61            TypeInfo::StructType { .. } | TypeInfo::UnionType { .. } | TypeInfo::ArrayType { .. }
62        )
63    }
64    /// Convert EvaluationResult to LLVM value
65    pub fn evaluate_result_to_llvm_value(
66        &mut self,
67        evaluation_result: &EvaluationResult,
68        dwarf_type: &TypeInfo,
69        var_name: &str,
70        pc_address: u64,
71        status_ptr: Option<PointerValue<'ctx>>,
72    ) -> Result<BasicValueEnum<'ctx>> {
73        debug!(
74            "Converting EvaluationResult to LLVM value for variable: {}",
75            var_name
76        );
77        debug!("Evaluation context PC address: 0x{:x}", pc_address);
78
79        // Get pt_regs parameter
80        let pt_regs_ptr = self.get_pt_regs_parameter()?;
81
82        match evaluation_result {
83            EvaluationResult::DirectValue(direct) => {
84                self.generate_direct_value(direct, pt_regs_ptr)
85            }
86            EvaluationResult::MemoryLocation(location) => {
87                self.generate_memory_location(location, pt_regs_ptr, dwarf_type, status_ptr)
88            }
89            EvaluationResult::Optimized => {
90                debug!("Variable {} is optimized out", var_name);
91                // Return a placeholder value for optimized out variables
92                Ok(self.context.i64_type().const_zero().into())
93            }
94            EvaluationResult::Composite(members) => {
95                debug!(
96                    "Variable {} is composite with {} members",
97                    var_name,
98                    members.len()
99                );
100                // For now, just return the first member if available
101                if let Some(first_member) = members.first() {
102                    self.evaluate_result_to_llvm_value(
103                        &first_member.location,
104                        dwarf_type,
105                        var_name,
106                        pc_address,
107                        status_ptr,
108                    )
109                } else {
110                    Ok(self.context.i64_type().const_zero().into())
111                }
112            }
113        }
114    }
115
116    /// Variant that allows passing an explicit module hint for offsets lookup
117    pub fn evaluation_result_to_address_with_hint(
118        &mut self,
119        evaluation_result: &EvaluationResult,
120        status_ptr: Option<PointerValue<'ctx>>,
121        module_hint: Option<&str>,
122    ) -> Result<IntValue<'ctx>> {
123        // Policy note:
124        // - Link-time addresses (DW_OP_addr or constant-foldable address expressions) are
125        //   always rebased using per-module section offsets (ASLR) to get a runtime address.
126        // - Runtime-derived addresses (register/stack-relative or computed via dereference)
127        //   are used as-is and are NOT rebased.
128        // The caller signals which path we are on by providing the original evaluation_result.
129        let pt_regs_ptr = self.get_pt_regs_parameter()?;
130        // Default assumption: offsets are available unless a lookup proves otherwise.
131        self.store_offsets_found_const(true)?;
132
133        match evaluation_result {
134            EvaluationResult::MemoryLocation(LocationResult::Address(addr)) => {
135                // Unified: always attempt runtime rebasing via proc_module_offsets
136                let ctx = self.get_compile_time_context()?;
137                let module_for_offsets = module_hint
138                    .map(|s| s.to_string())
139                    .or_else(|| self.current_resolved_var_module_path.clone())
140                    .unwrap_or_else(|| ctx.module_path.clone());
141                let st_code = self.section_code_for_address(&module_for_offsets, *addr);
142                let cookie = self.cookie_for_module_or_fallback(&module_for_offsets);
143                let link_val = self.context.i64_type().const_int(*addr, false);
144                let (rt_addr, found_flag) =
145                    self.generate_runtime_address_from_offsets(link_val, st_code, cookie)?;
146                if let Some(sp) = status_ptr {
147                    let is_miss = self
148                        .builder
149                        .build_int_compare(
150                            inkwell::IntPredicate::EQ,
151                            found_flag,
152                            self.context.bool_type().const_zero(),
153                            "is_off_miss",
154                        )
155                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
156                    let cur_status = self
157                        .builder
158                        .build_load(self.context.i8_type(), sp, "cur_status")
159                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
160                    let is_ok = self
161                        .builder
162                        .build_int_compare(
163                            inkwell::IntPredicate::EQ,
164                            cur_status.into_int_value(),
165                            self.context.i8_type().const_zero(),
166                            "status_is_ok",
167                        )
168                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
169                    let should_store = self
170                        .builder
171                        .build_and(is_miss, is_ok, "store_offsets_unavail")
172                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
173                    let new_status = self
174                        .builder
175                        .build_select(
176                            should_store,
177                            self.context
178                                .i8_type()
179                                .const_int(
180                                    ghostscope_protocol::VariableStatus::OffsetsUnavailable as u64,
181                                    false,
182                                )
183                                .into(),
184                            cur_status,
185                            "new_status",
186                        )
187                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
188                    self.builder
189                        .build_store(sp, new_status)
190                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
191                }
192                self.store_offsets_found_flag(found_flag)?;
193                self.current_resolved_var_module_path = None;
194                Ok(rt_addr)
195            }
196            EvaluationResult::MemoryLocation(LocationResult::RegisterAddress {
197                register,
198                offset,
199                ..
200            }) => {
201                let reg_val = self.load_register_value(*register, pt_regs_ptr)?;
202                if let BasicValueEnum::IntValue(reg_i) = reg_val {
203                    if let Some(ofs) = offset {
204                        let ofs_val = self.context.i64_type().const_int(*ofs as u64, true);
205                        let sum = self
206                            .builder
207                            .build_int_add(reg_i, ofs_val, "addr_with_offset")
208                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
209                        Ok(sum)
210                    } else {
211                        Ok(reg_i)
212                    }
213                } else {
214                    Err(CodeGenError::RegisterMappingError(
215                        "Register value is not integer".to_string(),
216                    ))
217                }
218            }
219            EvaluationResult::MemoryLocation(LocationResult::ComputedLocation { steps }) => {
220                // Try to fold constant-only address expressions (e.g., global + const offset)
221                // If foldable, treat as link-time address and apply ASLR offsets via map.
222                let mut const_stack: Vec<i64> = Vec::new();
223                let mut foldable = true;
224                for s in steps.iter() {
225                    match s {
226                        ComputeStep::PushConstant(v) => const_stack.push(*v),
227                        ComputeStep::Add => {
228                            if const_stack.len() >= 2 {
229                                let b = const_stack.pop().unwrap();
230                                let a = const_stack.pop().unwrap();
231                                const_stack.push(a.saturating_add(b));
232                            } else {
233                                foldable = false;
234                                break;
235                            }
236                        }
237                        // Any register load or deref means runtime-derived address; not foldable
238                        ComputeStep::LoadRegister(_) | ComputeStep::Dereference { .. } => {
239                            foldable = false;
240                            break;
241                        }
242                        _ => {
243                            // Unknown/non-add op: treat as non-foldable
244                            foldable = false;
245                            break;
246                        }
247                    }
248                }
249
250                if foldable && const_stack.len() == 1 {
251                    let link_addr_u = const_stack[0] as u64;
252                    let ctx = self.get_compile_time_context()?;
253                    let module_for_offsets = module_hint
254                        .map(|s| s.to_string())
255                        .or_else(|| self.current_resolved_var_module_path.clone())
256                        .unwrap_or_else(|| ctx.module_path.clone());
257                    let st_code = self.section_code_for_address(&module_for_offsets, link_addr_u);
258                    let cookie = self.cookie_for_module_or_fallback(&module_for_offsets);
259                    let link_val = self.context.i64_type().const_int(link_addr_u, false);
260                    let (rt_addr, found_flag) =
261                        self.generate_runtime_address_from_offsets(link_val, st_code, cookie)?;
262                    if let Some(sp) = status_ptr {
263                        let is_miss = self
264                            .builder
265                            .build_int_compare(
266                                inkwell::IntPredicate::EQ,
267                                found_flag,
268                                self.context.bool_type().const_zero(),
269                                "is_off_miss",
270                            )
271                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
272                        let cur_status = self
273                            .builder
274                            .build_load(self.context.i8_type(), sp, "cur_status")
275                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
276                        let is_ok = self
277                            .builder
278                            .build_int_compare(
279                                inkwell::IntPredicate::EQ,
280                                cur_status.into_int_value(),
281                                self.context.i8_type().const_zero(),
282                                "status_is_ok",
283                            )
284                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
285                        let should_store = self
286                            .builder
287                            .build_and(is_miss, is_ok, "store_offsets_unavail")
288                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
289                        let new_status = self
290                            .builder
291                            .build_select(
292                                should_store,
293                                self.context
294                                    .i8_type()
295                                    .const_int(
296                                        ghostscope_protocol::VariableStatus::OffsetsUnavailable
297                                            as u64,
298                                        false,
299                                    )
300                                    .into(),
301                                cur_status,
302                                "new_status",
303                            )
304                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
305                        self.builder
306                            .build_store(sp, new_status)
307                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
308                    }
309                    self.current_resolved_var_module_path = None;
310                    return Ok(rt_addr);
311                }
312
313                // Attempt: if steps start with PushConstant(base) and first dynamic op is Dereference
314                // (with no LoadRegister before it), apply ASLR offsets to base and continue
315                if let Some(ComputeStep::PushConstant(base_const)) = steps.first() {
316                    // Scan until first Dereference or LoadRegister
317                    let mut saw_reg = false;
318                    let mut saw_deref = false;
319                    for s in &steps[1..] {
320                        match s {
321                            ComputeStep::LoadRegister(_) => {
322                                saw_reg = true;
323                                break;
324                            }
325                            ComputeStep::Dereference { .. } => {
326                                saw_deref = true;
327                                break;
328                            }
329                            _ => {}
330                        }
331                    }
332                    if saw_deref && !saw_reg {
333                        let link_addr_u = *base_const as u64;
334                        let ctx = self.get_compile_time_context()?;
335                        let module_for_offsets = module_hint
336                            .map(|s| s.to_string())
337                            .or_else(|| self.current_resolved_var_module_path.clone())
338                            .unwrap_or_else(|| ctx.module_path.clone());
339                        let st_code =
340                            self.section_code_for_address(&module_for_offsets, link_addr_u);
341                        let cookie = self.cookie_for_module_or_fallback(&module_for_offsets);
342                        let link_val = self.context.i64_type().const_int(link_addr_u, false);
343                        let (rt, found_flag) =
344                            self.generate_runtime_address_from_offsets(link_val, st_code, cookie)?;
345                        if let Some(sp) = status_ptr {
346                            let is_miss = self
347                                .builder
348                                .build_int_compare(
349                                    inkwell::IntPredicate::EQ,
350                                    found_flag,
351                                    self.context.bool_type().const_zero(),
352                                    "is_off_miss",
353                                )
354                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
355                            let cur_status = self
356                                .builder
357                                .build_load(self.context.i8_type(), sp, "cur_status")
358                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
359                            let is_ok = self
360                                .builder
361                                .build_int_compare(
362                                    inkwell::IntPredicate::EQ,
363                                    cur_status.into_int_value(),
364                                    self.context.i8_type().const_zero(),
365                                    "status_is_ok",
366                                )
367                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
368                            let should_store = self
369                                .builder
370                                .build_and(is_miss, is_ok, "store_offsets_unavail")
371                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
372                            let new_status = self
373                                .builder
374                                .build_select(
375                                    should_store,
376                                    self.context
377                                        .i8_type()
378                                        .const_int(
379                                            ghostscope_protocol::VariableStatus::OffsetsUnavailable
380                                                as u64,
381                                            false,
382                                        )
383                                        .into(),
384                                    cur_status,
385                                    "new_status",
386                                )
387                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
388                            self.builder
389                                .build_store(sp, new_status)
390                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
391                        }
392                        // Execute remaining steps with rt pre-pushed as base
393                        let rest = &steps[1..];
394                        let val = self.generate_compute_steps(
395                            rest,
396                            pt_regs_ptr,
397                            None,
398                            status_ptr,
399                            Some(rt),
400                        )?;
401                        if let BasicValueEnum::IntValue(i) = val {
402                            return Ok(i);
403                        } else {
404                            return Err(CodeGenError::LLVMError(
405                                "Computed location did not produce integer".to_string(),
406                            ));
407                        }
408                    }
409                }
410
411                // Fallback: execute steps at runtime and use the result directly (no offsets)
412                let val =
413                    self.generate_compute_steps(steps, pt_regs_ptr, None, status_ptr, None)?;
414                if let BasicValueEnum::IntValue(i) = val {
415                    Ok(i)
416                } else {
417                    Err(CodeGenError::LLVMError(
418                        "Computed location did not produce integer".to_string(),
419                    ))
420                }
421            }
422            _ => Err(CodeGenError::NotImplemented(
423                "Unable to compute address from evaluation result".to_string(),
424            )),
425        }
426    }
427
428    /// Convert DWARF type size to MemoryAccessSize
429    fn dwarf_type_to_memory_access_size(&self, dwarf_type: &TypeInfo) -> MemoryAccessSize {
430        let size = Self::get_dwarf_type_size(dwarf_type);
431        match size {
432            1 => MemoryAccessSize::U8,
433            2 => MemoryAccessSize::U16,
434            4 => MemoryAccessSize::U32,
435            8 => MemoryAccessSize::U64,
436            _ => MemoryAccessSize::U64, // Default to U64 for unknown sizes
437        }
438    }
439
440    /// Generate LLVM IR for direct value result
441    fn generate_direct_value(
442        &mut self,
443        direct: &DirectValueResult,
444        pt_regs_ptr: PointerValue<'ctx>,
445    ) -> Result<BasicValueEnum<'ctx>> {
446        match direct {
447            DirectValueResult::Constant(value) => {
448                debug!("Generating constant: {}", value);
449                Ok(self
450                    .context
451                    .i64_type()
452                    .const_int(*value as u64, true)
453                    .into())
454            }
455
456            DirectValueResult::AbsoluteAddress(value) => {
457                debug!("Generating rebased absolute address: 0x{value:x}");
458                let module_hint = self.current_resolved_var_module_path.clone();
459                let status_ptr = if self.condition_context_active {
460                    Some(self.get_or_create_cond_error_global())
461                } else {
462                    None
463                };
464                let eval = ghostscope_dwarf::EvaluationResult::MemoryLocation(
465                    ghostscope_dwarf::LocationResult::Address(*value),
466                );
467                self.evaluation_result_to_address_with_hint(
468                    &eval,
469                    status_ptr,
470                    module_hint.as_deref(),
471                )
472                .map(Into::into)
473            }
474
475            DirectValueResult::ImplicitValue(bytes) => {
476                debug!("Generating implicit value: {} bytes", bytes.len());
477                // Convert bytes to integer value (little-endian)
478                let mut value: u64 = 0;
479                for (i, &byte) in bytes.iter().enumerate().take(8) {
480                    value |= (byte as u64) << (i * 8);
481                }
482                Ok(self.context.i64_type().const_int(value, false).into())
483            }
484
485            DirectValueResult::RegisterValue(reg_num) => {
486                debug!("Generating register value: {}", reg_num);
487                let reg_value = self.load_register_value(*reg_num, pt_regs_ptr)?;
488                Ok(reg_value)
489            }
490
491            DirectValueResult::ComputedValue { steps, result_size } => {
492                debug!("Generating computed value: {} steps", steps.len());
493                let status_ptr = if self.condition_context_active {
494                    Some(self.get_or_create_cond_error_global())
495                } else {
496                    None
497                };
498                self.generate_compute_steps(
499                    steps,
500                    pt_regs_ptr,
501                    Some(*result_size),
502                    status_ptr,
503                    None,
504                )
505            }
506        }
507    }
508
509    /// Generate LLVM IR for memory location result
510    fn generate_memory_location(
511        &mut self,
512        location: &LocationResult,
513        pt_regs_ptr: PointerValue<'ctx>,
514        dwarf_type: &TypeInfo,
515        status_ptr: Option<PointerValue<'ctx>>,
516    ) -> Result<BasicValueEnum<'ctx>> {
517        match location {
518            // Policy note:
519            // We decide ASLR rebasing based on the DWARF evaluation RESULT SHAPE, not a
520            // "global variable" tag. Whenever DWARF yields a link-time address
521            // (LocationResult::Address) — including file-scope globals, static locals,
522            // rodata/data/bss, or any constant-folded address — we MUST apply per-module
523            // section offsets (.text/.rodata/.data/.bss) to obtain the runtime address.
524            // Conversely, for runtime-derived addresses (RegisterAddress or computed from
525            // registers/dereferences), we DO NOT rebase.
526            LocationResult::Address(addr) => {
527                debug!("Generating absolute address: 0x{:x}", addr);
528                // Convert link-time address to runtime address using ASLR offsets when available
529                let module_hint = self.current_resolved_var_module_path.clone();
530                let runtime_status_ptr = if self.condition_context_active {
531                    Some(self.get_or_create_cond_error_global())
532                } else {
533                    status_ptr
534                };
535                let eval = ghostscope_dwarf::EvaluationResult::MemoryLocation(
536                    ghostscope_dwarf::LocationResult::Address(*addr),
537                );
538                let rt_addr = self.evaluation_result_to_address_with_hint(
539                    &eval,
540                    runtime_status_ptr,
541                    module_hint.as_deref(),
542                )?;
543                // Aggregate types (struct/union/array) are represented as pointers in expressions
544                if self.is_aggregate_type(dwarf_type) {
545                    let ptr_ty = self.context.ptr_type(inkwell::AddressSpace::default());
546                    let as_ptr = self
547                        .builder
548                        .build_int_to_ptr(rt_addr, ptr_ty, "aggregate_addr_as_ptr")
549                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
550                    return Ok(as_ptr.into());
551                }
552                // Use DWARF type size for memory access
553                let access_size = self.dwarf_type_to_memory_access_size(dwarf_type);
554                if self.condition_context_active {
555                    self.generate_memory_read_with_status(rt_addr, access_size)
556                } else {
557                    self.generate_memory_read(rt_addr, access_size, status_ptr)
558                }
559            }
560
561            LocationResult::RegisterAddress {
562                register,
563                offset,
564                size,
565            } => {
566                debug!(
567                    "Generating register address: reg{} {:+}",
568                    register,
569                    offset.unwrap_or(0)
570                );
571
572                // Load register value
573                let reg_value = self.load_register_value(*register, pt_regs_ptr)?;
574
575                // Add offset if present
576                let final_addr = if let Some(offset) = offset {
577                    let offset_value = self.context.i64_type().const_int(*offset as u64, true);
578                    if let BasicValueEnum::IntValue(reg_int) = reg_value {
579                        self.builder
580                            .build_int_add(reg_int, offset_value, "addr_with_offset")
581                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
582                    } else {
583                        return Err(CodeGenError::RegisterMappingError(
584                            "Register value is not integer".to_string(),
585                        ));
586                    }
587                } else if let BasicValueEnum::IntValue(reg_int) = reg_value {
588                    reg_int
589                } else {
590                    return Err(CodeGenError::RegisterMappingError(
591                        "Register value is not integer".to_string(),
592                    ));
593                };
594                // Aggregate types: return pointer instead of reading as scalar
595                if self.is_aggregate_type(dwarf_type) {
596                    let ptr_ty = self.context.ptr_type(inkwell::AddressSpace::default());
597                    let as_ptr = self
598                        .builder
599                        .build_int_to_ptr(final_addr, ptr_ty, "aggregate_addr_as_ptr")
600                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
601                    return Ok(as_ptr.into());
602                }
603                // Determine memory access size - prefer LocationResult size if available, otherwise use DWARF type
604                let access_size = size
605                    .map(|s| match s {
606                        1 => MemoryAccessSize::U8,
607                        2 => MemoryAccessSize::U16,
608                        4 => MemoryAccessSize::U32,
609                        _ => MemoryAccessSize::U64,
610                    })
611                    .unwrap_or_else(|| self.dwarf_type_to_memory_access_size(dwarf_type));
612
613                if self.condition_context_active {
614                    self.generate_memory_read_with_status(final_addr, access_size)
615                } else {
616                    self.generate_memory_read(final_addr, access_size, status_ptr)
617                }
618            }
619
620            LocationResult::ComputedLocation { steps } => {
621                debug!("Generating computed location: {} steps", steps.len());
622                // Execute steps to compute the address
623                let runtime_status_ptr = if self.condition_context_active {
624                    Some(self.get_or_create_cond_error_global())
625                } else {
626                    status_ptr
627                };
628                let addr_value = self.generate_compute_steps(
629                    steps,
630                    pt_regs_ptr,
631                    None,
632                    runtime_status_ptr,
633                    None,
634                )?;
635                if let BasicValueEnum::IntValue(addr) = addr_value {
636                    // For aggregate types, return pointer to address instead of loading a value
637                    if self.is_aggregate_type(dwarf_type) {
638                        let ptr_ty = self.context.ptr_type(inkwell::AddressSpace::default());
639                        let as_ptr = self
640                            .builder
641                            .build_int_to_ptr(addr, ptr_ty, "aggregate_addr_as_ptr")
642                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
643                        return Ok(as_ptr.into());
644                    }
645                    // Use DWARF type size for memory access
646                    let access_size = self.dwarf_type_to_memory_access_size(dwarf_type);
647                    if self.condition_context_active {
648                        self.generate_memory_read_with_status(addr, access_size)
649                    } else {
650                        self.generate_memory_read(addr, access_size, status_ptr)
651                    }
652                } else {
653                    Err(CodeGenError::LLVMError(
654                        "Address computation must return integer".to_string(),
655                    ))
656                }
657            }
658        }
659    }
660
661    /// Execute a sequence of compute steps
662    fn generate_compute_steps(
663        &mut self,
664        steps: &[ComputeStep],
665        pt_regs_ptr: PointerValue<'ctx>,
666        _result_size: Option<MemoryAccessSize>,
667        status_ptr: Option<PointerValue<'ctx>>,
668        initial_top: Option<IntValue<'ctx>>,
669    ) -> Result<BasicValueEnum<'ctx>> {
670        // Implement stack-based computation
671        let mut stack: Vec<IntValue<'ctx>> = Vec::new();
672        // Track a runtime null-pointer flag from dereference steps; when true, subsequent
673        // arithmetic will be masked to zero to avoid reads at small offsets from NULL.
674        let mut deref_null_flag: Option<inkwell::values::IntValue> = None;
675        if let Some(top) = initial_top {
676            stack.push(top);
677        }
678
679        for step in steps {
680            match step {
681                ComputeStep::LoadRegister(reg_num) => {
682                    let reg_value = self.load_register_value(*reg_num, pt_regs_ptr)?;
683                    if let BasicValueEnum::IntValue(int_val) = reg_value {
684                        stack.push(int_val);
685                    } else {
686                        return Err(CodeGenError::RegisterMappingError(format!(
687                            "Register {reg_num} did not return integer value"
688                        )));
689                    }
690                }
691
692                ComputeStep::PushConstant(value) => {
693                    let const_val = self.context.i64_type().const_int(*value as u64, true);
694                    stack.push(const_val);
695                }
696
697                ComputeStep::Add => {
698                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
699                        let sum_val = self
700                            .builder
701                            .build_int_add(a, b, "add")
702                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
703                        if let Some(nf) = deref_null_flag {
704                            let masked_bv = self
705                                .builder
706                                .build_select::<inkwell::values::BasicValueEnum<'ctx>, _>(
707                                    nf,
708                                    self.context.i64_type().const_zero().into(),
709                                    sum_val.into(),
710                                    "add_masked",
711                                )
712                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
713                            stack.push(masked_bv.into_int_value());
714                        } else {
715                            stack.push(sum_val);
716                        }
717                    } else {
718                        return Err(CodeGenError::LLVMError(
719                            "Stack underflow in Add".to_string(),
720                        ));
721                    }
722                }
723
724                ComputeStep::Sub => {
725                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
726                        let result = self
727                            .builder
728                            .build_int_sub(a, b, "sub")
729                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
730                        stack.push(result);
731                    } else {
732                        return Err(CodeGenError::LLVMError(
733                            "Stack underflow in Sub".to_string(),
734                        ));
735                    }
736                }
737
738                ComputeStep::Mul => {
739                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
740                        let result = self
741                            .builder
742                            .build_int_mul(a, b, "mul")
743                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
744                        stack.push(result);
745                    } else {
746                        return Err(CodeGenError::LLVMError(
747                            "Stack underflow in Mul".to_string(),
748                        ));
749                    }
750                }
751
752                ComputeStep::Div => {
753                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
754                        let result = self
755                            .builder
756                            .build_int_signed_div(a, b, "div")
757                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
758                        stack.push(result);
759                    } else {
760                        return Err(CodeGenError::LLVMError(
761                            "Stack underflow in Div".to_string(),
762                        ));
763                    }
764                }
765
766                ComputeStep::And => {
767                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
768                        let result = self
769                            .builder
770                            .build_and(a, b, "and")
771                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
772                        stack.push(result);
773                    } else {
774                        return Err(CodeGenError::LLVMError(
775                            "Stack underflow in BitwiseAnd".to_string(),
776                        ));
777                    }
778                }
779
780                ComputeStep::Or => {
781                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
782                        let result = self
783                            .builder
784                            .build_or(a, b, "or")
785                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
786                        stack.push(result);
787                    } else {
788                        return Err(CodeGenError::LLVMError(
789                            "Stack underflow in BitwiseOr".to_string(),
790                        ));
791                    }
792                }
793
794                ComputeStep::Xor => {
795                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
796                        let result = self
797                            .builder
798                            .build_xor(a, b, "xor")
799                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
800                        stack.push(result);
801                    } else {
802                        return Err(CodeGenError::LLVMError(
803                            "Stack underflow in BitwiseXor".to_string(),
804                        ));
805                    }
806                }
807
808                ComputeStep::Shl => {
809                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
810                        let result = self
811                            .builder
812                            .build_left_shift(a, b, "shl")
813                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
814                        stack.push(result);
815                    } else {
816                        return Err(CodeGenError::LLVMError(
817                            "Stack underflow in ShiftLeft".to_string(),
818                        ));
819                    }
820                }
821
822                ComputeStep::Shr => {
823                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
824                        let result = self
825                            .builder
826                            .build_right_shift(a, b, false, "shr")
827                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
828                        stack.push(result);
829                    } else {
830                        return Err(CodeGenError::LLVMError(
831                            "Stack underflow in ShiftRight".to_string(),
832                        ));
833                    }
834                }
835
836                ComputeStep::Dereference { size } => {
837                    if let Some(addr) = stack.pop() {
838                        // Null guard: if addr == 0, set NullDeref (if status_ptr provided and current is Ok)
839                        let zero64 = self.context.i64_type().const_zero();
840                        let is_null = self
841                            .builder
842                            .build_int_compare(
843                                inkwell::IntPredicate::EQ,
844                                addr,
845                                zero64,
846                                "is_null_deref",
847                            )
848                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
849
850                        let cur_fn = self
851                            .builder
852                            .get_insert_block()
853                            .unwrap()
854                            .get_parent()
855                            .unwrap();
856                        let null_bb = self.context.append_basic_block(cur_fn, "deref_null");
857                        let read_bb = self.context.append_basic_block(cur_fn, "deref_read");
858                        let cont_bb = self.context.append_basic_block(cur_fn, "deref_cont");
859                        self.builder
860                            .build_conditional_branch(is_null, null_bb, read_bb)
861                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
862
863                        // Null path: optionally set status=NullDeref if currently Ok, branch to cont
864                        self.builder.position_at_end(null_bb);
865                        let null_val = self.context.i64_type().const_zero();
866                        if let Some(sp) = status_ptr {
867                            let cur_status = self
868                                .builder
869                                .build_load(self.context.i8_type(), sp, "cur_status")
870                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
871                                .into_int_value();
872                            let is_ok = self
873                                .builder
874                                .build_int_compare(
875                                    inkwell::IntPredicate::EQ,
876                                    cur_status,
877                                    self.context.i8_type().const_zero(),
878                                    "status_is_ok",
879                                )
880                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
881                            let then_val = self.context.i8_type().const_int(
882                                ghostscope_protocol::VariableStatus::NullDeref as u64,
883                                false,
884                            );
885                            let new_status_bv = self
886                                .builder
887                                .build_select::<inkwell::values::BasicValueEnum<'ctx>, _>(
888                                    is_ok,
889                                    then_val.into(),
890                                    cur_status.into(),
891                                    "new_status",
892                                )
893                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
894                            self.builder
895                                .build_store(sp, new_status_bv)
896                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
897                        }
898                        self.builder
899                            .build_unconditional_branch(cont_bb)
900                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
901
902                        // Read path: load pointer-sized value into tmp then branch to cont
903                        self.builder.position_at_end(read_bb);
904                        let access_size = *size;
905                        let loaded_bv = if self.condition_context_active {
906                            self.generate_memory_read_with_status(addr, access_size)?
907                        } else {
908                            self.generate_memory_read(addr, access_size, status_ptr)?
909                        };
910                        let loaded_int = if let BasicValueEnum::IntValue(int_val) = loaded_bv {
911                            int_val
912                        } else {
913                            return Err(CodeGenError::LLVMError(
914                                "Memory load did not return integer".to_string(),
915                            ));
916                        };
917                        let value_block = self.builder.get_insert_block().ok_or_else(|| {
918                            CodeGenError::LLVMError(
919                                "No insertion block after dereference read".to_string(),
920                            )
921                        })?;
922                        self.builder
923                            .build_unconditional_branch(cont_bb)
924                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
925
926                        // Continue at cont: create PHI to merge null/read values, push once
927                        self.builder.position_at_end(cont_bb);
928                        let phi = self
929                            .builder
930                            .build_phi(self.context.i64_type(), "deref_phi")
931                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
932                        phi.add_incoming(&[(&null_val, null_bb), (&loaded_int, value_block)]);
933                        let merged = phi.as_basic_value().into_int_value();
934                        // Update null flag based on loaded pointer value being zero
935                        let is_zero_ptr = self
936                            .builder
937                            .build_int_compare(
938                                inkwell::IntPredicate::EQ,
939                                merged,
940                                self.context.i64_type().const_zero(),
941                                "is_zero_ptr",
942                            )
943                            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
944                        deref_null_flag = Some(match deref_null_flag {
945                            Some(prev) => self
946                                .builder
947                                .build_or(prev, is_zero_ptr, "null_or")
948                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?,
949                            None => is_zero_ptr,
950                        });
951                        if let (Some(sp), Some(nf)) = (status_ptr, deref_null_flag) {
952                            // Only store NullDeref if currently OK and nf is true
953                            let cur_status = self
954                                .builder
955                                .build_load(self.context.i8_type(), sp, "cur_status")
956                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
957                                .into_int_value();
958                            let is_ok = self
959                                .builder
960                                .build_int_compare(
961                                    inkwell::IntPredicate::EQ,
962                                    cur_status,
963                                    self.context.i8_type().const_zero(),
964                                    "status_is_ok2",
965                                )
966                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
967                            let should_store = self
968                                .builder
969                                .build_and(is_ok, nf, "store_null_deref_from_ptr")
970                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
971                            let then_val = self.context.i8_type().const_int(
972                                ghostscope_protocol::VariableStatus::NullDeref as u64,
973                                false,
974                            );
975                            let new_status_bv = self
976                                .builder
977                                .build_select::<inkwell::values::BasicValueEnum<'ctx>, _>(
978                                    should_store,
979                                    then_val.into(),
980                                    cur_status.into(),
981                                    "new_status2",
982                                )
983                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
984                            self.builder
985                                .build_store(sp, new_status_bv)
986                                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
987                        }
988                        stack.push(merged);
989                    } else {
990                        return Err(CodeGenError::LLVMError(
991                            "Stack underflow in LoadMemory".to_string(),
992                        ));
993                    }
994                }
995
996                ComputeStep::EntryValueLookup {
997                    caller_pc_steps,
998                    cases,
999                } => {
1000                    let value = self.generate_entry_value_lookup(
1001                        caller_pc_steps,
1002                        cases,
1003                        pt_regs_ptr,
1004                        _result_size,
1005                        status_ptr,
1006                    )?;
1007                    stack.push(value);
1008                }
1009
1010                // Add catch-all for unimplemented operations
1011                _ => {
1012                    warn!("Unimplemented ComputeStep: {:?}", step);
1013                    return Err(CodeGenError::NotImplemented(format!(
1014                        "ComputeStep {step:?} not yet implemented"
1015                    )));
1016                }
1017            }
1018        }
1019
1020        if stack.len() == 1 {
1021            Ok(stack.pop().unwrap().into())
1022        } else {
1023            Err(CodeGenError::LLVMError(format!(
1024                "Invalid stack state after computation: {} elements remaining",
1025                stack.len()
1026            )))
1027        }
1028    }
1029
1030    fn generate_entry_value_lookup(
1031        &mut self,
1032        caller_pc_steps: &[ComputeStep],
1033        cases: &[ghostscope_dwarf::core::EntryValueCase],
1034        pt_regs_ptr: PointerValue<'ctx>,
1035        result_size: Option<MemoryAccessSize>,
1036        status_ptr: Option<PointerValue<'ctx>>,
1037    ) -> Result<IntValue<'ctx>> {
1038        if cases.is_empty() {
1039            return Err(CodeGenError::LLVMError(
1040                "EntryValueLookup requires at least one case".to_string(),
1041            ));
1042        }
1043
1044        let caller_pc = self
1045            .generate_compute_steps(
1046                caller_pc_steps,
1047                pt_regs_ptr,
1048                Some(MemoryAccessSize::U64),
1049                status_ptr,
1050                None,
1051            )?
1052            .into_int_value();
1053
1054        let current_block = self.builder.get_insert_block().ok_or_else(|| {
1055            CodeGenError::LLVMError("No insertion block for EntryValueLookup".to_string())
1056        })?;
1057        let current_fn = current_block.get_parent().ok_or_else(|| {
1058            CodeGenError::LLVMError("No parent function for EntryValueLookup".to_string())
1059        })?;
1060        let merge_bb = self
1061            .context
1062            .append_basic_block(current_fn, "entry_value_merge");
1063        let default_bb = self
1064            .context
1065            .append_basic_block(current_fn, "entry_value_default");
1066
1067        let module_for_offsets = {
1068            let ctx = self.get_compile_time_context()?;
1069            self.current_resolved_var_module_path
1070                .clone()
1071                .unwrap_or_else(|| ctx.module_path.clone())
1072        };
1073        let module_cookie = self.cookie_for_module_or_fallback(&module_for_offsets);
1074        let mut incoming_values = Vec::with_capacity(cases.len() + 1);
1075        let mut any_missing_offsets = None;
1076
1077        for (index, case) in cases.iter().enumerate() {
1078            let st_code = self.section_code_for_address(&module_for_offsets, case.caller_return_pc);
1079            let link_pc = self
1080                .context
1081                .i64_type()
1082                .const_int(case.caller_return_pc, false);
1083            let (runtime_return_pc, found_flag) =
1084                self.generate_runtime_address_from_offsets(link_pc, st_code, module_cookie)?;
1085            let missing_offsets = self
1086                .builder
1087                .build_not(found_flag, &format!("entry_value_missing_{index}"))
1088                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1089            any_missing_offsets = Some(match any_missing_offsets {
1090                Some(prev) => self
1091                    .builder
1092                    .build_or(
1093                        prev,
1094                        missing_offsets,
1095                        &format!("entry_value_missing_or_{index}"),
1096                    )
1097                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?,
1098                None => missing_offsets,
1099            });
1100
1101            let is_match = self
1102                .builder
1103                .build_int_compare(
1104                    inkwell::IntPredicate::EQ,
1105                    caller_pc,
1106                    runtime_return_pc,
1107                    &format!("entry_value_match_{index}"),
1108                )
1109                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1110            let case_bb = self
1111                .context
1112                .append_basic_block(current_fn, &format!("entry_value_case_{index}"));
1113            let next_bb = if index + 1 == cases.len() {
1114                default_bb
1115            } else {
1116                self.context
1117                    .append_basic_block(current_fn, &format!("entry_value_check_{}", index + 1))
1118            };
1119            self.builder
1120                .build_conditional_branch(is_match, case_bb, next_bb)
1121                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1122
1123            self.builder.position_at_end(case_bb);
1124            let case_value = self
1125                .generate_compute_steps(
1126                    &case.value_steps,
1127                    pt_regs_ptr,
1128                    result_size,
1129                    status_ptr,
1130                    None,
1131                )?
1132                .into_int_value();
1133            let case_value_block = self.builder.get_insert_block().ok_or_else(|| {
1134                CodeGenError::LLVMError(
1135                    "No insertion block after EntryValueLookup case".to_string(),
1136                )
1137            })?;
1138            self.builder
1139                .build_unconditional_branch(merge_bb)
1140                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1141            incoming_values.push((case_value, case_value_block));
1142
1143            self.builder.position_at_end(next_bb);
1144        }
1145
1146        self.builder.position_at_end(default_bb);
1147        if let Some(sp) = status_ptr {
1148            self.store_variable_read_status(
1149                sp,
1150                self.context.bool_type().const_int(1, false),
1151                any_missing_offsets.unwrap_or_else(|| self.context.bool_type().const_zero()),
1152                "entry_value_default",
1153            )?;
1154        }
1155        let default_value = self.context.i64_type().const_zero();
1156        let default_value_block = self.builder.get_insert_block().ok_or_else(|| {
1157            CodeGenError::LLVMError("No default block for EntryValueLookup".to_string())
1158        })?;
1159        self.builder
1160            .build_unconditional_branch(merge_bb)
1161            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1162        incoming_values.push((default_value, default_value_block));
1163
1164        self.builder.position_at_end(merge_bb);
1165        let phi = self
1166            .builder
1167            .build_phi(self.context.i64_type(), "entry_value_phi")
1168            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1169        let incoming_refs: Vec<(&dyn inkwell::values::BasicValue<'ctx>, _)> = incoming_values
1170            .iter()
1171            .map(|(value, block)| (value as &dyn inkwell::values::BasicValue<'ctx>, *block))
1172            .collect();
1173        phi.add_incoming(&incoming_refs);
1174
1175        Ok(phi.as_basic_value().into_int_value())
1176    }
1177
1178    /// Query DWARF for complex expression (supports member access, array access, etc.)
1179    pub fn query_dwarf_for_complex_expr(
1180        &mut self,
1181        expr: &crate::script::Expr,
1182    ) -> Result<Option<VariableWithEvaluation>> {
1183        use crate::script::Expr;
1184
1185        // Expand script alias variables inside the expression so downstream
1186        // DWARF resolvers see the actual DWARF-based expression tree.
1187        // Guard against self-referential or cyclic aliases.
1188        fn expand_aliases(
1189            ctx: &crate::ebpf::context::EbpfContext<'_, '_>,
1190            e: &crate::script::Expr,
1191            visited: &mut std::collections::HashSet<String>,
1192            depth: usize,
1193        ) -> std::result::Result<crate::script::Expr, super::context::CodeGenError> {
1194            use crate::script::Expr as E;
1195            const MAX_DEPTH: usize = 64;
1196            if depth > MAX_DEPTH {
1197                return Err(super::context::CodeGenError::TypeError(
1198                    "alias expansion depth exceeded (cycle?)".to_string(),
1199                ));
1200            }
1201            Ok(match e {
1202                E::Variable(name) => {
1203                    if ctx.alias_variable_exists(name) {
1204                        if !visited.insert(name.clone()) {
1205                            return Err(super::context::CodeGenError::TypeError(format!(
1206                                "alias cycle detected for '{name}'"
1207                            )));
1208                        }
1209                        if let Some(t) = ctx.get_alias_variable(name) {
1210                            let res = expand_aliases(ctx, &t, visited, depth + 1)?;
1211                            visited.remove(name);
1212                            res
1213                        } else {
1214                            e.clone()
1215                        }
1216                    } else {
1217                        e.clone()
1218                    }
1219                }
1220                E::MemberAccess(obj, field) => {
1221                    let base = expand_aliases(ctx, obj, visited, depth + 1)?;
1222                    E::MemberAccess(Box::new(base), field.clone())
1223                }
1224                E::ArrayAccess(arr, idx) => {
1225                    let base = expand_aliases(ctx, arr, visited, depth + 1)?;
1226                    let idx2 = expand_aliases(ctx, idx, visited, depth + 1)?;
1227                    E::ArrayAccess(Box::new(base), Box::new(idx2))
1228                }
1229                E::PointerDeref(inner) => {
1230                    let in2 = expand_aliases(ctx, inner, visited, depth + 1)?;
1231                    E::PointerDeref(Box::new(in2))
1232                }
1233                E::AddressOf(inner) => {
1234                    let in2 = expand_aliases(ctx, inner, visited, depth + 1)?;
1235                    E::AddressOf(Box::new(in2))
1236                }
1237                E::ChainAccess(chain) => {
1238                    if chain.is_empty() {
1239                        return Ok(e.clone());
1240                    }
1241                    let head = &chain[0];
1242                    if ctx.alias_variable_exists(head) {
1243                        if !visited.insert(head.clone()) {
1244                            return Err(super::context::CodeGenError::TypeError(format!(
1245                                "alias cycle detected for '{head}'"
1246                            )));
1247                        }
1248                        if let Some(alias_expr) = ctx.get_alias_variable(head) {
1249                            // Expand the alias head, then append member segments
1250                            let mut acc = expand_aliases(ctx, &alias_expr, visited, depth + 1)?;
1251                            for seg in &chain[1..] {
1252                                acc = E::MemberAccess(Box::new(acc), seg.clone());
1253                            }
1254                            visited.remove(head);
1255                            acc
1256                        } else {
1257                            e.clone()
1258                        }
1259                    } else {
1260                        e.clone()
1261                    }
1262                }
1263                E::BuiltinCall { name, args } => E::BuiltinCall {
1264                    name: name.clone(),
1265                    args: args
1266                        .iter()
1267                        .map(|a| expand_aliases(ctx, a, visited, depth + 1))
1268                        .collect::<std::result::Result<Vec<_>, _>>()?,
1269                },
1270                E::BinaryOp { left, op, right } => E::BinaryOp {
1271                    left: Box::new(expand_aliases(ctx, left, visited, depth + 1)?),
1272                    op: op.clone(),
1273                    right: Box::new(expand_aliases(ctx, right, visited, depth + 1)?),
1274                },
1275                _ => e.clone(),
1276            })
1277        }
1278
1279        let mut visited = std::collections::HashSet::new();
1280        let expanded = expand_aliases(self, expr, &mut visited, 0)?;
1281
1282        match &expanded {
1283            // Simple variable lookup
1284            Expr::Variable(var_name) => self.query_dwarf_for_variable(var_name),
1285
1286            // Member access: obj.field
1287            Expr::MemberAccess(obj_expr, field_name) => {
1288                self.query_dwarf_for_member_access(obj_expr, field_name)
1289            }
1290
1291            // Array access: arr[index]
1292            Expr::ArrayAccess(array_expr, index_expr) => {
1293                self.query_dwarf_for_array_access(array_expr, index_expr)
1294            }
1295
1296            // Chain access: person.name.first
1297            Expr::ChainAccess(chain) => self.query_dwarf_for_chain_access(chain),
1298
1299            // Pointer dereference: *ptr
1300            Expr::PointerDeref(expr) => self.query_dwarf_for_pointer_deref(expr),
1301
1302            // Other expression types are not supported for DWARF queries
1303            _ => Ok(None),
1304        }
1305    }
1306
1307    /// Query DWARF for variable information
1308    pub fn query_dwarf_for_variable(
1309        &mut self,
1310        var_name: &str,
1311    ) -> Result<Option<VariableWithEvaluation>> {
1312        let context = self.get_compile_time_context()?;
1313        let pc_address = context.pc_address;
1314        let module_path = context.module_path.clone();
1315
1316        debug!(
1317            "Querying DWARF for variable '{}' at PC 0x{:x} in module '{}'",
1318            var_name, pc_address, module_path
1319        );
1320
1321        let analyzer = self
1322            .process_analyzer
1323            .ok_or_else(|| CodeGenError::DwarfError("No DWARF analyzer available".to_string()))?;
1324
1325        let module_address = ghostscope_dwarf::ModuleAddress::new(
1326            std::path::PathBuf::from(module_path.clone()),
1327            pc_address,
1328        );
1329
1330        let module_path_owned = module_path;
1331        let lookup_globals = |analyzer: &ghostscope_dwarf::DwarfAnalyzer| -> Result<
1332            Option<(std::path::PathBuf, VariableWithEvaluation)>,
1333        > {
1334            debug!(
1335                "Variable '{}' not found in locals; attempting global lookup",
1336                var_name
1337            );
1338            let matches = analyzer.find_global_variables_by_name(var_name);
1339            if matches.is_empty() {
1340                return Ok(None);
1341            }
1342
1343            // Candidate ranking:
1344            // 1) current module + link address
1345            // 2) any module + link address
1346            // 3) current module
1347            // 4) all matches
1348            let preferred: Vec<(
1349                std::path::PathBuf,
1350                ghostscope_dwarf::core::GlobalVariableInfo,
1351            )> = matches
1352                .iter()
1353                .filter(|(p, _)| p.to_string_lossy() == module_path_owned.as_str())
1354                .cloned()
1355                .collect();
1356            let preferred_with_addr: Vec<(
1357                std::path::PathBuf,
1358                ghostscope_dwarf::core::GlobalVariableInfo,
1359            )> = preferred
1360                .iter()
1361                .filter(|(_, info)| info.link_address.is_some())
1362                .cloned()
1363                .collect();
1364            let with_addr: Vec<(
1365                std::path::PathBuf,
1366                ghostscope_dwarf::core::GlobalVariableInfo,
1367            )> = matches
1368                .iter()
1369                .filter(|(_, info)| info.link_address.is_some())
1370                .cloned()
1371                .collect();
1372
1373            let candidates: Vec<(
1374                std::path::PathBuf,
1375                ghostscope_dwarf::core::GlobalVariableInfo,
1376            )> = if !preferred_with_addr.is_empty() {
1377                preferred_with_addr
1378            } else if !with_addr.is_empty() {
1379                with_addr
1380            } else if !preferred.is_empty() {
1381                preferred
1382            } else {
1383                matches
1384            };
1385
1386            if candidates.len() == 1 {
1387                let (mpath, info) = &candidates[0];
1388                let gv = analyzer
1389                    .resolve_variable_by_offsets_in_module(
1390                        mpath,
1391                        info.unit_offset,
1392                        info.die_offset,
1393                    )
1394                    .map_err(|err| CodeGenError::DwarfError(err.to_string()))?;
1395                return Ok(Some((mpath.clone(), gv)));
1396            }
1397
1398            // Ambiguous candidates: resolve each candidate and prefer the one with a concrete type size.
1399            let mut resolved: Vec<(std::path::PathBuf, VariableWithEvaluation)> = Vec::new();
1400            let mut resolved_with_size: Vec<(std::path::PathBuf, VariableWithEvaluation)> =
1401                Vec::new();
1402            for (mpath, info) in candidates.iter() {
1403                let gv = match analyzer.resolve_variable_by_offsets_in_module(
1404                    mpath,
1405                    info.unit_offset,
1406                    info.die_offset,
1407                ) {
1408                    Ok(v) => v,
1409                    Err(err) => {
1410                        debug!(
1411                            "Skipping unresolved global candidate '{}' in '{}': {}",
1412                            var_name,
1413                            mpath.display(),
1414                            err
1415                        );
1416                        continue;
1417                    }
1418                };
1419                let ty_size = gv.dwarf_type.as_ref().map(|t| t.size()).unwrap_or(0);
1420                if ty_size > 0 {
1421                    resolved_with_size.push((mpath.clone(), gv.clone()));
1422                }
1423                resolved.push((mpath.clone(), gv));
1424            }
1425
1426            if resolved_with_size.len() == 1 {
1427                return Ok(resolved_with_size.into_iter().next());
1428            }
1429
1430            if resolved.len() == 1 {
1431                return Ok(resolved.into_iter().next());
1432            }
1433
1434            let ambiguous_count = if resolved_with_size.len() > 1 {
1435                resolved_with_size.len()
1436            } else if !resolved.is_empty() {
1437                resolved.len()
1438            } else {
1439                candidates.len()
1440            };
1441            debug!("Global '{var_name}' is ambiguous across modules ({ambiguous_count} candidates)");
1442            Err(CodeGenError::DwarfError(format!(
1443                "Ambiguous global '{var_name}': {ambiguous_count} matches"
1444            )))
1445        };
1446
1447        match analyzer.get_all_variables_at_address(&module_address) {
1448            Ok(vars) => {
1449                if let Some(var_result) = vars.iter().find(|v| v.name == var_name).or_else(|| {
1450                    let prefix = format!("{var_name}@");
1451                    vars.iter().find(|v| v.name.starts_with(&prefix))
1452                }) {
1453                    debug!("Found DWARF variable '{}' in locals/params", var_name);
1454                    Ok(Some(var_result.clone()))
1455                } else if let Some((mpath, gv)) = lookup_globals(analyzer)? {
1456                    self.current_resolved_var_module_path =
1457                        Some(mpath.to_string_lossy().to_string());
1458                    Ok(Some(gv))
1459                } else {
1460                    Ok(None)
1461                }
1462            }
1463            Err(e) => {
1464                debug!(
1465                    "DWARF local lookup error for '{}': {e}; falling back to globals",
1466                    var_name
1467                );
1468                if let Some((mpath, gv)) = lookup_globals(analyzer)? {
1469                    self.current_resolved_var_module_path =
1470                        Some(mpath.to_string_lossy().to_string());
1471                    Ok(Some(gv))
1472                } else {
1473                    Ok(None)
1474                }
1475            }
1476        }
1477    }
1478
1479    /// Get DWARF type size in bytes
1480    pub fn get_dwarf_type_size(dwarf_type: &TypeInfo) -> u64 {
1481        match dwarf_type {
1482            TypeInfo::BaseType { size, .. } => *size,
1483            TypeInfo::PointerType { size, .. } => *size,
1484            TypeInfo::ArrayType { total_size, .. } => total_size.unwrap_or(0),
1485            TypeInfo::StructType { size, .. } => *size,
1486            TypeInfo::UnionType { size, .. } => *size,
1487            TypeInfo::EnumType { size, .. } => *size,
1488            TypeInfo::BitfieldType {
1489                underlying_type, ..
1490            } => {
1491                // Read size equals the storage type size
1492                Self::get_dwarf_type_size(underlying_type)
1493            }
1494            TypeInfo::TypedefType {
1495                underlying_type, ..
1496            } => Self::get_dwarf_type_size(underlying_type),
1497            TypeInfo::QualifiedType {
1498                underlying_type, ..
1499            } => Self::get_dwarf_type_size(underlying_type),
1500            TypeInfo::FunctionType { .. } => 8, // Function pointer size
1501            TypeInfo::UnknownType { .. } => 0,
1502            TypeInfo::OptimizedOut { .. } => 0, // Optimized out has no size
1503        }
1504    }
1505
1506    /// Query DWARF for member access (obj.field)
1507    pub fn query_dwarf_for_member_access(
1508        &mut self,
1509        obj_expr: &crate::script::Expr,
1510        field_name: &str,
1511    ) -> Result<Option<VariableWithEvaluation>> {
1512        // Generic path: try to resolve the base expression first and add constant member offset
1513        if !matches!(obj_expr, crate::script::Expr::Variable(_)) {
1514            if let Some(base_var) = self.query_dwarf_for_complex_expr(obj_expr)? {
1515                if let Some(base_ty) = base_var.dwarf_type.as_ref() {
1516                    fn find_member_offset_and_type(
1517                        t: &ghostscope_dwarf::TypeInfo,
1518                        field: &str,
1519                    ) -> Option<(u64, ghostscope_dwarf::TypeInfo)> {
1520                        match t {
1521                            ghostscope_dwarf::TypeInfo::StructType { members, .. }
1522                            | ghostscope_dwarf::TypeInfo::UnionType { members, .. } => {
1523                                for m in members {
1524                                    if m.name == field {
1525                                        return Some((m.offset, m.member_type.clone()));
1526                                    }
1527                                }
1528                                None
1529                            }
1530                            ghostscope_dwarf::TypeInfo::TypedefType {
1531                                underlying_type, ..
1532                            }
1533                            | ghostscope_dwarf::TypeInfo::QualifiedType {
1534                                underlying_type, ..
1535                            } => find_member_offset_and_type(underlying_type, field),
1536                            _ => None,
1537                        }
1538                    }
1539                    // Optional auto-deref for pointer-to-aggregate
1540                    let mut effective_ty = base_ty.clone();
1541                    let mut effective_eval = base_var.evaluation_result.clone();
1542                    // unwrap typedef/qualifier for pointer detection
1543                    fn unwrap_typedef(
1544                        mut t: &ghostscope_dwarf::TypeInfo,
1545                    ) -> &ghostscope_dwarf::TypeInfo {
1546                        while let ghostscope_dwarf::TypeInfo::TypedefType {
1547                            underlying_type, ..
1548                        }
1549                        | ghostscope_dwarf::TypeInfo::QualifiedType {
1550                            underlying_type,
1551                            ..
1552                        } = t
1553                        {
1554                            t = underlying_type.as_ref();
1555                        }
1556                        t
1557                    }
1558                    let unwrapped = unwrap_typedef(&effective_ty);
1559                    if let ghostscope_dwarf::TypeInfo::PointerType { target_type, .. } = unwrapped {
1560                        // Insert a dereference step into evaluation
1561                        effective_eval = self.compute_pointer_dereference(&effective_eval)?;
1562                        effective_ty = *target_type.clone();
1563                    }
1564
1565                    if let Some((member_off, member_ty)) =
1566                        find_member_offset_and_type(&effective_ty, field_name)
1567                    {
1568                        use ghostscope_dwarf::{
1569                            ComputeStep as CS, EvaluationResult as ER, LocationResult as LR,
1570                        };
1571                        let new_eval = match &effective_eval {
1572                            ER::MemoryLocation(LR::Address(a)) => {
1573                                ER::MemoryLocation(LR::Address(a + member_off))
1574                            }
1575                            ER::MemoryLocation(LR::ComputedLocation { steps }) => {
1576                                let mut s = steps.clone();
1577                                s.push(CS::PushConstant(member_off as i64));
1578                                s.push(CS::Add);
1579                                ER::MemoryLocation(LR::ComputedLocation { steps: s })
1580                            }
1581                            ER::MemoryLocation(LR::RegisterAddress {
1582                                register,
1583                                offset,
1584                                size,
1585                            }) => {
1586                                let new_off = offset.unwrap_or(0).saturating_add(member_off as i64);
1587                                ER::MemoryLocation(LR::RegisterAddress {
1588                                    register: *register,
1589                                    offset: Some(new_off),
1590                                    size: *size,
1591                                })
1592                            }
1593                            _ => {
1594                                return Err(CodeGenError::NotImplemented(
1595                                    "Member access on non-addressable expression".to_string(),
1596                                ))
1597                            }
1598                        };
1599                        let name = format!("{}.{}", base_var.name, field_name);
1600                        let v = VariableWithEvaluation {
1601                            name,
1602                            type_name: member_ty.type_name(),
1603                            dwarf_type: Some(member_ty),
1604                            evaluation_result: new_eval,
1605                            scope_depth: base_var.scope_depth,
1606                            is_parameter: base_var.is_parameter,
1607                            is_artificial: base_var.is_artificial,
1608                        };
1609                        return Ok(Some(v));
1610                    }
1611                }
1612            }
1613            // Try a planner-based chain if generic path missed and base is a pure identifier chain
1614            {
1615                fn flatten_ident_chain<'a>(
1616                    e: &'a crate::script::Expr,
1617                    out: &mut Vec<&'a str>,
1618                ) -> bool {
1619                    match e {
1620                        crate::script::Expr::Variable(name) => {
1621                            out.push(name.as_str());
1622                            true
1623                        }
1624                        crate::script::Expr::MemberAccess(obj, field) => {
1625                            if flatten_ident_chain(obj, out) {
1626                                out.push(field.as_str());
1627                                true
1628                            } else {
1629                                false
1630                            }
1631                        }
1632                        _ => false,
1633                    }
1634                }
1635                let mut segs: Vec<&str> = Vec::new();
1636                if flatten_ident_chain(obj_expr, &mut segs) && !segs.is_empty() {
1637                    let mut chain: Vec<String> = segs.into_iter().map(|s| s.to_string()).collect();
1638                    chain.push(field_name.to_string());
1639                    return self.query_dwarf_for_chain_access(&chain);
1640                }
1641            }
1642            // fall through to legacy variable-only behavior
1643        }
1644        // Support simple variable base and fall back to global/static lowering
1645        if let crate::script::Expr::Variable(base_name) = obj_expr {
1646            let ctx = self.get_compile_time_context()?;
1647            let module_path = ctx.module_path.clone();
1648            let pc_address = ctx.pc_address;
1649            let analyzer = self.process_analyzer.ok_or_else(|| {
1650                CodeGenError::DwarfError("No DWARF analyzer available".to_string())
1651            })?;
1652            let module_address = ghostscope_dwarf::ModuleAddress::new(
1653                std::path::PathBuf::from(module_path.clone()),
1654                pc_address,
1655            );
1656            // Try current module at PC first
1657            match analyzer.plan_chain_access(&module_address, base_name, &[field_name.to_string()])
1658            {
1659                Ok(Some(var)) => return Ok(Some(var)),
1660                Ok(None) => {}
1661                Err(e) => {
1662                    tracing::debug!("member planner miss at current module: {}", e);
1663                }
1664            }
1665
1666            // Strict cross-module chain planning via analyzer API
1667            match analyzer
1668                .plan_global_chain_access(
1669                    &std::path::PathBuf::from(module_path.clone()),
1670                    base_name,
1671                    &[field_name.to_string()],
1672                )
1673                .map_err(|e| CodeGenError::DwarfError(e.to_string()))?
1674            {
1675                Some((mpath, v)) => {
1676                    self.current_resolved_var_module_path =
1677                        Some(mpath.to_string_lossy().to_string());
1678                    Ok(Some(v))
1679                }
1680                None => {
1681                    // Friendly unknown-member message for globals: try to resolve the base's type
1682                    // and list available members.
1683                    // We only attempt this for globals to avoid scanning locals aggressively.
1684                    let mut matches = analyzer.find_global_variables_by_name(base_name);
1685                    if !matches.is_empty() {
1686                        // Prefer current module
1687                        let preferred: Vec<(
1688                            std::path::PathBuf,
1689                            ghostscope_dwarf::core::GlobalVariableInfo,
1690                        )> = matches
1691                            .iter()
1692                            .filter(|(p, _)| p.to_string_lossy() == module_path.as_str())
1693                            .cloned()
1694                            .collect();
1695                        let chosen = if preferred.len() == 1 {
1696                            Some(preferred[0].clone())
1697                        } else if preferred.is_empty() && matches.len() == 1 {
1698                            Some(matches.remove(0))
1699                        } else {
1700                            None
1701                        };
1702                        if let Some((mp, info)) = chosen {
1703                            if let Ok(var) = analyzer.resolve_variable_by_offsets_in_module(
1704                                &mp,
1705                                info.unit_offset,
1706                                info.die_offset,
1707                            ) {
1708                                if let Some(ty) = var.dwarf_type.as_ref() {
1709                                    // Unwrap aliases
1710                                    let mut t = ty;
1711                                    loop {
1712                                        match t {
1713                                            ghostscope_dwarf::TypeInfo::TypedefType {
1714                                                underlying_type,
1715                                                ..
1716                                            } => t = underlying_type.as_ref(),
1717                                            ghostscope_dwarf::TypeInfo::QualifiedType {
1718                                                underlying_type,
1719                                                ..
1720                                            } => t = underlying_type.as_ref(),
1721                                            _ => break,
1722                                        }
1723                                    }
1724                                    let mut kind: Option<&'static str> = None;
1725                                    let mut member_names: Vec<String> = Vec::new();
1726                                    match t {
1727                                        ghostscope_dwarf::TypeInfo::StructType {
1728                                            members, ..
1729                                        } => {
1730                                            kind = Some("struct");
1731                                            member_names =
1732                                                members.iter().map(|m| m.name.clone()).collect();
1733                                        }
1734                                        ghostscope_dwarf::TypeInfo::UnionType {
1735                                            members, ..
1736                                        } => {
1737                                            kind = Some("union");
1738                                            member_names =
1739                                                members.iter().map(|m| m.name.clone()).collect();
1740                                        }
1741                                        _ => {}
1742                                    }
1743                                    if let Some(k) = kind {
1744                                        // Form friendly message consistent with tests
1745                                        // Example: Unknown member 'no_such_member' in struct 'G_STATE'. Known members: a, b, c
1746                                        member_names.sort();
1747                                        member_names.dedup();
1748                                        let list = if member_names.is_empty() {
1749                                            "<none>".to_string()
1750                                        } else {
1751                                            member_names.join(", ")
1752                                        };
1753                                        let msg = format!(
1754                                            "Unknown member '{field_name}' in {k} '{base_name}' (known members: {list})"
1755                                        );
1756                                        return Err(CodeGenError::TypeError(msg));
1757                                    }
1758                                }
1759                            }
1760                        }
1761                    }
1762                    Ok(None)
1763                }
1764            }
1765        } else {
1766            Err(CodeGenError::NotImplemented(
1767                "MemberAccess base must be a simple variable (use chain access)".to_string(),
1768            ))
1769        }
1770    }
1771
1772    /// Query DWARF for array access (arr[index])
1773    pub fn query_dwarf_for_array_access(
1774        &mut self,
1775        array_expr: &crate::script::Expr,
1776        index_expr: &crate::script::Expr,
1777    ) -> Result<Option<VariableWithEvaluation>> {
1778        // Prefer planner for simple identifier chains like a.b.c as array base to avoid nested member lookups
1779        if let crate::script::Expr::MemberAccess(_, _) = array_expr {
1780            // Try to flatten to a chain of identifiers
1781            fn flatten_chain<'a>(e: &'a crate::script::Expr, out: &mut Vec<&'a str>) -> bool {
1782                match e {
1783                    crate::script::Expr::Variable(name) => {
1784                        out.push(name.as_str());
1785                        true
1786                    }
1787                    crate::script::Expr::MemberAccess(obj, field) => {
1788                        if flatten_chain(obj, out) {
1789                            out.push(field.as_str());
1790                            true
1791                        } else {
1792                            false
1793                        }
1794                    }
1795                    _ => false,
1796                }
1797            }
1798            let mut segs: Vec<&str> = Vec::new();
1799            if flatten_chain(array_expr, &mut segs) && !segs.is_empty() {
1800                let ctx = self.get_compile_time_context()?;
1801                let module_path = ctx.module_path.clone();
1802                let pc_address = ctx.pc_address;
1803                let analyzer = self.process_analyzer.ok_or_else(|| {
1804                    CodeGenError::DwarfError("No DWARF analyzer available".to_string())
1805                })?;
1806                let module_address = ghostscope_dwarf::ModuleAddress::new(
1807                    std::path::PathBuf::from(module_path),
1808                    pc_address,
1809                );
1810                let base = segs[0].to_string();
1811                let rest: Vec<String> = segs[1..].iter().map(|s| s.to_string()).collect();
1812                if let Ok(Some(var)) = analyzer.plan_chain_access(&module_address, &base, &rest) {
1813                    // Use planner result as array base
1814                    let base_var = var;
1815                    return self.finish_array_access_from_base(base_var, index_expr);
1816                }
1817            }
1818        }
1819
1820        // Fallback: resolve the base array via generic complex expr path
1821        let base_var = match self.query_dwarf_for_complex_expr(array_expr)? {
1822            Some(var) => var,
1823            None => return Ok(None),
1824        };
1825
1826        self.finish_array_access_from_base(base_var, index_expr)
1827    }
1828
1829    fn finish_array_access_from_base(
1830        &mut self,
1831        base_var: VariableWithEvaluation,
1832        index_expr: &crate::script::Expr,
1833    ) -> Result<Option<VariableWithEvaluation>> {
1834        // Get the array's type
1835        let array_type = match &base_var.dwarf_type {
1836            Some(type_info) => type_info,
1837            None => return Ok(None),
1838        };
1839
1840        // Extract element type from array type
1841        let element_type = match array_type {
1842            TypeInfo::ArrayType { element_type, .. } => element_type.as_ref().clone(),
1843            _ => return Ok(None), // Not an array type
1844        };
1845
1846        // Calculate element size for address computation
1847        let element_size = element_type.size();
1848
1849        // For indexing, create a computed location representing: base + (index * element_size)
1850        // Only literal integer indices are supported at this stage
1851        let index_value: i64 = match index_expr {
1852            crate::script::Expr::Int(v) => *v,
1853            _ => {
1854                return Err(CodeGenError::NotImplemented(
1855                    "Only literal integer array indices are supported (TODO)".to_string(),
1856                ))
1857            }
1858        };
1859        let element_evaluation_result = match &base_var.evaluation_result {
1860            EvaluationResult::DirectValue(_) => {
1861                // If base is a value, we can't do array indexing
1862                return Ok(None);
1863            }
1864            EvaluationResult::MemoryLocation(location) => {
1865                match location {
1866                    // Address(base): perform Address arithmetic so ASLR logic applies uniformly
1867                    LocationResult::Address(addr) => {
1868                        let offs = (index_value as i128) * (element_size as i128);
1869                        let new_addr = (*addr as i128).saturating_add(offs);
1870                        if new_addr < 0 {
1871                            return Err(CodeGenError::LLVMError(
1872                                "negative address after indexing".to_string(),
1873                            ));
1874                        }
1875                        EvaluationResult::MemoryLocation(LocationResult::Address(new_addr as u64))
1876                    }
1877                    // Register/Computed: build compute steps at runtime
1878                    _ => {
1879                        let array_access_steps =
1880                            self.create_array_access_steps(location, element_size, index_value);
1881                        EvaluationResult::MemoryLocation(LocationResult::ComputedLocation {
1882                            steps: array_access_steps,
1883                        })
1884                    }
1885                }
1886            }
1887            EvaluationResult::Optimized => {
1888                return Ok(None);
1889            }
1890            EvaluationResult::Composite(_) => {
1891                // Array access on composite locations is complex, skip for now
1892                return Ok(None);
1893            }
1894        };
1895
1896        // Build readable element name: base_name[index]
1897        let elem_name = format!("{}[{}]", base_var.name, index_value);
1898        let element_var = VariableWithEvaluation {
1899            name: elem_name,
1900            type_name: Self::type_info_to_name(&element_type),
1901            dwarf_type: Some(element_type),
1902            evaluation_result: element_evaluation_result,
1903            scope_depth: base_var.scope_depth,
1904            is_parameter: false,
1905            is_artificial: false,
1906        };
1907
1908        Ok(Some(element_var))
1909    }
1910
1911    /// Query DWARF for chain access (person.name.first)
1912    pub fn query_dwarf_for_chain_access(
1913        &mut self,
1914        chain: &[String],
1915    ) -> Result<Option<VariableWithEvaluation>> {
1916        if chain.is_empty() {
1917            return Ok(None);
1918        }
1919        // If chain has only one element, treat it as a simple variable and reuse variable lookup.
1920        if chain.len() == 1 {
1921            return self.query_dwarf_for_variable(&chain[0]);
1922        }
1923        // Planner path only; do not fallback. If planning fails, surface an error.
1924        let ctx = self.get_compile_time_context()?;
1925        let module_path = ctx.module_path.clone();
1926        let pc_address = ctx.pc_address;
1927        let analyzer = self
1928            .process_analyzer
1929            .ok_or_else(|| CodeGenError::DwarfError("No DWARF analyzer available".to_string()))?;
1930        // First attempt: current module at current PC (locals/params)
1931        let module_address = ghostscope_dwarf::ModuleAddress::new(
1932            std::path::PathBuf::from(module_path.clone()),
1933            pc_address,
1934        );
1935        match analyzer.plan_chain_access(&module_address, &chain[0], &chain[1..]) {
1936            Ok(Some(var)) => return Ok(Some(var)),
1937            Ok(None) => {}
1938            Err(e) => {
1939                // Treat planner errors as a miss and continue to global fallback
1940                tracing::debug!("chain planner miss at current module: {}", e);
1941            }
1942        }
1943
1944        let base = &chain[0];
1945        let rest = &chain[1..];
1946        match analyzer
1947            .plan_global_chain_access(&std::path::PathBuf::from(module_path.clone()), base, rest)
1948            .map_err(|e| CodeGenError::DwarfError(e.to_string()))?
1949        {
1950            Some((mpath, v)) => {
1951                self.current_resolved_var_module_path = Some(mpath.to_string_lossy().to_string());
1952                Ok(Some(v))
1953            }
1954            None => {
1955                // Friendly message for unknown member on global in simple two-segment chains
1956                if chain.len() == 2 {
1957                    let field_name = &chain[1];
1958                    let mut matches = analyzer.find_global_variables_by_name(base);
1959                    if !matches.is_empty() {
1960                        let preferred: Vec<(
1961                            std::path::PathBuf,
1962                            ghostscope_dwarf::core::GlobalVariableInfo,
1963                        )> = matches
1964                            .iter()
1965                            .filter(|(p, _)| p.to_string_lossy() == module_path.as_str())
1966                            .cloned()
1967                            .collect();
1968                        let chosen = if preferred.len() == 1 {
1969                            Some(preferred[0].clone())
1970                        } else if preferred.is_empty() && matches.len() == 1 {
1971                            Some(matches.remove(0))
1972                        } else {
1973                            None
1974                        };
1975                        if let Some((mp, info)) = chosen {
1976                            if let Ok(var) = analyzer.resolve_variable_by_offsets_in_module(
1977                                &mp,
1978                                info.unit_offset,
1979                                info.die_offset,
1980                            ) {
1981                                if let Some(ty) = var.dwarf_type.as_ref() {
1982                                    // Unwrap typedef/qualified
1983                                    let mut t = ty;
1984                                    loop {
1985                                        match t {
1986                                            ghostscope_dwarf::TypeInfo::TypedefType {
1987                                                underlying_type,
1988                                                ..
1989                                            } => t = underlying_type.as_ref(),
1990                                            ghostscope_dwarf::TypeInfo::QualifiedType {
1991                                                underlying_type,
1992                                                ..
1993                                            } => t = underlying_type.as_ref(),
1994                                            _ => break,
1995                                        }
1996                                    }
1997                                    let mut kind: Option<&'static str> = None;
1998                                    let mut member_names: Vec<String> = Vec::new();
1999                                    match t {
2000                                        ghostscope_dwarf::TypeInfo::StructType {
2001                                            members, ..
2002                                        } => {
2003                                            kind = Some("struct");
2004                                            member_names =
2005                                                members.iter().map(|m| m.name.clone()).collect();
2006                                        }
2007                                        ghostscope_dwarf::TypeInfo::UnionType {
2008                                            members, ..
2009                                        } => {
2010                                            kind = Some("union");
2011                                            member_names =
2012                                                members.iter().map(|m| m.name.clone()).collect();
2013                                        }
2014                                        _ => {}
2015                                    }
2016                                    if let Some(k) = kind {
2017                                        member_names.sort();
2018                                        member_names.dedup();
2019                                        let list = if member_names.is_empty() {
2020                                            "<none>".to_string()
2021                                        } else {
2022                                            member_names.join(", ")
2023                                        };
2024                                        let msg = format!(
2025                                            "Unknown member '{field_name}' in {k} '{base}' (known members: {list})"
2026                                        );
2027                                        return Err(CodeGenError::TypeError(msg));
2028                                    }
2029                                }
2030                            }
2031                        }
2032                    }
2033                }
2034                Ok(None)
2035            }
2036        }
2037        // unreachable
2038    }
2039
2040    /// Query DWARF for pointer dereference (*ptr)
2041    pub fn query_dwarf_for_pointer_deref(
2042        &mut self,
2043        expr: &crate::script::Expr,
2044    ) -> Result<Option<VariableWithEvaluation>> {
2045        // First, resolve the pointer expression
2046        let ptr_var = match self.query_dwarf_for_complex_expr(expr)? {
2047            Some(var) => var,
2048            None => return Ok(None),
2049        };
2050
2051        // Get the pointer's type
2052        let ptr_type = match &ptr_var.dwarf_type {
2053            Some(type_info) => type_info,
2054            None => return Ok(None),
2055        };
2056
2057        // Extract pointed-to type from pointer type
2058        let mut pointed_type = match ptr_type {
2059            TypeInfo::PointerType { target_type, .. } => target_type.as_ref().clone(),
2060            _ => return Ok(None), // Not a pointer type
2061        };
2062
2063        // Upgrade UnknownType(target_name) using analyzer/type index to get a shallow type.
2064        // 1) Struct/union/class/enum: try analyzer shallow lookup by name (module-scoped first)
2065        // 2) Do not guess builtin type sizes — rely only on DWARF base type entries
2066        if let TypeInfo::UnknownType { name } = &pointed_type {
2067            let mut candidate_names: Vec<String> = Vec::new();
2068            if !name.is_empty() && name != "void" {
2069                candidate_names.push(name.clone());
2070            }
2071            // Fallback: derive from pointer variable's pretty type name, e.g., "GlobalState*" => "GlobalState"
2072            if candidate_names.is_empty() {
2073                let tn = ptr_var.type_name.trim().to_string();
2074                if let Some(idx) = tn.find('*') {
2075                    let mut base = tn[..idx].trim().to_string();
2076                    // Strip common qualifiers and tags
2077                    for prefix in [
2078                        "const ",
2079                        "volatile ",
2080                        "restrict ",
2081                        "struct ",
2082                        "class ",
2083                        "union ",
2084                    ] {
2085                        if base.starts_with(prefix) {
2086                            base = base[prefix.len()..].trim().to_string();
2087                        }
2088                    }
2089                    if !base.is_empty() && base != "void" {
2090                        candidate_names.push(base);
2091                    }
2092                }
2093            }
2094            let ctx = self.get_compile_time_context()?;
2095            let module_path = ctx.module_path.clone();
2096            if let Some(analyzer) = self.process_analyzer {
2097                let mut alias_used: Option<String> = None;
2098                for n in candidate_names {
2099                    // Prefer cross-module definitions first to avoid forward decls with size=0 in current CU
2100                    let mut upgraded: Option<TypeInfo> = None;
2101                    // struct/class
2102                    if let Some(ti) = analyzer.resolve_struct_type_shallow_by_name(&n) {
2103                        if ti.size() > 0 {
2104                            upgraded = Some(ti);
2105                        }
2106                    }
2107                    if upgraded.is_none() {
2108                        if let Some(ti) =
2109                            analyzer.resolve_struct_type_shallow_by_name_in_module(&module_path, &n)
2110                        {
2111                            if ti.size() > 0 {
2112                                upgraded = Some(ti);
2113                            }
2114                        }
2115                    }
2116                    // union
2117                    if upgraded.is_none() {
2118                        if let Some(ti) = analyzer.resolve_union_type_shallow_by_name(&n) {
2119                            if ti.size() > 0 {
2120                                upgraded = Some(ti);
2121                            }
2122                        }
2123                    }
2124                    if upgraded.is_none() {
2125                        if let Some(ti) =
2126                            analyzer.resolve_union_type_shallow_by_name_in_module(&module_path, &n)
2127                        {
2128                            if ti.size() > 0 {
2129                                upgraded = Some(ti);
2130                            }
2131                        }
2132                    }
2133                    // enum
2134                    if upgraded.is_none() {
2135                        if let Some(ti) = analyzer.resolve_enum_type_shallow_by_name(&n) {
2136                            if ti.size() > 0 {
2137                                upgraded = Some(ti);
2138                            }
2139                        }
2140                    }
2141                    if upgraded.is_none() {
2142                        if let Some(ti) =
2143                            analyzer.resolve_enum_type_shallow_by_name_in_module(&module_path, &n)
2144                        {
2145                            if ti.size() > 0 {
2146                                upgraded = Some(ti);
2147                            }
2148                        }
2149                    }
2150                    if let Some(ti) = upgraded {
2151                        pointed_type = ti;
2152                        alias_used = Some(n.clone());
2153                        break;
2154                    }
2155                }
2156
2157                // If we upgraded to an aggregate and have an alias name, wrap it as a typedef
2158                if let Some(alias) = alias_used {
2159                    match &pointed_type {
2160                        TypeInfo::StructType { .. }
2161                        | TypeInfo::UnionType { .. }
2162                        | TypeInfo::EnumType { .. } => {
2163                            pointed_type = TypeInfo::TypedefType {
2164                                name: alias,
2165                                underlying_type: Box::new(pointed_type.clone()),
2166                            };
2167                        }
2168                        _ => {}
2169                    }
2170                }
2171            }
2172        }
2173
2174        // Create dereferenced variable
2175        let deref_var = VariableWithEvaluation {
2176            name: format!("*{}", Self::expr_to_string(expr)),
2177            type_name: Self::type_info_to_name(&pointed_type),
2178            dwarf_type: Some(pointed_type),
2179            evaluation_result: self.compute_pointer_dereference(&ptr_var.evaluation_result)?,
2180            scope_depth: ptr_var.scope_depth,
2181            is_parameter: false,
2182            is_artificial: false,
2183        };
2184
2185        Ok(Some(deref_var))
2186    }
2187
2188    /// Helper: Compute pointer dereference
2189    fn compute_pointer_dereference(
2190        &self,
2191        ptr_result: &EvaluationResult,
2192    ) -> Result<EvaluationResult> {
2193        use ghostscope_dwarf::{ComputeStep, LocationResult, MemoryAccessSize};
2194
2195        match ptr_result {
2196            // If the pointer is a memory location, we need to read that location first,
2197            // then use the result as an address for another read
2198            EvaluationResult::MemoryLocation(location) => {
2199                let steps = [
2200                    self.location_to_compute_steps(location),
2201                    // Then dereference the pointer (read from the computed address)
2202                    vec![ComputeStep::Dereference {
2203                        size: MemoryAccessSize::U64,
2204                    }],
2205                ]
2206                .concat();
2207
2208                Ok(EvaluationResult::MemoryLocation(
2209                    LocationResult::ComputedLocation { steps },
2210                ))
2211            }
2212            // If the pointer value is held directly (common for function parameters)
2213            // interpret the value as an address to the pointed-to object.
2214            EvaluationResult::DirectValue(dv) => {
2215                use ghostscope_dwarf::DirectValueResult as DV;
2216                match dv {
2217                    DV::RegisterValue(reg) => Ok(EvaluationResult::MemoryLocation(
2218                        LocationResult::RegisterAddress {
2219                            register: *reg,
2220                            offset: None,
2221                            size: None,
2222                        },
2223                    )),
2224                    DV::Constant(val) => Ok(EvaluationResult::MemoryLocation(
2225                        LocationResult::Address(*val as u64),
2226                    )),
2227                    DV::AbsoluteAddress(val) => Ok(EvaluationResult::MemoryLocation(
2228                        LocationResult::Address(*val),
2229                    )),
2230                    DV::ImplicitValue(bytes) => {
2231                        // Assemble up to 8 bytes little-endian into u64
2232                        let mut v: u64 = 0;
2233                        for (i, b) in bytes.iter().take(8).enumerate() {
2234                            v |= (*b as u64) << (8 * i);
2235                        }
2236                        Ok(EvaluationResult::MemoryLocation(LocationResult::Address(v)))
2237                    }
2238                    DV::ComputedValue { steps, .. } => Ok(EvaluationResult::MemoryLocation(
2239                        LocationResult::ComputedLocation {
2240                            steps: steps.clone(),
2241                        },
2242                    )),
2243                }
2244            }
2245            _ => Err(CodeGenError::NotImplemented(
2246                "Unsupported pointer dereference scenario".to_string(),
2247            )),
2248        }
2249    }
2250
2251    /// Helper: Convert location to compute steps
2252    fn location_to_compute_steps(&self, location: &LocationResult) -> Vec<ComputeStep> {
2253        use ghostscope_dwarf::{ComputeStep, LocationResult};
2254
2255        match location {
2256            LocationResult::Address(addr) => {
2257                vec![ComputeStep::PushConstant(*addr as i64)]
2258            }
2259            LocationResult::RegisterAddress {
2260                register, offset, ..
2261            } => {
2262                let mut steps = vec![ComputeStep::LoadRegister(*register)];
2263                if let Some(offset) = offset {
2264                    steps.push(ComputeStep::PushConstant(*offset));
2265                    steps.push(ComputeStep::Add);
2266                }
2267                steps
2268            }
2269            LocationResult::ComputedLocation { steps } => steps.clone(),
2270        }
2271    }
2272
2273    /// Helper: Convert expression to string for debugging
2274    fn expr_to_string(expr: &crate::script::Expr) -> String {
2275        use crate::script::Expr;
2276
2277        match expr {
2278            Expr::Variable(name) => name.clone(),
2279            Expr::MemberAccess(obj, field) => format!("{}.{}", Self::expr_to_string(obj), field),
2280            Expr::ArrayAccess(arr, _) => format!("{}[index]", Self::expr_to_string(arr)),
2281            Expr::ChainAccess(chain) => chain.join("."),
2282            Expr::PointerDeref(expr) => format!("*{}", Self::expr_to_string(expr)),
2283            _ => "expr".to_string(),
2284        }
2285    }
2286
2287    /// Helper: Extract readable name from TypeInfo
2288    fn type_info_to_name(type_info: &TypeInfo) -> String {
2289        match type_info {
2290            TypeInfo::BaseType { name, .. } => name.clone(),
2291            TypeInfo::PointerType { target_type, .. } => {
2292                format!("{}*", Self::type_info_to_name(target_type))
2293            }
2294            TypeInfo::ArrayType {
2295                element_type,
2296                element_count,
2297                ..
2298            } => {
2299                if let Some(count) = element_count {
2300                    format!("{}[{}]", Self::type_info_to_name(element_type), count)
2301                } else {
2302                    format!("{}[]", Self::type_info_to_name(element_type))
2303                }
2304            }
2305            TypeInfo::StructType { name, .. } => format!("struct {name}"),
2306            TypeInfo::UnionType { name, .. } => format!("union {name}"),
2307            TypeInfo::EnumType { name, .. } => format!("enum {name}"),
2308            TypeInfo::BitfieldType {
2309                underlying_type,
2310                bit_offset,
2311                bit_size,
2312            } => {
2313                format!(
2314                    "bitfield<{}:{}> {}",
2315                    bit_offset,
2316                    bit_size,
2317                    Self::type_info_to_name(underlying_type)
2318                )
2319            }
2320            TypeInfo::TypedefType { name, .. } => name.clone(),
2321            TypeInfo::QualifiedType {
2322                underlying_type, ..
2323            } => Self::type_info_to_name(underlying_type),
2324            TypeInfo::FunctionType { .. } => "function".to_string(),
2325            TypeInfo::UnknownType { name } => name.clone(),
2326            TypeInfo::OptimizedOut { name } => format!("<optimized_out> {name}"),
2327        }
2328    }
2329
2330    /// Create computation steps for array access: base_address + (index * element_size)
2331    fn create_array_access_steps(
2332        &self,
2333        base_location: &LocationResult,
2334        element_size: u64,
2335        index: i64,
2336    ) -> Vec<ComputeStep> {
2337        let mut steps = Vec::new();
2338
2339        // First, get the base address computation steps
2340        match base_location {
2341            LocationResult::Address(addr) => {
2342                steps.push(ComputeStep::PushConstant(*addr as i64));
2343            }
2344            LocationResult::RegisterAddress {
2345                register, offset, ..
2346            } => {
2347                steps.push(ComputeStep::LoadRegister(*register));
2348                if let Some(offset) = offset {
2349                    if *offset != 0 {
2350                        steps.push(ComputeStep::PushConstant(*offset));
2351                        steps.push(ComputeStep::Add);
2352                    }
2353                }
2354            }
2355            LocationResult::ComputedLocation { steps: base_steps } => {
2356                steps.extend(base_steps.clone());
2357            }
2358        }
2359
2360        // Now add array indexing computation: current_address + (index * element_size)
2361        steps.push(ComputeStep::PushConstant(index)); // literal index
2362        steps.push(ComputeStep::PushConstant(element_size as i64)); // element_size
2363        steps.push(ComputeStep::Mul); // index * element_size
2364        steps.push(ComputeStep::Add); // base_address + (index * element_size)
2365
2366        steps
2367    }
2368
2369    /// Compute a typed pointed-to location for expressions like `ptr +/- K` where K is an element index.
2370    /// Returns a computed location EvaluationResult along with the pointed-to DWARF type.
2371    /// The offset is scaled by the element size of the pointer/array target type.
2372    pub fn compute_pointed_location_with_index(
2373        &mut self,
2374        ptr_expr: &crate::script::Expr,
2375        index: i64,
2376    ) -> Result<(EvaluationResult, TypeInfo)> {
2377        use ghostscope_dwarf::{
2378            ComputeStep, EvaluationResult as ER, LocationResult as LR, TypeInfo,
2379        };
2380
2381        // Resolve the pointer expression via DWARF
2382        let ptr_var = self
2383            .query_dwarf_for_complex_expr(ptr_expr)?
2384            .ok_or_else(|| CodeGenError::VariableNotFound(format!("{ptr_expr:?}")))?;
2385
2386        let ptr_ty = ptr_var.dwarf_type.as_ref().ok_or_else(|| {
2387            CodeGenError::DwarfError("Expression has no DWARF type information".to_string())
2388        })?;
2389
2390        // Unwrap typedef/qualified wrappers
2391        let mut ty = ptr_ty;
2392        loop {
2393            match ty {
2394                TypeInfo::TypedefType {
2395                    underlying_type, ..
2396                } => ty = underlying_type.as_ref(),
2397                TypeInfo::QualifiedType {
2398                    underlying_type, ..
2399                } => ty = underlying_type.as_ref(),
2400                _ => break,
2401            }
2402        }
2403
2404        // Extract pointed-to (element) type and element size
2405        let (elem_ty, elem_size) = match ty {
2406            TypeInfo::PointerType { target_type, .. } => {
2407                let et = target_type.as_ref().clone();
2408                let es = et.size();
2409                let es = if es == 0 { 1 } else { es };
2410                (et, es)
2411            }
2412            TypeInfo::ArrayType { element_type, .. } => {
2413                let et = element_type.as_ref().clone();
2414                let es = et.size();
2415                let es = if es == 0 { 1 } else { es };
2416                (et, es)
2417            }
2418            TypeInfo::FunctionType { .. } => {
2419                return Err(CodeGenError::TypeError(
2420                    "Pointer arithmetic is not supported on function pointers".to_string(),
2421                ))
2422            }
2423            _ => {
2424                return Err(CodeGenError::TypeError(
2425                    "Pointer arithmetic requires a pointer or array expression".to_string(),
2426                ))
2427            }
2428        };
2429
2430        // First compute the base pointed-to location for `*ptr_expr`
2431        let base_loc_eval = self.compute_pointer_dereference(&ptr_var.evaluation_result)?;
2432        let base_loc = match &base_loc_eval {
2433            ER::MemoryLocation(loc) => loc,
2434            _ => {
2435                return Err(CodeGenError::DwarfError(
2436                    "Failed to compute base location for pointer arithmetic".to_string(),
2437                ))
2438            }
2439        };
2440
2441        // Build compute steps: base_address + index * elem_size
2442        let steps = {
2443            let mut s = self.location_to_compute_steps(base_loc);
2444            // scale index by element size (can be negative)
2445            s.push(ComputeStep::PushConstant(index));
2446            s.push(ComputeStep::PushConstant(elem_size as i64));
2447            s.push(ComputeStep::Mul);
2448            s.push(ComputeStep::Add);
2449            s
2450        };
2451
2452        Ok((ER::MemoryLocation(LR::ComputedLocation { steps }), elem_ty))
2453    }
2454}
2455
2456#[cfg(test)]
2457mod tests {
2458    use super::*;
2459    use inkwell::context::Context as LlvmContext;
2460
2461    #[test]
2462    fn aggregate_address_returns_pointer_for_struct_and_array() {
2463        let llctx = LlvmContext::create();
2464        let opts = crate::CompileOptions::default();
2465        let mut ctx = EbpfContext::new(&llctx, "agg_ptr", Some(0), &opts).expect("ctx");
2466        // Ensure we have a function/pt_regs to satisfy builders
2467        ctx.create_basic_ebpf_function("f").expect("fn");
2468        // Ensure the ASLR offsets map exists in the module for unified codegen path
2469        ctx.__test_ensure_proc_offsets_map().expect("map");
2470        // Allocate per-invocation pm_key on the stack
2471        ctx.__test_alloc_pm_key().expect("pm_key");
2472        // Provide a minimal compile-time context so address rebasing has a module path
2473        ctx.set_compile_time_context(0, "/nonexistent/module".to_string());
2474
2475        // Struct type
2476        let st = ghostscope_protocol::TypeInfo::StructType {
2477            name: "S".to_string(),
2478            size: 80,
2479            members: vec![],
2480        };
2481        let eval = EvaluationResult::MemoryLocation(LocationResult::Address(0x1000));
2482        let v = ctx
2483            .evaluate_result_to_llvm_value(&eval, &st, "S", 0, None)
2484            .expect("eval");
2485        match v {
2486            BasicValueEnum::PointerValue(_) => {}
2487            other => panic!("expected PointerValue for struct, got {other:?}"),
2488        }
2489
2490        // Array type
2491        let arr = ghostscope_protocol::TypeInfo::ArrayType {
2492            element_type: Box::new(ghostscope_protocol::TypeInfo::BaseType {
2493                name: "int".to_string(),
2494                size: 4,
2495                encoding: ghostscope_dwarf::constants::DW_ATE_signed.0 as u16,
2496            }),
2497            element_count: Some(4),
2498            total_size: Some(16),
2499        };
2500        let v2 = ctx
2501            .evaluate_result_to_llvm_value(&eval, &arr, "A", 0, None)
2502            .expect("eval2");
2503        match v2 {
2504            BasicValueEnum::PointerValue(_) => {}
2505            other => panic!("expected PointerValue for array, got {other:?}"),
2506        }
2507    }
2508
2509    #[test]
2510    fn scalar_address_reads_value() {
2511        let llctx = LlvmContext::create();
2512        let opts = crate::CompileOptions::default();
2513        let mut ctx = EbpfContext::new(&llctx, "scalar_val", Some(0), &opts).expect("ctx");
2514        ctx.create_basic_ebpf_function("f").expect("fn");
2515        // Ensure the ASLR offsets map exists in the module for unified codegen path
2516        ctx.__test_ensure_proc_offsets_map().expect("map");
2517        // Allocate per-invocation pm_key on the stack
2518        ctx.__test_alloc_pm_key().expect("pm_key");
2519        // Provide a minimal compile-time context so address rebasing has a module path
2520        ctx.set_compile_time_context(0, "/nonexistent/module".to_string());
2521
2522        // Base int type
2523        let bt = ghostscope_protocol::TypeInfo::BaseType {
2524            name: "int".to_string(),
2525            size: 4,
2526            encoding: ghostscope_dwarf::constants::DW_ATE_signed.0 as u16,
2527        };
2528        let eval = EvaluationResult::MemoryLocation(LocationResult::Address(0x2000));
2529        let v = ctx
2530            .evaluate_result_to_llvm_value(&eval, &bt, "x", 0, None)
2531            .expect("eval");
2532        match v {
2533            BasicValueEnum::IntValue(_) => {}
2534            other => panic!("expected IntValue for scalar, got {other:?}"),
2535        }
2536        assert!(
2537            ctx.module.get_global("_temp_read_buffer_4").is_none(),
2538            "scalar reads should use per-invocation scratch, not shared temp globals"
2539        );
2540    }
2541
2542    #[test]
2543    fn computed_location_supports_dereference_before_trailing_arithmetic() {
2544        let llctx = LlvmContext::create();
2545        let opts = crate::CompileOptions::default();
2546        let mut ctx = EbpfContext::new(&llctx, "computed_addr", Some(0), &opts).expect("ctx");
2547        ctx.create_basic_ebpf_function("f").expect("fn");
2548        ctx.__test_ensure_proc_offsets_map().expect("map");
2549        ctx.__test_alloc_pm_key().expect("pm_key");
2550        ctx.set_compile_time_context(0, "/nonexistent/module".to_string());
2551
2552        let eval = EvaluationResult::MemoryLocation(LocationResult::ComputedLocation {
2553            steps: vec![
2554                ComputeStep::PushConstant(0x3000),
2555                ComputeStep::Dereference {
2556                    size: MemoryAccessSize::U64,
2557                },
2558                ComputeStep::PushConstant(16),
2559                ComputeStep::Add,
2560            ],
2561        });
2562
2563        let addr = ctx
2564            .evaluation_result_to_address_with_hint(&eval, None, None)
2565            .expect("computed address with mid-stream dereference should compile");
2566        assert_eq!(addr.get_type().get_bit_width(), 64);
2567    }
2568}