Skip to main content

ghostscope_compiler/ebpf/
expression.rs

1//! Expression compilation for eBPF code generation
2//!
3//! This module handles compilation of various expression types to LLVM IR.
4
5use super::context::{CodeGenError, EbpfContext, Result, RuntimeAddress};
6use super::expression_plan::{
7    BinaryEmitKind, BinaryIntegerSemantics, BuiltinCallPlan, SpecialVarPlan,
8};
9use crate::script::{BinaryOp, Expr};
10use aya_ebpf_bindings::bindings::bpf_func_id::BPF_FUNC_probe_read_user;
11use ghostscope_dwarf::{
12    AmbiguityReason, Availability, CIntegerComparisonPlan, CIntegerComparisonType,
13    RuntimeRequirement, TypeInfo as DwarfType, TypeLayoutError, UnsupportedReason,
14    VariableReadPlan,
15};
16use inkwell::values::{BasicValueEnum, IntValue, PointerValue};
17use inkwell::AddressSpace;
18use std::path::{Path, PathBuf};
19use tracing::debug;
20
21// compare cap is provided via compile_options.compare_cap (config: ebpf.compare_cap)
22
23#[derive(Clone)]
24pub(super) struct DynamicTypeInfo {
25    pub(super) dwarf_type: DwarfType,
26    pub(super) module_path: Option<PathBuf>,
27}
28
29pub(super) struct DynamicLvalue<'ctx> {
30    pub(super) address: RuntimeAddress<'ctx>,
31    pub(super) type_info: DynamicTypeInfo,
32}
33
34struct IndexableElementInfo {
35    element_type: DwarfType,
36    stride: u64,
37    module_path: Option<PathBuf>,
38}
39
40impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> {
41    pub(crate) fn get_host_pid_tid_values(&mut self) -> Result<(IntValue<'ctx>, IntValue<'ctx>)> {
42        let i32_type = self.context.i32_type();
43        let i64_type = self.context.i64_type();
44
45        // bpf_get_current_pid_tgid() returns:
46        // - high 32 bits: TGID (process ID / getpid() view)
47        // - low 32 bits: PID (thread ID / gettid() view)
48        let host_pid_tgid = self.get_current_pid_tgid()?;
49        let host_tid = self
50            .builder
51            .build_and(
52                host_pid_tgid,
53                i64_type.const_int(0xFFFF_FFFF, false),
54                "host_tid",
55            )
56            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
57        let host_pid = self
58            .builder
59            .build_right_shift(
60                host_pid_tgid,
61                i64_type.const_int(32, false),
62                false,
63                "host_pid",
64            )
65            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
66
67        let host_pid_i32 = self
68            .builder
69            .build_int_truncate(host_pid, i32_type, "host_pid_i32")
70            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
71        let host_tid_i32 = self
72            .builder
73            .build_int_truncate(host_tid, i32_type, "host_tid_i32")
74            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
75
76        Ok((host_pid_i32, host_tid_i32))
77    }
78
79    pub(crate) fn get_special_pid_tid_values(
80        &mut self,
81    ) -> Result<(IntValue<'ctx>, IntValue<'ctx>)> {
82        const BPF_FUNC_GET_NS_CURRENT_PID_TGID: u64 = 120;
83        const BPF_PIDNS_INFO_SIZE: u64 = 8; // struct { u32 pid; u32 tgid; }
84
85        let i32_type = self.context.i32_type();
86        let i64_type = self.context.i64_type();
87        let (host_pid_i32, host_tid_i32) = self.get_host_pid_tid_values()?;
88
89        let ns_spec = if let Some(crate::PidFilterSpec::NamespaceTgid { pid_ns, .. }) =
90            self.compile_options.pid_filter_spec
91        {
92            pid_ns.helper_dev_inode()
93        } else {
94            self.compile_options
95                .special_pid_ns
96                .and_then(|pid_ns| pid_ns.helper_dev_inode())
97        };
98        let Some((pid_ns_dev, pid_ns_inode)) = ns_spec else {
99            let host_pid = self
100                .builder
101                .build_int_z_extend(host_pid_i32, i64_type, "selected_host_pid")
102                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
103            let host_tid = self
104                .builder
105                .build_int_z_extend(host_tid_i32, i64_type, "selected_host_tid")
106                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
107            return Ok((host_pid, host_tid));
108        };
109
110        let ptr_type = self.context.ptr_type(AddressSpace::default());
111        let key_arr_ty = i32_type.array_type(4);
112        let key_alloca = self.pm_key_alloca.ok_or_else(|| {
113            CodeGenError::LLVMError("pm_key not allocated in entry block".to_string())
114        })?;
115        // Reuse entry-allocated stack key storage: helper only needs first 8 bytes.
116        self.builder
117            .build_store(key_alloca, key_arr_ty.const_zero())
118            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
119
120        let pidns_info_ptr = self
121            .builder
122            .build_bit_cast(key_alloca, ptr_type, "special_pidns_info_ptr")
123            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
124
125        let helper_args = [
126            i64_type.const_int(pid_ns_dev, false).into(),
127            i64_type.const_int(pid_ns_inode, false).into(),
128            pidns_info_ptr,
129            i64_type.const_int(BPF_PIDNS_INFO_SIZE, false).into(),
130        ];
131        let helper_ret = self.create_bpf_helper_call(
132            BPF_FUNC_GET_NS_CURRENT_PID_TGID,
133            &helper_args,
134            i64_type.into(),
135            "special_ns_pid_tgid_ret",
136        )?;
137        let helper_ret = match helper_ret {
138            BasicValueEnum::IntValue(v) => v,
139            _ => {
140                return Err(CodeGenError::LLVMError(
141                    "bpf_get_ns_current_pid_tgid did not return integer".to_string(),
142                ))
143            }
144        };
145
146        let helper_ok = self
147            .builder
148            .build_int_compare(
149                inkwell::IntPredicate::EQ,
150                helper_ret,
151                i64_type.const_zero(),
152                "special_ns_helper_ok",
153            )
154            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
155
156        // SAFETY: key_alloca temporarily holds the two-field pid namespace helper
157        // result, so [0, 0] addresses the pid field.
158        let ns_pid_ptr = unsafe {
159            self.builder.build_gep(
160                key_arr_ty,
161                key_alloca,
162                &[i32_type.const_zero(), i32_type.const_zero()],
163                "special_ns_pid_ptr",
164            )
165        }
166        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
167        // SAFETY: key_alloca temporarily holds the two-field pid namespace helper
168        // result, so [0, 1] addresses the tgid field.
169        let ns_tgid_ptr = unsafe {
170            self.builder.build_gep(
171                key_arr_ty,
172                key_alloca,
173                &[i32_type.const_zero(), i32_type.const_int(1, false)],
174                "special_ns_tgid_ptr",
175            )
176        }
177        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
178
179        let ns_pid = self
180            .builder
181            .build_load(i32_type, ns_pid_ptr, "special_ns_pid")
182            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
183            .into_int_value();
184        let ns_tgid = self
185            .builder
186            .build_load(i32_type, ns_tgid_ptr, "special_ns_tgid")
187            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
188            .into_int_value();
189
190        let selected_pid_i32 = self
191            .builder
192            .build_select(helper_ok, ns_tgid, host_pid_i32, "selected_pid_i32")
193            .map_err(|e| CodeGenError::Builder(e.to_string()))?
194            .into_int_value();
195        let selected_tid_i32 = self
196            .builder
197            .build_select(helper_ok, ns_pid, host_tid_i32, "selected_tid_i32")
198            .map_err(|e| CodeGenError::Builder(e.to_string()))?
199            .into_int_value();
200
201        let selected_pid = self
202            .builder
203            .build_int_z_extend(selected_pid_i32, i64_type, "selected_pid")
204            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
205        let selected_tid = self
206            .builder
207            .build_int_z_extend(selected_tid_i32, i64_type, "selected_tid")
208            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
209
210        Ok((selected_pid, selected_tid))
211    }
212
213    pub(super) fn is_dwarf_aggregate_expr(&mut self, expr: &Expr) -> bool {
214        if let Expr::Cast { target_type, .. } = expr {
215            return self
216                .resolve_cast_target_type(target_type)
217                .ok()
218                .is_some_and(|ty| ghostscope_dwarf::is_c_aggregate_type(&ty));
219        }
220
221        if let Ok(Some(var)) = self.query_dwarf_for_complex_expr(expr) {
222            if let Some(ref ty) = var.dwarf_type {
223                return ghostscope_dwarf::is_c_aggregate_type(ty);
224            }
225        }
226        false
227    }
228
229    /// Heuristic check: whether an expression should be treated as a pointer/address
230    /// Returns true for:
231    /// - Explicit address-of forms (&expr)
232    /// - Script string literals (compile to pointer data)
233    /// - Alias variables bound to addresses
234    /// - DWARF-backed expressions whose type is pointer or array
235    pub(super) fn is_pointer_like_expr(&mut self, expr: &Expr) -> bool {
236        use crate::script::Expr as E;
237        match expr {
238            E::AddressOf(_) => return true,
239            E::String(_) => return true,
240            E::Cast { target_type, .. } => {
241                if self
242                    .resolve_cast_target_type(target_type)
243                    .ok()
244                    .is_some_and(|ty| {
245                        matches!(
246                            ghostscope_dwarf::strip_type_aliases(&ty),
247                            DwarfType::PointerType { .. } | DwarfType::ArrayType { .. }
248                        )
249                    })
250                {
251                    return true;
252                }
253            }
254            E::Variable(name) => {
255                if self.alias_variable_exists(name) {
256                    return true;
257                }
258            }
259            _ => {}
260        }
261
262        if let Ok(Some(var)) = self.query_dwarf_for_complex_expr(expr) {
263            if let Some(ref ty) = var.dwarf_type {
264                if ghostscope_dwarf::is_c_pointer_or_array_type(ty) {
265                    return true;
266                }
267            }
268        }
269        false
270    }
271
272    pub(super) fn resolve_cast_target_type(&self, target_type: &str) -> Result<DwarfType> {
273        let analyzer = self.process_analyzer;
274        let resolved = if let Some(context) = self.current_compile_time_context.as_ref() {
275            let module_path = Path::new(&context.module_path);
276            analyzer
277                .map(|analyzer| analyzer.try_resolve_type_spec_in_module(module_path, target_type))
278                .transpose()
279                .map_err(|err| CodeGenError::DwarfError(err.to_string()))?
280                .flatten()
281                .or_else(|| ghostscope_dwarf::DwarfAnalyzer::resolve_builtin_type_spec(target_type))
282        } else {
283            analyzer
284                .map(|analyzer| analyzer.try_resolve_type_spec(target_type))
285                .transpose()
286                .map_err(|err| CodeGenError::DwarfError(err.to_string()))?
287                .flatten()
288                .or_else(|| ghostscope_dwarf::DwarfAnalyzer::resolve_builtin_type_spec(target_type))
289        };
290
291        resolved.ok_or_else(|| {
292            CodeGenError::DwarfError(format!("cast target type '{target_type}' was not found"))
293        })
294    }
295
296    pub(super) fn cast_pointer_target_type(target_type: &DwarfType) -> Option<DwarfType> {
297        match ghostscope_dwarf::strip_type_aliases(target_type) {
298            DwarfType::PointerType { target_type, .. } => Some(target_type.as_ref().clone()),
299            _ => None,
300        }
301    }
302
303    fn is_float_dwarf_type(target_type: &DwarfType) -> bool {
304        match ghostscope_dwarf::strip_type_aliases(target_type) {
305            DwarfType::BaseType { encoding, .. } => {
306                *encoding == ghostscope_dwarf::constants::DW_ATE_float.0 as u16
307            }
308            _ => false,
309        }
310    }
311
312    fn is_bool_dwarf_type(target_type: &DwarfType) -> bool {
313        match ghostscope_dwarf::strip_type_aliases(target_type) {
314            DwarfType::BaseType { encoding, .. } => {
315                *encoding == ghostscope_dwarf::constants::DW_ATE_boolean.0 as u16
316            }
317            _ => false,
318        }
319    }
320
321    pub(super) fn cast_value_byte_len(target_type: &DwarfType) -> Option<usize> {
322        if matches!(
323            ghostscope_dwarf::strip_type_aliases(target_type),
324            DwarfType::PointerType { .. }
325        ) {
326            return Some(8);
327        }
328
329        if let Some(integer_type) = ghostscope_dwarf::c_integer_comparison_type(target_type) {
330            return Some(integer_type.size.clamp(1, 8) as usize);
331        }
332
333        None
334    }
335
336    pub(super) fn cast_source_pointer_value(
337        &mut self,
338        expr: &Expr,
339    ) -> Result<RuntimeAddress<'ctx>> {
340        if let Ok(address) = self.resolve_runtime_address_from_expr(expr) {
341            return Ok(address);
342        }
343
344        match self.compile_expr(expr)? {
345            BasicValueEnum::IntValue(value) => Ok(RuntimeAddress::available(
346                self.normalize_int_to_i64(value, "cast_ptr_i64")?,
347                self.context,
348            )),
349            BasicValueEnum::PointerValue(value) => self
350                .builder
351                .build_ptr_to_int(value, self.context.i64_type(), "cast_ptr_value")
352                .map(|value| RuntimeAddress::available(value, self.context))
353                .map_err(|err| CodeGenError::Builder(err.to_string())),
354            _ => Err(CodeGenError::TypeError(
355                "cast source expression did not produce an address-sized value".to_string(),
356            )),
357        }
358    }
359
360    pub(super) fn cast_source_memory_address(
361        &mut self,
362        expr: &Expr,
363    ) -> Result<RuntimeAddress<'ctx>> {
364        if let Ok(address) = self.resolve_runtime_address_from_expr(expr) {
365            return Ok(address);
366        }
367
368        if let Some(plan) = self.query_dwarf_for_complex_expr(expr)? {
369            let status_ptr = if self.condition_context_active {
370                Some(self.get_or_create_cond_error_global())
371            } else {
372                None
373            };
374            let pc_address = self.get_compile_time_context()?.pc_address;
375            if let Ok(address) =
376                self.variable_read_plan_to_runtime_address(&plan, pc_address, status_ptr)
377            {
378                return Ok(address);
379            }
380        }
381
382        match self.compile_expr(expr)? {
383            BasicValueEnum::IntValue(value) => Ok(RuntimeAddress::available(
384                self.normalize_int_to_i64(value, "cast_mem_i64")?,
385                self.context,
386            )),
387            BasicValueEnum::PointerValue(value) => self
388                .builder
389                .build_ptr_to_int(value, self.context.i64_type(), "cast_mem_ptr")
390                .map(|value| RuntimeAddress::available(value, self.context))
391                .map_err(|err| CodeGenError::Builder(err.to_string())),
392            _ => Err(CodeGenError::TypeError(
393                "cast source expression is not addressable".to_string(),
394            )),
395        }
396    }
397
398    fn cast_lvalue_address_and_type(
399        &mut self,
400        expr: &Expr,
401        target_type: &str,
402    ) -> Result<DynamicLvalue<'ctx>> {
403        let target_type = self.resolve_cast_target_type(target_type)?;
404        let module_path = self
405            .current_compile_time_context
406            .as_ref()
407            .map(|context| PathBuf::from(&context.module_path));
408
409        if let Some(pointee_type) = Self::cast_pointer_target_type(&target_type) {
410            let address = self.cast_source_pointer_value(expr)?;
411            return Ok(DynamicLvalue {
412                address,
413                type_info: DynamicTypeInfo {
414                    dwarf_type: pointee_type,
415                    module_path,
416                },
417            });
418        }
419
420        let address = self.cast_source_memory_address(expr)?;
421        Ok(DynamicLvalue {
422            address,
423            type_info: DynamicTypeInfo {
424                dwarf_type: target_type,
425                module_path,
426            },
427        })
428    }
429
430    fn cast_index_base(
431        &mut self,
432        expr: &Expr,
433    ) -> Result<Option<(IndexableElementInfo, RuntimeAddress<'ctx>)>> {
434        let Expr::Cast {
435            expr: source_expr,
436            target_type,
437        } = expr
438        else {
439            return Ok(None);
440        };
441
442        let target_type = self.resolve_cast_target_type(target_type)?;
443        let module_path = self
444            .current_compile_time_context
445            .as_ref()
446            .map(|context| PathBuf::from(&context.module_path));
447
448        match ghostscope_dwarf::strip_type_aliases(&target_type) {
449            DwarfType::PointerType { .. } => {
450                let Some(element_info) = Self::indexable_info_from_type(&target_type, module_path)
451                else {
452                    return Ok(None);
453                };
454                let base_address = self.cast_source_pointer_value(source_expr)?;
455                Ok(Some((element_info, base_address)))
456            }
457            DwarfType::ArrayType { .. } => {
458                let Some(element_info) = Self::indexable_info_from_type(&target_type, module_path)
459                else {
460                    return Ok(None);
461                };
462                let base_address = self.cast_source_memory_address(source_expr)?;
463                Ok(Some((element_info, base_address)))
464            }
465            _ => Ok(None),
466        }
467    }
468
469    fn indexable_info_from_type(
470        dwarf_type: &DwarfType,
471        module_path: Option<PathBuf>,
472    ) -> Option<IndexableElementInfo> {
473        ghostscope_dwarf::indexable_element_layout(dwarf_type).map(|layout| IndexableElementInfo {
474            element_type: layout.element_type,
475            stride: layout.stride,
476            module_path,
477        })
478    }
479
480    fn compiled_pointer_value_to_runtime_address(
481        &mut self,
482        value: BasicValueEnum<'ctx>,
483        int_name: &str,
484        ptr_name: &str,
485        error_message: &'static str,
486    ) -> Result<RuntimeAddress<'ctx>> {
487        match value {
488            BasicValueEnum::IntValue(value) => Ok(RuntimeAddress::available(
489                self.normalize_int_to_i64(value, int_name)?,
490                self.context,
491            )),
492            BasicValueEnum::PointerValue(value) => self
493                .builder
494                .build_ptr_to_int(value, self.context.i64_type(), ptr_name)
495                .map(|value| RuntimeAddress::available(value, self.context))
496                .map_err(|err| CodeGenError::Builder(err.to_string())),
497            _ => Err(CodeGenError::TypeError(error_message.to_string())),
498        }
499    }
500
501    fn dynamic_lvalue_from_indexable_base(
502        &mut self,
503        element_info: IndexableElementInfo,
504        base_address: RuntimeAddress<'ctx>,
505        index_value: IntValue<'ctx>,
506        name: &str,
507    ) -> Result<DynamicLvalue<'ctx>> {
508        let stride_value = self
509            .context
510            .i64_type()
511            .const_int(element_info.stride, false);
512        let byte_offset = self
513            .builder
514            .build_int_mul(index_value, stride_value, &format!("{name}_byte_offset"))
515            .map_err(|err| CodeGenError::Builder(err.to_string()))?;
516        let element_address = self
517            .builder
518            .build_int_add(
519                base_address.value,
520                byte_offset,
521                &format!("{name}_element_address"),
522            )
523            .map_err(|err| CodeGenError::Builder(err.to_string()))?;
524
525        Ok(DynamicLvalue {
526            address: base_address.with_value(element_address),
527            type_info: DynamicTypeInfo {
528                dwarf_type: element_info.element_type,
529                module_path: element_info.module_path,
530            },
531        })
532    }
533
534    fn dynamic_lvalue_from_const_pointer_arithmetic(
535        &mut self,
536        expr: &Expr,
537    ) -> Result<Option<DynamicLvalue<'ctx>>> {
538        let Some((base_expr, index)) = self.pointer_arithmetic_parts_expanding_aliases(expr)?
539        else {
540            return Ok(None);
541        };
542        let Some((element_info, base_address)) = self.cast_index_base(&base_expr)? else {
543            return Ok(None);
544        };
545        let index_value = self.context.i64_type().const_int(index as u64, true);
546        self.dynamic_lvalue_from_indexable_base(
547            element_info,
548            base_address,
549            index_value,
550            "dynamic_cast_ptr_arith",
551        )
552        .map(Some)
553    }
554
555    fn compile_cast_integer_value(
556        &mut self,
557        expr: &Expr,
558        target_type: &DwarfType,
559    ) -> Result<IntValue<'ctx>> {
560        let value = match self.compile_expr(expr)? {
561            BasicValueEnum::IntValue(value) => value,
562            BasicValueEnum::PointerValue(value) => self
563                .builder
564                .build_ptr_to_int(value, self.context.i64_type(), "cast_int_ptr")
565                .map_err(|err| CodeGenError::Builder(err.to_string()))?,
566            _ => {
567                return Err(CodeGenError::TypeError(
568                    "integer cast source must be an integer or pointer".to_string(),
569                ))
570            }
571        };
572
573        if Self::is_bool_dwarf_type(target_type) {
574            let value = self.normalize_int_to_i64(value, "cast_bool_i64")?;
575            return self
576                .builder
577                .build_int_compare(
578                    inkwell::IntPredicate::NE,
579                    value,
580                    self.context.i64_type().const_zero(),
581                    "cast_bool",
582                )
583                .map_err(|err| CodeGenError::Builder(err.to_string()));
584        }
585
586        let Some(integer_type) = ghostscope_dwarf::c_integer_comparison_type(target_type) else {
587            return Err(CodeGenError::TypeError(format!(
588                "cast target '{}' is not an integer type",
589                target_type.type_name()
590            )));
591        };
592
593        let bit_width = integer_type.size.saturating_mul(8).clamp(1, 64) as u32;
594        let target_int_type = self.context.custom_width_int_type(bit_width);
595        let current_width = value.get_type().get_bit_width();
596        let narrowed = if current_width > bit_width {
597            self.builder
598                .build_int_truncate(value, target_int_type, "cast_int_trunc")
599                .map_err(|err| CodeGenError::Builder(err.to_string()))?
600        } else if current_width < bit_width {
601            if integer_type.is_unsigned || current_width == 1 {
602                self.builder
603                    .build_int_z_extend(value, target_int_type, "cast_int_zext")
604                    .map_err(|err| CodeGenError::Builder(err.to_string()))?
605            } else {
606                self.builder
607                    .build_int_s_extend(value, target_int_type, "cast_int_sext")
608                    .map_err(|err| CodeGenError::Builder(err.to_string()))?
609            }
610        } else {
611            value
612        };
613
614        if bit_width == 64 {
615            return Ok(narrowed);
616        }
617
618        if integer_type.is_unsigned {
619            self.builder
620                .build_int_z_extend(narrowed, self.context.i64_type(), "cast_int_zext_i64")
621                .map_err(|err| CodeGenError::Builder(err.to_string()))
622        } else {
623            self.builder
624                .build_int_s_extend(narrowed, self.context.i64_type(), "cast_int_sext_i64")
625                .map_err(|err| CodeGenError::Builder(err.to_string()))
626        }
627    }
628
629    fn compile_cast_expr_value(
630        &mut self,
631        expr: &Expr,
632        target_type: &str,
633    ) -> Result<BasicValueEnum<'ctx>> {
634        let target_type = self.resolve_cast_target_type(target_type)?;
635
636        if Self::cast_pointer_target_type(&target_type).is_some() {
637            let address = self.cast_source_pointer_value(expr)?;
638            let ptr_ty = self.context.ptr_type(AddressSpace::default());
639            return self
640                .builder
641                .build_int_to_ptr(address.value, ptr_ty, "cast_as_ptr")
642                .map(|value| value.into())
643                .map_err(|err| CodeGenError::Builder(err.to_string()));
644        }
645
646        if ghostscope_dwarf::is_c_aggregate_type(&target_type) {
647            let address = self.cast_source_memory_address(expr)?;
648            let ptr_ty = self.context.ptr_type(AddressSpace::default());
649            return self
650                .builder
651                .build_int_to_ptr(address.value, ptr_ty, "cast_aggregate_ptr")
652                .map(|value| value.into())
653                .map_err(|err| CodeGenError::Builder(err.to_string()));
654        }
655
656        if ghostscope_dwarf::c_integer_comparison_type(&target_type).is_some() {
657            return self
658                .compile_cast_integer_value(expr, &target_type)
659                .map(|value| value.into());
660        }
661
662        if Self::is_float_dwarf_type(&target_type) {
663            return Err(CodeGenError::TypeError(
664                "floating-point casts are only supported for memory reads/printing".to_string(),
665            ));
666        }
667
668        Err(CodeGenError::TypeError(format!(
669            "cast target '{}' is not supported as a value expression",
670            target_type.type_name()
671        )))
672    }
673
674    pub(crate) fn integer_literal_value(expr: &Expr) -> Option<i64> {
675        use crate::script::ast::BinaryOp as BO;
676        use crate::script::ast::Expr as E;
677
678        match expr {
679            E::Int(value) => Some(*value),
680            E::BinaryOp {
681                left,
682                op: BO::Add,
683                right,
684            } => {
685                Self::integer_literal_value(left)?.checked_add(Self::integer_literal_value(right)?)
686            }
687            E::BinaryOp {
688                left,
689                op: BO::Subtract,
690                right,
691            } => {
692                Self::integer_literal_value(left)?.checked_sub(Self::integer_literal_value(right)?)
693            }
694            E::BinaryOp {
695                left,
696                op: BO::Multiply,
697                right,
698            } => {
699                Self::integer_literal_value(left)?.checked_mul(Self::integer_literal_value(right)?)
700            }
701            E::BinaryOp {
702                left,
703                op: BO::Divide,
704                right,
705            } => {
706                Self::integer_literal_value(left)?.checked_div(Self::integer_literal_value(right)?)
707            }
708            E::BinaryOp {
709                left,
710                op: BO::Modulo,
711                right,
712            } => {
713                Self::integer_literal_value(left)?.checked_rem(Self::integer_literal_value(right)?)
714            }
715            E::BinaryOp {
716                left,
717                op: BO::BitAnd,
718                right,
719            } => Some(Self::integer_literal_value(left)? & Self::integer_literal_value(right)?),
720            E::BinaryOp {
721                left,
722                op: BO::BitXor,
723                right,
724            } => Some(Self::integer_literal_value(left)? ^ Self::integer_literal_value(right)?),
725            E::BinaryOp {
726                left,
727                op: BO::BitOr,
728                right,
729            } => Some(Self::integer_literal_value(left)? | Self::integer_literal_value(right)?),
730            E::BinaryOp {
731                left,
732                op: BO::ShiftLeft,
733                right,
734            } => {
735                let shift = u32::try_from(Self::integer_literal_value(right)?).ok()?;
736                Self::integer_literal_value(left)?.checked_shl(shift)
737            }
738            E::BinaryOp {
739                left,
740                op: BO::ShiftRight,
741                right,
742            } => {
743                let shift = u32::try_from(Self::integer_literal_value(right)?).ok()?;
744                Self::integer_literal_value(left)?.checked_shr(shift)
745            }
746            E::UnaryBitNot(inner) => Some(!Self::integer_literal_value(inner)?),
747            _ => None,
748        }
749    }
750
751    pub(crate) fn pointer_arithmetic_parts(expr: &Expr) -> Option<(&Expr, i64)> {
752        use crate::script::ast::Expr as E;
753
754        fn collect_offset(expr: &Expr, acc: i64) -> Option<(&Expr, i64)> {
755            use crate::script::ast::BinaryOp as BO;
756            use crate::script::ast::Expr as E;
757
758            match expr {
759                E::BinaryOp {
760                    left,
761                    op: BO::Add,
762                    right,
763                } => match (&**left, &**right) {
764                    (ptr_side, int_expr)
765                        if EbpfContext::<'static, 'static>::integer_literal_value(int_expr)
766                            .is_some() =>
767                    {
768                        let index =
769                            EbpfContext::<'static, 'static>::integer_literal_value(int_expr)?;
770                        collect_offset(ptr_side, acc.checked_add(index)?)
771                    }
772                    (int_expr, ptr_side)
773                        if EbpfContext::<'static, 'static>::integer_literal_value(int_expr)
774                            .is_some() =>
775                    {
776                        let index =
777                            EbpfContext::<'static, 'static>::integer_literal_value(int_expr)?;
778                        collect_offset(ptr_side, acc.checked_add(index)?)
779                    }
780                    _ => Some((expr, acc)),
781                },
782                E::BinaryOp {
783                    left,
784                    op: BO::Subtract,
785                    right,
786                } => match &**right {
787                    int_expr
788                        if EbpfContext::<'static, 'static>::integer_literal_value(int_expr)
789                            .is_some() =>
790                    {
791                        let index =
792                            EbpfContext::<'static, 'static>::integer_literal_value(int_expr)?;
793                        collect_offset(left, acc.checked_sub(index)?)
794                    }
795                    _ => Some((expr, acc)),
796                },
797                _ => Some((expr, acc)),
798            }
799        }
800
801        let E::BinaryOp { .. } = expr else {
802            return None;
803        };
804
805        let (base, index) = collect_offset(expr, 0)?;
806        match base {
807            E::BinaryOp { .. } => None,
808            _ => Some((base, index)),
809        }
810    }
811
812    pub(crate) fn pointer_arithmetic_parts_expanding_aliases(
813        &self,
814        expr: &Expr,
815    ) -> Result<Option<(Expr, i64)>> {
816        let Some((base, index)) = Self::pointer_arithmetic_parts(expr) else {
817            return Ok(None);
818        };
819
820        let mut base = base.clone();
821        let mut index = index;
822        let mut visited = std::collections::HashSet::new();
823
824        loop {
825            let Expr::Variable(name) = &base else {
826                break;
827            };
828            if !self.alias_variable_exists(name) {
829                break;
830            }
831            if !visited.insert(name.clone()) {
832                return Err(CodeGenError::TypeError(format!(
833                    "alias cycle detected for '{name}'"
834                )));
835            }
836            let Some(target) = self.get_alias_variable(name) else {
837                break;
838            };
839            if let Some((alias_base, alias_index)) = Self::pointer_arithmetic_parts(&target) {
840                index = alias_index.checked_add(index).ok_or_else(|| {
841                    CodeGenError::TypeError("pointer arithmetic offset overflow".to_string())
842                })?;
843                base = alias_base.clone();
844            } else {
845                base = target;
846            }
847        }
848
849        Ok(Some((base, index)))
850    }
851
852    fn is_dwarf_pointer_or_array_arg(&mut self, expr: &Expr) -> Result<bool> {
853        let Some(var) = self.query_dwarf_for_complex_expr(expr)? else {
854            return Ok(false);
855        };
856        let Some(ty) = var.dwarf_type.as_ref() else {
857            return Ok(false);
858        };
859        let ty = ghostscope_dwarf::strip_type_aliases(ty);
860        Ok(matches!(
861            ty,
862            DwarfType::PointerType { .. } | DwarfType::ArrayType { .. }
863        ))
864    }
865
866    fn dwarf_integer_comparison_expr(&mut self, expr: &Expr) -> Option<CIntegerComparisonType> {
867        if let Expr::Cast { target_type, .. } = expr {
868            return self
869                .resolve_cast_target_type(target_type)
870                .ok()
871                .and_then(|ty| ghostscope_dwarf::c_integer_comparison_type(&ty));
872        }
873
874        if let Ok(Some(var)) = self.query_dwarf_for_complex_expr(expr) {
875            if let Some(ref ty) = var.dwarf_type {
876                return ghostscope_dwarf::c_integer_comparison_type(ty);
877            }
878        }
879        None
880    }
881
882    fn integer_comparison_plan_for_exprs(
883        &mut self,
884        left: &Expr,
885        right: &Expr,
886    ) -> Option<CIntegerComparisonPlan> {
887        let left_ty = self.dwarf_integer_comparison_expr(left);
888        let right_ty = self.dwarf_integer_comparison_expr(right);
889        if left_ty.is_none() && right_ty.is_none() {
890            return None;
891        }
892
893        Some(ghostscope_dwarf::usual_c_arithmetic_comparison_plan(
894            left_ty.unwrap_or_else(CIntegerComparisonType::signed_i64),
895            right_ty.unwrap_or_else(CIntegerComparisonType::signed_i64),
896        ))
897    }
898
899    pub(super) fn unsigned_ordering_width_for_exprs(
900        &mut self,
901        left: &Expr,
902        right: &Expr,
903    ) -> Option<u32> {
904        let plan = self.integer_comparison_plan_for_exprs(left, right)?;
905        if plan.is_unsigned {
906            Some((plan.size * 8) as u32)
907        } else {
908            None
909        }
910    }
911
912    pub(super) fn unsigned_shift_width_for_expr(&mut self, expr: &Expr) -> Option<u32> {
913        let c_type = self.dwarf_integer_comparison_expr(expr)?.promoted();
914        if c_type.is_unsigned {
915            Some((c_type.size * 8) as u32)
916        } else {
917            None
918        }
919    }
920
921    /// Ensure that when an expression refers to a DWARF-backed variable (not via address-of),
922    /// the variable's DWARF type is a pointer or array (decays to pointer for memcmp/strncmp).
923    fn ensure_dwarf_pointer_arg(&mut self, e: &Expr, where_ctx: &str) -> Result<()> {
924        // Allow explicit address-of forms (&expr), which purposefully produce a pointer
925        if matches!(e, Expr::AddressOf(_)) {
926            return Ok(());
927        }
928        if let Some((ptr_side, _)) = self.pointer_arithmetic_parts_expanding_aliases(e)? {
929            if matches!(&ptr_side, Expr::AddressOf(_))
930                || self
931                    .is_dwarf_pointer_or_array_arg(&ptr_side)
932                    .unwrap_or(false)
933            {
934                return Ok(());
935            }
936        }
937        if self.is_dynamic_pointer_arithmetic_expr(e)?
938            || self.expands_to_nonliteral_pointer_arithmetic(e)?
939        {
940            return Ok(());
941        }
942        match self.query_dwarf_for_complex_expr(e) {
943            Ok(Some(var)) => {
944                let Some(ty) = var.dwarf_type.as_ref() else {
945                    return Err(CodeGenError::TypeError(format!(
946                        "{where_ctx}: DWARF variable has no type information"
947                    )));
948                };
949                let ty = ghostscope_dwarf::strip_type_aliases(ty);
950                if !matches!(
951                    ty,
952                    DwarfType::PointerType { .. } | DwarfType::ArrayType { .. }
953                ) {
954                    return Err(CodeGenError::TypeError(format!(
955                        "{where_ctx}: only pointer or array DWARF variables are supported"
956                    )));
957                }
958                Ok(())
959            }
960            // No DWARF info or analyzer missing: allow script-level pointer values
961            Ok(None) | Err(_) => match self.compile_expr(e) {
962                Ok(BasicValueEnum::PointerValue(_)) => Ok(()),
963                _ => Err(CodeGenError::TypeError(format!(
964                    "{where_ctx}: expression is not a pointer"
965                ))),
966            },
967        }
968    }
969
970    fn is_dynamic_pointer_arithmetic_expr(&mut self, expr: &Expr) -> Result<bool> {
971        use crate::script::ast::BinaryOp as BO;
972        use crate::script::ast::Expr as E;
973
974        let E::BinaryOp { left, op, right } = expr else {
975            return Ok(false);
976        };
977
978        match op {
979            BO::Add => Ok(self.is_dynamic_indexable_pointer_base(left)?
980                || self.is_dynamic_indexable_pointer_base(right)?),
981            BO::Subtract => self.is_dynamic_indexable_pointer_base(left),
982            _ => Ok(false),
983        }
984    }
985
986    fn is_dynamic_indexable_pointer_base(&mut self, expr: &Expr) -> Result<bool> {
987        if matches!(expr, Expr::AddressOf(_)) {
988            return Ok(true);
989        }
990
991        if self.cast_index_base(expr)?.is_some() {
992            return Ok(true);
993        }
994
995        if self
996            .query_dwarf_for_complex_expr(expr)
997            .ok()
998            .flatten()
999            .and_then(|var| var.dwarf_type)
1000            .is_some_and(|ty| ghostscope_dwarf::is_c_pointer_or_array_type(&ty))
1001        {
1002            return Ok(true);
1003        }
1004
1005        let expanded = self.expand_alias_variable_expr(expr)?;
1006        if matches!(expanded, Expr::AddressOf(_)) {
1007            return Ok(true);
1008        }
1009        let Some((base_expr, _static_index)) =
1010            self.pointer_arithmetic_parts_expanding_aliases(&expanded)?
1011        else {
1012            return Ok(false);
1013        };
1014
1015        Ok(self
1016            .query_dwarf_for_complex_expr(&base_expr)
1017            .ok()
1018            .flatten()
1019            .and_then(|var| var.dwarf_type)
1020            .is_some_and(|ty| ghostscope_dwarf::is_c_pointer_or_array_type(&ty)))
1021    }
1022
1023    fn expands_to_nonliteral_pointer_arithmetic(&mut self, expr: &Expr) -> Result<bool> {
1024        let expanded = self.expand_alias_variable_expr(expr)?;
1025        self.is_nonliteral_pointer_arithmetic_expr(&expanded)
1026    }
1027
1028    fn is_nonliteral_pointer_arithmetic_expr(&mut self, expr: &Expr) -> Result<bool> {
1029        use crate::script::ast::BinaryOp as BO;
1030        use crate::script::ast::Expr as E;
1031
1032        let E::BinaryOp { left, op, right } = expr else {
1033            return Ok(false);
1034        };
1035
1036        match op {
1037            BO::Add => {
1038                let left_is_ptr = self.is_dynamic_indexable_pointer_base(left)?;
1039                let right_is_ptr = self.is_dynamic_indexable_pointer_base(right)?;
1040                let left_is_literal = Self::integer_literal_value(left).is_some();
1041                let right_is_literal = Self::integer_literal_value(right).is_some();
1042
1043                if (left_is_ptr && !right_is_ptr && !right_is_literal)
1044                    || (right_is_ptr && !left_is_ptr && !left_is_literal)
1045                {
1046                    return Ok(true);
1047                }
1048
1049                Ok(self.expands_to_nonliteral_pointer_arithmetic(left)?
1050                    || self.expands_to_nonliteral_pointer_arithmetic(right)?)
1051            }
1052            BO::Subtract => {
1053                let left_is_ptr = self.is_dynamic_indexable_pointer_base(left)?;
1054                let right_is_literal = Self::integer_literal_value(right).is_some();
1055
1056                if left_is_ptr && !right_is_literal {
1057                    return Ok(true);
1058                }
1059
1060                self.expands_to_nonliteral_pointer_arithmetic(left)
1061            }
1062            _ => Ok(false),
1063        }
1064    }
1065
1066    /// Resolve an expression to an i64 pointer value. Accepts integer (address) and pointer values;
1067    /// falls back to DWARF evaluation for complex expressions.
1068    pub(crate) fn resolve_ptr_i64_from_expr(
1069        &mut self,
1070        e: &Expr,
1071    ) -> Result<inkwell::values::IntValue<'ctx>> {
1072        self.resolve_runtime_address_from_expr(e)
1073            .map(|address| address.value)
1074    }
1075
1076    pub(crate) fn resolve_runtime_address_from_expr(
1077        &mut self,
1078        e: &Expr,
1079    ) -> Result<RuntimeAddress<'ctx>> {
1080        let mut visited = std::collections::HashSet::new();
1081        self.resolve_runtime_address_from_expr_internal(e, &mut visited, 0)
1082    }
1083
1084    fn resolve_runtime_address_from_expr_internal(
1085        &mut self,
1086        e: &Expr,
1087        visited: &mut std::collections::HashSet<String>,
1088        depth: usize,
1089    ) -> Result<RuntimeAddress<'ctx>> {
1090        use crate::script::ast::BinaryOp as BO;
1091        use crate::script::ast::Expr as E;
1092        use inkwell::values::BasicValueEnum::*;
1093        const MAX_DEPTH: usize = 64;
1094        if depth > MAX_DEPTH {
1095            return Err(CodeGenError::TypeError(
1096                "alias expansion depth exceeded (cycle?)".into(),
1097            ));
1098        }
1099        if let E::Cast { expr, target_type } = e {
1100            let target_type_info = self.resolve_cast_target_type(target_type)?;
1101            if Self::cast_pointer_target_type(&target_type_info).is_some() {
1102                return self.cast_source_pointer_value(expr);
1103            }
1104            return self.cast_source_memory_address(expr);
1105        }
1106        // Alias variable indirection: resolve its target expression first
1107        if let E::Variable(name) = e {
1108            if self.alias_variable_exists(name) {
1109                if !visited.insert(name.clone()) {
1110                    return Err(CodeGenError::TypeError(format!(
1111                        "alias cycle detected for '{name}'"
1112                    )));
1113                }
1114                if let Some(target) = self.get_alias_variable(name) {
1115                    let r = self.resolve_runtime_address_from_expr_internal(
1116                        &target,
1117                        visited,
1118                        depth + 1,
1119                    );
1120                    visited.remove(name);
1121                    return r;
1122                }
1123            }
1124        }
1125        // Special-case: explicit address-of must yield a pointer-sized address
1126        if let E::AddressOf(inner) = e {
1127            // Support alias variables transparently: &alias -> address of aliased DWARF expr
1128            let resolved_inner: &E = if let E::Variable(name) = inner.as_ref() {
1129                if self.alias_variable_exists(name) {
1130                    // Owned target for query
1131                    if let Some(target) = self.get_alias_variable(name) {
1132                        if let Some(var) = self.query_dwarf_for_complex_expr(&target)? {
1133                            let status_ptr = if self.condition_context_active {
1134                                Some(self.get_or_create_cond_error_global())
1135                            } else {
1136                                None
1137                            };
1138                            let pc_address = self.get_compile_time_context()?.pc_address;
1139                            return self.variable_read_plan_to_runtime_address(
1140                                &var, pc_address, status_ptr,
1141                            );
1142                        } else {
1143                            return Err(CodeGenError::TypeError(
1144                                "cannot take address of unresolved expression".into(),
1145                            ));
1146                        }
1147                    } else {
1148                        return Err(CodeGenError::TypeError(
1149                            "cannot take address of unresolved expression".into(),
1150                        ));
1151                    }
1152                } else {
1153                    inner.as_ref()
1154                }
1155            } else {
1156                inner.as_ref()
1157            };
1158
1159            if let E::ArrayAccess(array_expr, index_expr) = resolved_inner {
1160                if let Some(element_lvalue) =
1161                    self.compile_dynamic_array_element_address(array_expr, index_expr)?
1162                {
1163                    return Ok(element_lvalue.address);
1164                }
1165            }
1166
1167            if let Some(lvalue) = self.dynamic_lvalue_address_and_type(resolved_inner)? {
1168                return Ok(lvalue.address);
1169            }
1170
1171            if let Some(var) = self.query_dwarf_for_complex_expr(resolved_inner)? {
1172                let status_ptr = if self.condition_context_active {
1173                    Some(self.get_or_create_cond_error_global())
1174                } else {
1175                    None
1176                };
1177                let pc_address = self.get_compile_time_context()?.pc_address;
1178                return self.variable_read_plan_to_runtime_address(&var, pc_address, status_ptr);
1179            } else {
1180                return Err(CodeGenError::TypeError(
1181                    "cannot take address of unresolved expression".into(),
1182                ));
1183            }
1184        }
1185
1186        if let Some(address) = self.dynamic_pointer_arithmetic_address(e)? {
1187            return Ok(address);
1188        }
1189
1190        if let Some((ptr_side, index)) = self.pointer_arithmetic_parts_expanding_aliases(e)? {
1191            if matches!(&ptr_side, E::AddressOf(_)) {
1192                let base =
1193                    self.resolve_runtime_address_from_expr_internal(&ptr_side, visited, depth + 1)?;
1194                let off = self.context.i64_type().const_int(index as u64, false);
1195                let value = self
1196                    .builder
1197                    .build_int_add(base.value, off, "ptr_add")
1198                    .map_err(|err| CodeGenError::Builder(err.to_string()))?;
1199                return Ok(base.with_value(value));
1200            } else if let Some((element_info, base_address)) = self.cast_index_base(&ptr_side)? {
1201                let index_value = self.context.i64_type().const_int(index as u64, true);
1202                return self
1203                    .dynamic_lvalue_from_indexable_base(
1204                        element_info,
1205                        base_address,
1206                        index_value,
1207                        "cast_ptr_add",
1208                    )
1209                    .map(|lvalue| lvalue.address);
1210            } else if let Some(var) = self.query_dwarf_for_complex_expr(&ptr_side)? {
1211                if var.dwarf_type.is_some() {
1212                    let pointed_plan = var
1213                        .plan_pointer_element_index(index)
1214                        .map_err(|err| CodeGenError::DwarfError(err.to_string()))?;
1215                    let status_ptr = if self.condition_context_active {
1216                        Some(self.get_or_create_cond_error_global())
1217                    } else {
1218                        None
1219                    };
1220                    let pc_address = self.get_compile_time_context()?.pc_address;
1221                    return self.variable_read_plan_to_runtime_address(
1222                        &pointed_plan,
1223                        pc_address,
1224                        status_ptr,
1225                    );
1226                }
1227            }
1228        }
1229
1230        // Support constant-offset addressing: (alias_expr + K) or (K + alias_expr)
1231        if let E::BinaryOp { left, op, right } = e {
1232            if matches!(op, BO::Add) {
1233                // alias + K
1234                if let Some(k) = Self::integer_literal_value(right) {
1235                    if let Ok(base) =
1236                        self.resolve_runtime_address_from_expr_internal(left, visited, depth + 1)
1237                    {
1238                        let off = self.context.i64_type().const_int(k as u64, false);
1239                        let value = self
1240                            .builder
1241                            .build_int_add(base.value, off, "ptr_add")
1242                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1243                        return Ok(base.with_value(value));
1244                    }
1245                }
1246                // K + alias
1247                if let Some(k) = Self::integer_literal_value(left) {
1248                    if let Ok(base) =
1249                        self.resolve_runtime_address_from_expr_internal(right, visited, depth + 1)
1250                    {
1251                        let off = self.context.i64_type().const_int(k as u64, false);
1252                        let value = self
1253                            .builder
1254                            .build_int_add(base.value, off, "ptr_add")
1255                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1256                        return Ok(base.with_value(value));
1257                    }
1258                }
1259            } else if matches!(op, BO::Subtract) {
1260                if let Some(k) = Self::integer_literal_value(right) {
1261                    if let Ok(base) =
1262                        self.resolve_runtime_address_from_expr_internal(left, visited, depth + 1)
1263                    {
1264                        let off = self
1265                            .context
1266                            .i64_type()
1267                            .const_int(k.wrapping_neg() as u64, false);
1268                        let value = self
1269                            .builder
1270                            .build_int_add(base.value, off, "ptr_sub")
1271                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1272                        return Ok(base.with_value(value));
1273                    }
1274                }
1275            }
1276        }
1277        // Prefer DWARF-based address resolution first so that array/aggregate
1278        // expressions decay to their base address rather than loading values.
1279        if let Ok(Some(var)) = self.query_dwarf_for_complex_expr(e) {
1280            if let Some(dty) = var.dwarf_type.as_ref() {
1281                let dty = ghostscope_dwarf::strip_type_aliases(dty);
1282                match dty {
1283                    DwarfType::PointerType { .. } => {
1284                        let pc_address = self.get_compile_time_context()?.pc_address;
1285                        let val_any =
1286                            self.variable_read_plan_to_llvm_value(&var, pc_address, None)?;
1287                        match val_any {
1288                            IntValue(iv) => Ok(RuntimeAddress::available(iv, self.context)),
1289                            PointerValue(pv) => self
1290                                .builder
1291                                .build_ptr_to_int(pv, self.context.i64_type(), "ptr_as_i64")
1292                                .map(|value| RuntimeAddress::available(value, self.context))
1293                                .map_err(|e| CodeGenError::Builder(e.to_string())),
1294                            _ => Err(CodeGenError::TypeError(
1295                                "DWARF value is not pointer/integer".into(),
1296                            )),
1297                        }
1298                    }
1299                    DwarfType::ArrayType { .. } => {
1300                        // Use the base address of the array as pointer
1301                        let status_ptr = if self.condition_context_active {
1302                            Some(self.get_or_create_cond_error_global())
1303                        } else {
1304                            None
1305                        };
1306                        let pc_address = self.get_compile_time_context()?.pc_address;
1307                        self.variable_read_plan_to_runtime_address(&var, pc_address, status_ptr)
1308                    }
1309                    _ => Err(CodeGenError::TypeError(
1310                        "DWARF value is not pointer/array".into(),
1311                    )),
1312                }
1313            } else {
1314                let status_ptr = if self.condition_context_active {
1315                    Some(self.get_or_create_cond_error_global())
1316                } else {
1317                    None
1318                };
1319                let pc_address = self.get_compile_time_context()?.pc_address;
1320                self.variable_read_plan_to_runtime_address(&var, pc_address, status_ptr)
1321            }
1322        } else {
1323            // No DWARF-backed address and not an address-of/alias+const: reject script-level pointers.
1324            Err(CodeGenError::TypeError(
1325                "expression is not a pointer/address".into(),
1326            ))
1327        }
1328    }
1329
1330    fn dynamic_pointer_arithmetic_address(
1331        &mut self,
1332        expr: &Expr,
1333    ) -> Result<Option<RuntimeAddress<'ctx>>> {
1334        use crate::script::ast::BinaryOp as BO;
1335        use crate::script::ast::Expr as E;
1336
1337        let E::BinaryOp { left, op, right } = expr else {
1338            return Ok(None);
1339        };
1340
1341        match op {
1342            BO::Add => {
1343                if let Some(address) = self.dynamic_raw_address_candidate(left, right, false)? {
1344                    return Ok(Some(address));
1345                }
1346                if let Some(address) = self.dynamic_raw_address_candidate(right, left, false)? {
1347                    return Ok(Some(address));
1348                }
1349                if let Some(address) = self.dynamic_index_address_candidate(left, right)? {
1350                    return Ok(Some(address));
1351                }
1352                self.dynamic_index_address_candidate(right, left)
1353            }
1354            BO::Subtract => {
1355                if let Some(address) = self.dynamic_raw_address_candidate(left, right, true)? {
1356                    return Ok(Some(address));
1357                }
1358                let negative_right = E::BinaryOp {
1359                    left: Box::new(E::Int(0)),
1360                    op: BO::Subtract,
1361                    right: right.clone(),
1362                };
1363                self.dynamic_index_address_candidate(left, &negative_right)
1364            }
1365            _ => Ok(None),
1366        }
1367    }
1368
1369    fn dynamic_raw_address_candidate(
1370        &mut self,
1371        base_expr: &Expr,
1372        offset_expr: &Expr,
1373        subtract: bool,
1374    ) -> Result<Option<RuntimeAddress<'ctx>>> {
1375        if Self::integer_literal_value(offset_expr).is_some() {
1376            return Ok(None);
1377        }
1378
1379        let expanded_base = self.expand_alias_variable_expr(base_expr)?;
1380        if !matches!(expanded_base, Expr::AddressOf(_)) {
1381            return Ok(None);
1382        }
1383
1384        let base_address = self.resolve_runtime_address_from_expr(&expanded_base)?;
1385        let offset = match self.compile_expr(offset_expr)? {
1386            BasicValueEnum::IntValue(value) => {
1387                self.normalize_int_to_i64(value, "dynamic_raw_offset_i64")?
1388            }
1389            _ => {
1390                return Err(CodeGenError::TypeError(
1391                    "raw address offset expression must compile to an integer".to_string(),
1392                ))
1393            }
1394        };
1395        let offset = if subtract {
1396            self.builder
1397                .build_int_neg(offset, "dynamic_raw_offset_neg")
1398                .map_err(|err| CodeGenError::Builder(err.to_string()))?
1399        } else {
1400            offset
1401        };
1402        let address = self
1403            .builder
1404            .build_int_add(base_address.value, offset, "dynamic_raw_address")
1405            .map_err(|err| CodeGenError::Builder(err.to_string()))?;
1406        Ok(Some(base_address.with_value(address)))
1407    }
1408
1409    fn dynamic_index_address_candidate(
1410        &mut self,
1411        base_expr: &Expr,
1412        index_expr: &Expr,
1413    ) -> Result<Option<RuntimeAddress<'ctx>>> {
1414        match self.compile_dynamic_array_element_address(base_expr, index_expr) {
1415            Ok(Some(element_lvalue)) => Ok(Some(element_lvalue.address)),
1416            Ok(None) => Ok(None),
1417            Err(CodeGenError::VariableNotFound(_))
1418            | Err(CodeGenError::VariableNotInScope(_))
1419            | Err(CodeGenError::TypeError(_)) => Ok(None),
1420            Err(err) => Err(err),
1421        }
1422    }
1423
1424    /// Builtin memcmp (boolean variant): returns true iff first `len` bytes equal.
1425    /// Supports dynamic `len` (expr), clamped to [0, compare_cap].
1426    fn compile_memcmp_builtin(
1427        &mut self,
1428        a_expr: &Expr,
1429        b_expr: &Expr,
1430        len_expr: &Expr,
1431    ) -> Result<BasicValueEnum<'ctx>> {
1432        // Note: constant hex/len validation happens at parse-time; dynamic cases are handled at runtime by masking bytes.
1433
1434        // Note: do not resolve pointers yet; if either side is hex("...") we will synthesize bytes
1435
1436        // Compile length expr to i32 and clamp to [0, CAP]
1437        let len_val = self.compile_expr(len_expr)?;
1438        let len_iv = match len_val {
1439            BasicValueEnum::IntValue(iv) => iv,
1440            _ => {
1441                return Err(CodeGenError::TypeError(
1442                    "memcmp length must be an integer expression".into(),
1443                ))
1444            }
1445        };
1446        let i32_ty = self.context.i32_type();
1447        let len_i32 = if len_iv.get_type().get_bit_width() > 32 {
1448            self.builder
1449                .build_int_truncate(len_iv, i32_ty, "memcmp_len_trunc")
1450                .map_err(|e| CodeGenError::Builder(e.to_string()))?
1451        } else if len_iv.get_type().get_bit_width() < 32 {
1452            self.builder
1453                .build_int_z_extend(len_iv, i32_ty, "memcmp_len_zext")
1454                .map_err(|e| CodeGenError::Builder(e.to_string()))?
1455        } else {
1456            len_iv
1457        };
1458        let zero_i32 = i32_ty.const_zero();
1459        let is_neg = self
1460            .builder
1461            .build_int_compare(
1462                inkwell::IntPredicate::SLT,
1463                len_i32,
1464                zero_i32,
1465                "memcmp_len_neg",
1466            )
1467            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1468        let len_nn = self
1469            .builder
1470            .build_select(is_neg, zero_i32, len_i32, "memcmp_len_nn")
1471            .map_err(|e| CodeGenError::Builder(e.to_string()))?
1472            .into_int_value();
1473        let cap = self.compile_options.compare_cap;
1474        let cap_const = i32_ty.const_int(cap as u64, false);
1475        let gt = self
1476            .builder
1477            .build_int_compare(
1478                inkwell::IntPredicate::UGT,
1479                len_nn,
1480                cap_const,
1481                "memcmp_len_gt",
1482            )
1483            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1484        let sel_len = self
1485            .builder
1486            .build_select(gt, cap_const, len_nn, "memcmp_len_sel")
1487            .map_err(|e| CodeGenError::Builder(e.to_string()))?
1488            .into_int_value();
1489
1490        // Fast-path: if effective length is zero, return true without any reads
1491        let len_is_zero = self
1492            .builder
1493            .build_int_compare(
1494                inkwell::IntPredicate::EQ,
1495                sel_len,
1496                i32_ty.const_zero(),
1497                "memcmp_len_is_zero",
1498            )
1499            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1500        let func = self.current_function("compile memcmp length branch")?;
1501        let zero_b = self.context.append_basic_block(func, "memcmp_len_zero");
1502        let nz_b = self.context.append_basic_block(func, "memcmp_len_nz");
1503        let cont_b = self.context.append_basic_block(func, "memcmp_len_cont");
1504        self.builder
1505            .build_conditional_branch(len_is_zero, zero_b, nz_b)
1506            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1507
1508        // Zero-length branch: true
1509        self.builder.position_at_end(zero_b);
1510        let bool_true = self.context.bool_type().const_int(1, false);
1511        self.builder
1512            .build_unconditional_branch(cont_b)
1513            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1514        let zero_block = self.current_insert_block("finish memcmp zero-length block")?;
1515
1516        // Non-zero branch: perform reads and compare
1517        self.builder.position_at_end(nz_b);
1518
1519        // Prepare static buffers of size CAP for both sides
1520        let (arr_a_ty, buf_a) = self.get_or_create_i8_buffer(cap, "_gs_bi_memcmp_a");
1521        let (arr_b_ty, buf_b) = self.get_or_create_i8_buffer(cap, "_gs_bi_memcmp_b");
1522        let ptr_ty = self.context.ptr_type(AddressSpace::default());
1523
1524        // Helper: parse hex builtin into bytes
1525        let parse_hex_bytes = |e: &Expr| -> Option<Vec<u8>> {
1526            if let Expr::BuiltinCall { name, args } = e {
1527                if name == "hex" && args.len() == 1 {
1528                    if let Expr::String(s) = &args[0] {
1529                        // Parser guarantees only hex digits and even length
1530                        if s.is_empty() {
1531                            return Some(Vec::new());
1532                        }
1533                        let mut out = Vec::with_capacity(s.len() / 2);
1534                        let mut i = 0usize;
1535                        while i + 1 < s.len() {
1536                            let v = u8::from_str_radix(&s[i..i + 2], 16).ok()?;
1537                            out.push(v);
1538                            i += 2;
1539                        }
1540                        return Some(out);
1541                    }
1542                }
1543            }
1544            None
1545        };
1546
1547        // Side A
1548        // If side A is DWARF-backed (and not an explicit address-of), enforce pointer DWARF type
1549        if parse_hex_bytes(a_expr).is_none() {
1550            self.ensure_dwarf_pointer_arg(a_expr, "memcmp arg0")?;
1551        }
1552        let ok_a = if let Some(bytes) = parse_hex_bytes(a_expr) {
1553            let i32_ty = self.context.i32_type();
1554            let idx0 = i32_ty.const_zero();
1555            for i in 0..(cap as usize) {
1556                let idx_i = i32_ty.const_int(i as u64, false);
1557                // SAFETY: buf_a is a cap-sized array and i is bounded by cap.
1558                let pa = unsafe {
1559                    self.builder
1560                        .build_gep(arr_a_ty, buf_a, &[idx0, idx_i], &format!("hex_a_i{i}"))
1561                        .map_err(|e| CodeGenError::Builder(e.to_string()))?
1562                };
1563                let byte = if i < bytes.len() { bytes[i] } else { 0 } as u64;
1564                let bv = self.context.i8_type().const_int(byte, false);
1565                self.builder
1566                    .build_store(pa, bv)
1567                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1568            }
1569            self.context.bool_type().const_int(1, false)
1570        } else {
1571            // Resolve pointer for A and read from user memory
1572            let ptr_a = self.resolve_runtime_address_from_expr(a_expr)?;
1573            let offsets_found_a = ptr_a.offsets_found;
1574            let dst_a = self
1575                .builder
1576                .build_bit_cast(buf_a, ptr_ty, "memcmp_dst_a")
1577                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1578            let base_src_a = self
1579                .builder
1580                .build_int_to_ptr(ptr_a.value, ptr_ty, "memcmp_src_a")
1581                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1582            let null_ptr = ptr_ty.const_null();
1583            let src_a = self
1584                .builder
1585                .build_select::<BasicValueEnum<'ctx>, _>(
1586                    offsets_found_a,
1587                    base_src_a.into(),
1588                    null_ptr.into(),
1589                    "memcmp_src_a_or_null",
1590                )
1591                .map_err(|e| CodeGenError::Builder(e.to_string()))?
1592                .into_pointer_value();
1593            let zero_i32 = self.context.i32_type().const_zero();
1594            let effective_len_a = self
1595                .builder
1596                .build_select::<BasicValueEnum<'ctx>, _>(
1597                    offsets_found_a,
1598                    sel_len.into(),
1599                    zero_i32.into(),
1600                    "memcmp_len_a_or_zero",
1601                )
1602                .map_err(|e| CodeGenError::Builder(e.to_string()))?
1603                .into_int_value();
1604            let ret_a = self
1605                .create_bpf_helper_call(
1606                    BPF_FUNC_probe_read_user as u64,
1607                    &[dst_a, effective_len_a.into(), src_a.into()],
1608                    self.context.i64_type().into(),
1609                    "probe_read_user_memcmp_a",
1610                )?
1611                .into_int_value();
1612            let i64_ty = self.context.i64_type();
1613            let eq_a = self
1614                .builder
1615                .build_int_compare(
1616                    inkwell::IntPredicate::EQ,
1617                    ret_a,
1618                    i64_ty.const_zero(),
1619                    "memcmp_ok_a",
1620                )
1621                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1622            self.builder
1623                .build_and(eq_a, offsets_found_a, "memcmp_ok_a")
1624                .map_err(|e| CodeGenError::Builder(e.to_string()))?
1625        };
1626
1627        // Side B
1628        if parse_hex_bytes(b_expr).is_none() {
1629            self.ensure_dwarf_pointer_arg(b_expr, "memcmp arg1")?;
1630        }
1631        let ok_b = if let Some(bytes) = parse_hex_bytes(b_expr) {
1632            let i32_ty = self.context.i32_type();
1633            let idx0 = i32_ty.const_zero();
1634            for i in 0..(cap as usize) {
1635                let idx_i = i32_ty.const_int(i as u64, false);
1636                // SAFETY: buf_b is a cap-sized array and i is bounded by cap.
1637                let pb = unsafe {
1638                    self.builder
1639                        .build_gep(arr_b_ty, buf_b, &[idx0, idx_i], &format!("hex_b_i{i}"))
1640                        .map_err(|e| CodeGenError::Builder(e.to_string()))?
1641                };
1642                let byte = if i < bytes.len() { bytes[i] } else { 0 } as u64;
1643                let bv = self.context.i8_type().const_int(byte, false);
1644                self.builder
1645                    .build_store(pb, bv)
1646                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1647            }
1648            self.context.bool_type().const_int(1, false)
1649        } else {
1650            // Resolve pointer for B and read from user memory
1651            let ptr_b = self.resolve_runtime_address_from_expr(b_expr)?;
1652            let offsets_found_b = ptr_b.offsets_found;
1653            let dst_b = self
1654                .builder
1655                .build_bit_cast(buf_b, ptr_ty, "memcmp_dst_b")
1656                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1657            let base_src_b = self
1658                .builder
1659                .build_int_to_ptr(ptr_b.value, ptr_ty, "memcmp_src_b")
1660                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1661            let null_ptr = ptr_ty.const_null();
1662            let src_b = self
1663                .builder
1664                .build_select::<BasicValueEnum<'ctx>, _>(
1665                    offsets_found_b,
1666                    base_src_b.into(),
1667                    null_ptr.into(),
1668                    "memcmp_src_b_or_null",
1669                )
1670                .map_err(|e| CodeGenError::Builder(e.to_string()))?
1671                .into_pointer_value();
1672            let zero_i32 = self.context.i32_type().const_zero();
1673            let effective_len_b = self
1674                .builder
1675                .build_select::<BasicValueEnum<'ctx>, _>(
1676                    offsets_found_b,
1677                    sel_len.into(),
1678                    zero_i32.into(),
1679                    "memcmp_len_b_or_zero",
1680                )
1681                .map_err(|e| CodeGenError::Builder(e.to_string()))?
1682                .into_int_value();
1683            let ret_b = self
1684                .create_bpf_helper_call(
1685                    BPF_FUNC_probe_read_user as u64,
1686                    &[dst_b, effective_len_b.into(), src_b.into()],
1687                    self.context.i64_type().into(),
1688                    "probe_read_user_memcmp_b",
1689                )?
1690                .into_int_value();
1691            let i64_ty = self.context.i64_type();
1692            let eq_b = self
1693                .builder
1694                .build_int_compare(
1695                    inkwell::IntPredicate::EQ,
1696                    ret_b,
1697                    i64_ty.const_zero(),
1698                    "memcmp_ok_b",
1699                )
1700                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1701            self.builder
1702                .build_and(eq_b, offsets_found_b, "memcmp_ok_b")
1703                .map_err(|e| CodeGenError::Builder(e.to_string()))?
1704        };
1705
1706        let status_ok = self
1707            .builder
1708            .build_and(ok_a, ok_b, "memcmp_status_ok")
1709            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1710
1711        // If in condition context and either side failed, set condition error code = 1 (ProbeReadFailed)
1712        if self.condition_context_active {
1713            let not_a = self
1714                .builder
1715                .build_not(ok_a, "memcmp_fail_a")
1716                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1717            let not_b = self
1718                .builder
1719                .build_not(ok_b, "memcmp_fail_b")
1720                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1721            let any_fail = self
1722                .builder
1723                .build_or(not_a, not_b, "memcmp_any_fail")
1724                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1725            let func = self.current_function("compile memcmp condition error branch")?;
1726            let set_b = self.context.append_basic_block(func, "memcmp_set_err");
1727            let cont_b = self.context.append_basic_block(func, "memcmp_cont");
1728            self.builder
1729                .build_conditional_branch(any_fail, set_b, cont_b)
1730                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1731            self.builder.position_at_end(set_b);
1732            // Align error_code with VariableStatus::ReadError = 2
1733            let _ = self.set_condition_error_if_unset(2u8);
1734            // Decide which side failed (prefer recording the actual failing side)
1735            let not_a_val = self
1736                .builder
1737                .build_not(ok_a, "memcmp_fail_a_val")
1738                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1739            let not_b_val = self
1740                .builder
1741                .build_not(ok_b, "memcmp_fail_b_val")
1742                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1743            let cur_fn = self.current_function("compile memcmp failure address branch")?;
1744            let set_a_bb = self.context.append_basic_block(cur_fn, "set_addr_a");
1745            let check_b_bb = self.context.append_basic_block(cur_fn, "check_fail_b");
1746            let set_b_bb = self.context.append_basic_block(cur_fn, "set_addr_b");
1747            let after_set_bb = self.context.append_basic_block(cur_fn, "after_set_addr");
1748
1749            // Branch on A failure first
1750            self.builder
1751                .build_conditional_branch(not_a_val, set_a_bb, check_b_bb)
1752                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1753
1754            // set A address
1755            self.builder.position_at_end(set_a_bb);
1756            if let Some(pa) = match parse_hex_bytes(a_expr) {
1757                Some(_) => None,
1758                None => Some(self.resolve_ptr_i64_from_expr(a_expr)?),
1759            } {
1760                let _ = self.set_condition_error_addr_if_unset(pa);
1761            }
1762            self.builder
1763                .build_unconditional_branch(after_set_bb)
1764                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1765
1766            // check B failure and set B
1767            self.builder.position_at_end(check_b_bb);
1768            self.builder
1769                .build_conditional_branch(not_b_val, set_b_bb, after_set_bb)
1770                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1771            self.builder.position_at_end(set_b_bb);
1772            if let Some(pb) = match parse_hex_bytes(b_expr) {
1773                Some(_) => None,
1774                None => Some(self.resolve_ptr_i64_from_expr(b_expr)?),
1775            } {
1776                let _ = self.set_condition_error_addr_if_unset(pb);
1777            }
1778            self.builder
1779                .build_unconditional_branch(after_set_bb)
1780                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1781            self.builder.position_at_end(after_set_bb);
1782            // Build flags: bit0=A fail, bit1=B fail, bit2=len clamped, bit3=len<=0
1783            let i8t = self.context.i8_type();
1784            let b_a = self
1785                .builder
1786                .build_int_z_extend(
1787                    self.builder
1788                        .build_not(ok_a, "fa")
1789                        .map_err(|e| CodeGenError::Builder(e.to_string()))?,
1790                    i8t,
1791                    "fa8",
1792                )
1793                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1794            let b_b1 = self
1795                .builder
1796                .build_int_z_extend(
1797                    self.builder
1798                        .build_not(ok_b, "fb")
1799                        .map_err(|e| CodeGenError::Builder(e.to_string()))?,
1800                    i8t,
1801                    "fb8",
1802                )
1803                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1804            let sh1 = self
1805                .builder
1806                .build_left_shift(b_b1, i8t.const_int(1, false), "b_b_shift")
1807                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1808            // gt: len_nn > cap  (len clamped)
1809            let b_c = self
1810                .builder
1811                .build_int_z_extend(gt, i8t, "clamped8")
1812                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1813            let sh2 = self
1814                .builder
1815                .build_left_shift(b_c, i8t.const_int(2, false), "b_c_shift")
1816                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1817            // len<=0: reuse len_is_zero
1818            let b_z = self
1819                .builder
1820                .build_int_z_extend(len_is_zero, i8t, "len0_8")
1821                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1822            let sh3 = self
1823                .builder
1824                .build_left_shift(b_z, i8t.const_int(3, false), "b_z_shift")
1825                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1826            let f01 = self
1827                .builder
1828                .build_or(b_a, sh1, "f01")
1829                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1830            let f012 = self
1831                .builder
1832                .build_or(f01, sh2, "f012")
1833                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1834            let flags = self
1835                .builder
1836                .build_or(f012, sh3, "flags")
1837                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1838            let _ = self.or_condition_error_flags(flags);
1839            self.builder
1840                .build_unconditional_branch(cont_b)
1841                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1842            self.builder.position_at_end(cont_b);
1843        }
1844
1845        // Aggregate XOR/OR across 0..CAP, masked by (i < sel_len)
1846        let i32_ty = self.context.i32_type();
1847        let idx0 = i32_ty.const_zero();
1848        let mut acc = self.context.i8_type().const_zero();
1849        for i in 0..cap as usize {
1850            let idx_i = i32_ty.const_int(i as u64, false);
1851            // active = (i < sel_len)
1852            let active = self
1853                .builder
1854                .build_int_compare(
1855                    inkwell::IntPredicate::ULT,
1856                    idx_i,
1857                    sel_len,
1858                    &format!("memcmp_i{i}_active"),
1859                )
1860                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1861            // a[i]
1862            // SAFETY: i is bounded by cmp_bound, which is clamped to the buffer cap.
1863            let pa = unsafe {
1864                self.builder
1865                    .build_gep(arr_a_ty, buf_a, &[idx0, idx_i], &format!("memcmp_a_i{i}"))
1866                    .map_err(|e| CodeGenError::Builder(e.to_string()))?
1867            };
1868            let va = self
1869                .builder
1870                .build_load(self.context.i8_type(), pa, &format!("ld_a_{i}"))
1871                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1872            let va = match va {
1873                BasicValueEnum::IntValue(iv) => iv,
1874                _ => return Err(CodeGenError::LLVMError("memcmp load a != i8".into())),
1875            };
1876            // b[i]
1877            // SAFETY: i is bounded by cmp_bound, which is clamped to the buffer cap.
1878            let pb = unsafe {
1879                self.builder
1880                    .build_gep(arr_b_ty, buf_b, &[idx0, idx_i], &format!("memcmp_b_i{i}"))
1881                    .map_err(|e| CodeGenError::Builder(e.to_string()))?
1882            };
1883            let vb = self
1884                .builder
1885                .build_load(self.context.i8_type(), pb, &format!("ld_b_{i}"))
1886                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1887            let vb = match vb {
1888                BasicValueEnum::IntValue(iv) => iv,
1889                _ => return Err(CodeGenError::LLVMError("memcmp load b != i8".into())),
1890            };
1891            let diff = self
1892                .builder
1893                .build_xor(va, vb, &format!("memcmp_diff_{i}"))
1894                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1895            let zero8 = self.context.i8_type().const_zero();
1896            let masked = self
1897                .builder
1898                .build_select(active, diff, zero8, &format!("memcmp_masked_{i}"))
1899                .map_err(|e| CodeGenError::Builder(e.to_string()))?
1900                .into_int_value();
1901            acc = self
1902                .builder
1903                .build_or(acc, masked, &format!("memcmp_acc_{i}"))
1904                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1905        }
1906        let eq_bytes = self
1907            .builder
1908            .build_int_compare(
1909                inkwell::IntPredicate::EQ,
1910                acc,
1911                self.context.i8_type().const_zero(),
1912                "memcmp_acc_zero",
1913            )
1914            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1915        let nz_result = self
1916            .builder
1917            .build_and(status_ok, eq_bytes, "memcmp_and")
1918            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1919
1920        self.builder
1921            .build_unconditional_branch(cont_b)
1922            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1923        let nz_block = self.current_insert_block("finish memcmp non-zero block")?;
1924
1925        // Merge
1926        self.builder.position_at_end(cont_b);
1927        let phi = self
1928            .builder
1929            .build_phi(self.context.bool_type(), "memcmp_phi")
1930            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1931        phi.add_incoming(&[(&bool_true, zero_block), (&nz_result, nz_block)]);
1932        Ok(phi.as_basic_value())
1933    }
1934    /// Builtin strncmp/starts_with implementation: bounded byte-compare without NUL requirement.
1935    fn compile_bounded_compare_len_i32(
1936        &mut self,
1937        len_expr: &Expr,
1938        max_len: u32,
1939        name_prefix: &str,
1940    ) -> Result<(IntValue<'ctx>, IntValue<'ctx>)> {
1941        let len_val = self.compile_expr(len_expr)?;
1942        let len_iv = match len_val {
1943            BasicValueEnum::IntValue(iv) => iv,
1944            _ => {
1945                return Err(CodeGenError::TypeError(format!(
1946                    "{name_prefix} length must be an integer expression"
1947                )))
1948            }
1949        };
1950        let i32_ty = self.context.i32_type();
1951        let len_i32 = if len_iv.get_type().get_bit_width() > 32 {
1952            self.builder
1953                .build_int_truncate(len_iv, i32_ty, &format!("{name_prefix}_len_trunc"))
1954                .map_err(|e| CodeGenError::Builder(e.to_string()))?
1955        } else if len_iv.get_type().get_bit_width() < 32 {
1956            self.builder
1957                .build_int_z_extend(len_iv, i32_ty, &format!("{name_prefix}_len_zext"))
1958                .map_err(|e| CodeGenError::Builder(e.to_string()))?
1959        } else {
1960            len_iv
1961        };
1962        let zero_i32 = i32_ty.const_zero();
1963        let is_neg = self
1964            .builder
1965            .build_int_compare(
1966                inkwell::IntPredicate::SLT,
1967                len_i32,
1968                zero_i32,
1969                &format!("{name_prefix}_len_neg"),
1970            )
1971            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1972        let len_nn = self
1973            .builder
1974            .build_select(is_neg, zero_i32, len_i32, &format!("{name_prefix}_len_nn"))
1975            .map_err(|e| CodeGenError::Builder(e.to_string()))?
1976            .into_int_value();
1977        let max_const = i32_ty.const_int(max_len as u64, false);
1978        let gt = self
1979            .builder
1980            .build_int_compare(
1981                inkwell::IntPredicate::UGT,
1982                len_nn,
1983                max_const,
1984                &format!("{name_prefix}_len_gt"),
1985            )
1986            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
1987        let bounded_len = self
1988            .builder
1989            .build_select(gt, max_const, len_nn, &format!("{name_prefix}_len_sel"))
1990            .map_err(|e| CodeGenError::Builder(e.to_string()))?
1991            .into_int_value();
1992        let is_zero = self
1993            .builder
1994            .build_int_compare(
1995                inkwell::IntPredicate::EQ,
1996                bounded_len,
1997                zero_i32,
1998                &format!("{name_prefix}_len_is_zero"),
1999            )
2000            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2001        Ok((bounded_len, is_zero))
2002    }
2003
2004    fn compile_strncmp_builtin(
2005        &mut self,
2006        dwarf_expr: &Expr,
2007        lit: &str,
2008        n_expr: &Expr,
2009    ) -> Result<BasicValueEnum<'ctx>> {
2010        // Fast path: if the first argument is a script string variable or a string literal,
2011        // perform a compile-time bounded comparison and return a constant boolean.
2012        let immediate_bytes_opt = match dwarf_expr {
2013            Expr::Variable(name) => {
2014                if self
2015                    .get_variable_type(name)
2016                    .is_some_and(|t| matches!(t, crate::script::VarType::String))
2017                {
2018                    self.get_string_variable_bytes(name).cloned()
2019                } else {
2020                    None
2021                }
2022            }
2023            Expr::String(s) => {
2024                let mut b = s.as_bytes().to_vec();
2025                b.push(0);
2026                Some(b)
2027            }
2028            _ => None,
2029        };
2030
2031        if let Some(bytes) = immediate_bytes_opt {
2032            if let Expr::Int(n) = n_expr {
2033                let n_usize = std::cmp::min(
2034                    (*n).max(0) as usize,
2035                    self.compile_options.compare_cap as usize,
2036                );
2037                let content_len = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
2038                let cmp_len = std::cmp::min(n_usize, std::cmp::min(content_len, lit.len()));
2039                let equal = bytes.get(0..cmp_len).unwrap_or(&[])
2040                    == lit.as_bytes().get(0..cmp_len).unwrap_or(&[]);
2041                let bool_val = self
2042                    .context
2043                    .bool_type()
2044                    .const_int(if equal { 1 } else { 0 }, false);
2045                return Ok(bool_val.into());
2046            }
2047
2048            // Treat as bounded byte compare between two immediate strings
2049            let cap = self.compile_options.compare_cap as usize;
2050            let content_len = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
2051            let cmp_bound = std::cmp::min(cap, std::cmp::min(content_len, lit.len())) as u32;
2052            if cmp_bound == 0 {
2053                return Ok(self.context.bool_type().const_int(1, false).into());
2054            }
2055            let (bounded_len, _len_is_zero) =
2056                self.compile_bounded_compare_len_i32(n_expr, cmp_bound, "strncmp")?;
2057            let i32_ty = self.context.i32_type();
2058            let i8_ty = self.context.i8_type();
2059            let mut acc = i8_ty.const_zero();
2060            for (i, (byte, lit_byte)) in bytes
2061                .iter()
2062                .copied()
2063                .zip(lit.as_bytes().iter().copied())
2064                .take(cmp_bound as usize)
2065                .enumerate()
2066            {
2067                let active = self
2068                    .builder
2069                    .build_int_compare(
2070                        inkwell::IntPredicate::UGT,
2071                        bounded_len,
2072                        i32_ty.const_int(i as u64, false),
2073                        "strncmp_imm_active",
2074                    )
2075                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2076                let diff = i8_ty.const_int((byte ^ lit_byte) as u64, false);
2077                let active_diff = self
2078                    .builder
2079                    .build_select(active, diff, i8_ty.const_zero(), "strncmp_imm_diff")
2080                    .map_err(|e| CodeGenError::Builder(e.to_string()))?
2081                    .into_int_value();
2082                acc = self
2083                    .builder
2084                    .build_or(acc, active_diff, "strncmp_imm_acc")
2085                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2086            }
2087            let equal = self
2088                .builder
2089                .build_int_compare(
2090                    inkwell::IntPredicate::EQ,
2091                    acc,
2092                    i8_ty.const_zero(),
2093                    "strncmp_imm_eq",
2094                )
2095                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2096            return Ok(equal.into());
2097        }
2098
2099        // Determine pointer value (i64) of the target memory (DWARF or alias)
2100        // Prefer DWARF resolution for richer status/hints; fallback to generic pointer resolver.
2101        let ptr_i64 = match self.query_dwarf_for_complex_expr(dwarf_expr)? {
2102            Some(var) => {
2103                if let Some(ty) = var.dwarf_type.as_ref() {
2104                    let ty = ghostscope_dwarf::strip_type_aliases(ty);
2105                    match ty {
2106                        DwarfType::PointerType { .. } => {
2107                            let pc_address = self.get_compile_time_context()?.pc_address;
2108                            let val_any =
2109                                self.variable_read_plan_to_llvm_value(&var, pc_address, None)?;
2110                            match val_any {
2111                                BasicValueEnum::IntValue(iv) => {
2112                                    RuntimeAddress::available(iv, self.context)
2113                                }
2114                                BasicValueEnum::PointerValue(pv) => self
2115                                    .builder
2116                                    .build_ptr_to_int(pv, self.context.i64_type(), "ptr_as_i64")
2117                                    .map(|value| RuntimeAddress::available(value, self.context))
2118                                    .map_err(|e| CodeGenError::Builder(e.to_string()))?,
2119                                _ => {
2120                                    return Err(CodeGenError::TypeError(
2121                                        "strncmp requires pointer/integer value for pointer; got unsupported DWARF value".into(),
2122                                    ))
2123                                }
2124                            }
2125                        }
2126                        DwarfType::ArrayType { .. } => {
2127                            let status_ptr = if self.condition_context_active {
2128                                Some(self.get_or_create_cond_error_global())
2129                            } else {
2130                                None
2131                            };
2132                            let pc_address = self.get_compile_time_context()?.pc_address;
2133                            self.variable_read_plan_to_runtime_address(
2134                                &var, pc_address, status_ptr,
2135                            )?
2136                        }
2137                        _ => {
2138                            // Not a pointer/array -> treat as error
2139                            return Err(CodeGenError::TypeError(
2140                                "strncmp requires the non-string side to be an address expression (pointer/array)".into(),
2141                            ));
2142                        }
2143                    }
2144                } else {
2145                    return Err(CodeGenError::TypeError(
2146                        "strncmp non-string side lacks DWARF type info".into(),
2147                    ));
2148                }
2149            }
2150            None => {
2151                // Generic pointer expr (e.g., alias); resolve to i64
2152                self.resolve_runtime_address_from_expr(dwarf_expr).map_err(|_| {
2153                    CodeGenError::TypeError(
2154                        "strncmp requires at least one string argument, and the other side must be an address expression (DWARF pointer/array or alias)".to_string(),
2155                    )
2156                })?
2157            }
2158        };
2159
2160        let cap = self.compile_options.compare_cap;
2161        let cmp_bound = std::cmp::min(lit.len() as u32, cap);
2162        if cmp_bound == 0 {
2163            return Ok(self.context.bool_type().const_int(1, false).into());
2164        }
2165        let (bounded_len, len_is_zero) =
2166            self.compile_bounded_compare_len_i32(n_expr, cmp_bound, "strncmp")?;
2167
2168        let func = self.current_function("compile strncmp length branch")?;
2169        let zero_b = self.context.append_basic_block(func, "strncmp_len_zero");
2170        let nz_b = self.context.append_basic_block(func, "strncmp_len_nz");
2171        let final_b = self.context.append_basic_block(func, "strncmp_len_cont");
2172        self.builder
2173            .build_conditional_branch(len_is_zero, zero_b, nz_b)
2174            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2175
2176        self.builder.position_at_end(zero_b);
2177        let bool_true = self.context.bool_type().const_int(1, false);
2178        self.builder
2179            .build_unconditional_branch(final_b)
2180            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2181        let zero_block = self.current_insert_block("finish strncmp zero-length block")?;
2182
2183        self.builder.position_at_end(nz_b);
2184
2185        let (arr_ty, buf_global) = self.get_or_create_i8_buffer(cmp_bound, "_gs_bi_strncmp");
2186        let ptr_ty = self.context.ptr_type(AddressSpace::default());
2187        let dst_ptr = self
2188            .builder
2189            .build_bit_cast(buf_global, ptr_ty, "strncmp_dst_ptr")
2190            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2191        let base_src_ptr = self
2192            .builder
2193            .build_int_to_ptr(ptr_i64.value, ptr_ty, "strncmp_src_ptr")
2194            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2195        let src_ptr = self
2196            .builder
2197            .build_select::<BasicValueEnum<'ctx>, _>(
2198                ptr_i64.offsets_found,
2199                base_src_ptr.into(),
2200                ptr_ty.const_null().into(),
2201                "strncmp_src_or_null",
2202            )
2203            .map_err(|e| CodeGenError::Builder(e.to_string()))?
2204            .into_pointer_value();
2205        let effective_len = self
2206            .builder
2207            .build_select::<BasicValueEnum<'ctx>, _>(
2208                ptr_i64.offsets_found,
2209                bounded_len.into(),
2210                self.context.i32_type().const_zero().into(),
2211                "strncmp_len_or_zero",
2212            )
2213            .map_err(|e| CodeGenError::Builder(e.to_string()))?
2214            .into_int_value();
2215        let ret = self
2216            .create_bpf_helper_call(
2217                BPF_FUNC_probe_read_user as u64,
2218                &[dst_ptr, effective_len.into(), src_ptr.into()],
2219                self.context.i64_type().into(),
2220                "probe_read_user_strncmp",
2221            )?
2222            .into_int_value();
2223        let read_ok = self
2224            .builder
2225            .build_int_compare(
2226                inkwell::IntPredicate::EQ,
2227                ret,
2228                self.context.i64_type().const_zero(),
2229                "rd_ok",
2230            )
2231            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2232        let status_ok = self
2233            .builder
2234            .build_and(read_ok, ptr_i64.offsets_found, "strncmp_ok_with_offsets")
2235            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2236
2237        // If in condition context and read failed, set condition error code = 1 (ProbeReadFailed)
2238        if self.condition_context_active {
2239            let func = self.current_function("compile strncmp condition error branch")?;
2240            let set_b = self.context.append_basic_block(func, "strncmp_set_err");
2241            let cont_b = self.context.append_basic_block(func, "strncmp_cont");
2242            let not_ok = self
2243                .builder
2244                .build_not(status_ok, "rd_fail")
2245                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2246            self.builder
2247                .build_conditional_branch(not_ok, set_b, cont_b)
2248                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2249            self.builder.position_at_end(set_b);
2250            // VariableStatus::ReadError = 2
2251            let _ = self.set_condition_error_if_unset(2u8);
2252            let _ = self.set_condition_error_addr_if_unset(ptr_i64.value);
2253            // flags: bit0 = read failure for strncmp
2254            let one = self.context.i8_type().const_int(1, false);
2255            let _ = self.or_condition_error_flags(one);
2256            self.builder
2257                .build_unconditional_branch(cont_b)
2258                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2259            self.builder.position_at_end(cont_b);
2260        }
2261
2262        // XOR/OR accumulation over the bounded maximum; inactive bytes do not contribute.
2263        let i32_ty = self.context.i32_type();
2264        let idx0 = i32_ty.const_zero();
2265        let mut acc = self.context.i8_type().const_zero();
2266        for (i, b) in lit.as_bytes().iter().take(cmp_bound as usize).enumerate() {
2267            let idx_i = i32_ty.const_int(i as u64, false);
2268            // SAFETY: i is bounded by cmp_bound, which is clamped to the buffer cap.
2269            let ptr_i = unsafe {
2270                self.builder
2271                    .build_gep(arr_ty, buf_global, &[idx0, idx_i], "ch_ptr")
2272                    .map_err(|e| CodeGenError::Builder(e.to_string()))?
2273            };
2274            let ch = self
2275                .builder
2276                .build_load(self.context.i8_type(), ptr_i, "ch")
2277                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2278            let ch = match ch {
2279                BasicValueEnum::IntValue(iv) => iv,
2280                _ => return Err(CodeGenError::LLVMError("load did not return i8".into())),
2281            };
2282            let expect = self.context.i8_type().const_int(*b as u64, false);
2283            let diff = self
2284                .builder
2285                .build_xor(ch, expect, "diff")
2286                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2287            let active = self
2288                .builder
2289                .build_int_compare(
2290                    inkwell::IntPredicate::UGT,
2291                    bounded_len,
2292                    idx_i,
2293                    "strncmp_byte_active",
2294                )
2295                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2296            let diff = self
2297                .builder
2298                .build_select(
2299                    active,
2300                    diff,
2301                    self.context.i8_type().const_zero(),
2302                    "strncmp_active_diff",
2303                )
2304                .map_err(|e| CodeGenError::Builder(e.to_string()))?
2305                .into_int_value();
2306            acc = self
2307                .builder
2308                .build_or(acc, diff, "acc_or")
2309                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2310        }
2311        let eq_bytes = self
2312            .builder
2313            .build_int_compare(
2314                inkwell::IntPredicate::EQ,
2315                acc,
2316                self.context.i8_type().const_zero(),
2317                "acc_zero",
2318            )
2319            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2320
2321        let result = self
2322            .builder
2323            .build_and(status_ok, eq_bytes, "strncmp_and")
2324            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2325        self.builder
2326            .build_unconditional_branch(final_b)
2327            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2328        let nz_block = self.current_insert_block("finish strncmp non-zero block")?;
2329
2330        self.builder.position_at_end(final_b);
2331        let result_phi = self
2332            .builder
2333            .build_phi(self.context.bool_type(), "strncmp_result")
2334            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2335        result_phi.add_incoming(&[(&bool_true, zero_block), (&result, nz_block)]);
2336        Ok(result_phi.as_basic_value())
2337    }
2338    /// Compile an expression
2339    pub fn compile_expr(&mut self, expr: &Expr) -> Result<BasicValueEnum<'ctx>> {
2340        match expr {
2341            Expr::Int(value) => {
2342                // Treat script integer literals as signed i64 constants
2343                let int_value = self.context.i64_type().const_int(*value as u64, true);
2344                debug!(
2345                    "compile_expr: Int literal {} compiled to IntValue with bit width {}",
2346                    value,
2347                    int_value.get_type().get_bit_width()
2348                );
2349                Ok(int_value.into())
2350            }
2351            Expr::Float(_value) => Err(CodeGenError::TypeError(
2352                "Floating point expressions are not supported".to_string(),
2353            )),
2354            Expr::String(value) => {
2355                // Create string constant using a simpler approach
2356                let string_value = self.context.const_string(value.as_bytes(), true);
2357                let global = self
2358                    .module
2359                    .add_global(string_value.get_type(), None, "str_const");
2360                global.set_initializer(&string_value);
2361
2362                let ptr_type = self.context.ptr_type(AddressSpace::default());
2363                let cast_ptr = self
2364                    .builder
2365                    .build_bit_cast(global.as_pointer_value(), ptr_type, "str_ptr")
2366                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2367                Ok(cast_ptr)
2368            }
2369            Expr::Bool(value) => {
2370                // Represent booleans as i1 for logical/compare consistency
2371                let b = self
2372                    .context
2373                    .bool_type()
2374                    .const_int(if *value { 1 } else { 0 }, false);
2375                Ok(b.into())
2376            }
2377            Expr::UnaryNot(inner) => {
2378                // Compile operand to integer and compare EQ to zero to produce boolean not
2379                let v = self.compile_expr(inner)?;
2380                let iv = match v {
2381                    BasicValueEnum::IntValue(iv) => iv,
2382                    _ => {
2383                        return Err(CodeGenError::TypeError(
2384                            "Logical NOT requires integer/boolean operand".to_string(),
2385                        ))
2386                    }
2387                };
2388                let zero = iv.get_type().const_zero();
2389                let res = self
2390                    .builder
2391                    .build_int_compare(inkwell::IntPredicate::EQ, iv, zero, "not_eq0")
2392                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2393                Ok(res.into())
2394            }
2395            Expr::UnaryBitNot(inner) => {
2396                let unsigned_width = self
2397                    .dwarf_integer_comparison_expr(inner)
2398                    .map(CIntegerComparisonType::promoted)
2399                    .and_then(|integer_type| {
2400                        integer_type
2401                            .is_unsigned
2402                            .then_some((integer_type.size * 8) as u32)
2403                    });
2404                let v = self.compile_expr(inner)?;
2405                let iv = match v {
2406                    BasicValueEnum::IntValue(iv) => iv,
2407                    _ => {
2408                        return Err(CodeGenError::TypeError(
2409                            "Bitwise NOT requires integer/boolean operand".to_string(),
2410                        ))
2411                    }
2412                };
2413                if let Some(bit_width) = unsigned_width {
2414                    let iv =
2415                        self.normalize_int_for_unsigned_compare(iv, bit_width, "bitnot_unsigned")?;
2416                    let result = self
2417                        .builder
2418                        .build_xor(iv, iv.get_type().const_all_ones(), "bitnot")
2419                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2420                    return self
2421                        .zero_extend_int_to_i64_if_needed(result, "bitnot_zext_i64")
2422                        .map(|value| value.into());
2423                }
2424                let iv = if iv.get_type().get_bit_width() == 1 {
2425                    self.builder
2426                        .build_int_z_extend(iv, self.context.i64_type(), "bitnot_bool_i64")
2427                        .map_err(|e| CodeGenError::Builder(e.to_string()))?
2428                } else {
2429                    iv
2430                };
2431                let all_ones = iv.get_type().const_all_ones();
2432                let result = self
2433                    .builder
2434                    .build_xor(iv, all_ones, "bitnot")
2435                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2436                Ok(result.into())
2437            }
2438            Expr::Variable(var_name) => {
2439                debug!("compile_expr: Compiling variable expression: {}", var_name);
2440
2441                // First: DWARF alias variable takes precedence
2442                if self.alias_variable_exists(var_name) {
2443                    debug!(
2444                        "compile_expr: '{}' is an alias variable; resolving to runtime address",
2445                        var_name
2446                    );
2447                    let aliased = self
2448                        .get_alias_variable(var_name)
2449                        .expect("alias existence just checked");
2450                    // Resolve to i64 address then cast to ptr
2451                    let addr_i64 = self.resolve_ptr_i64_from_expr(&aliased)?;
2452                    let ptr_ty = self.context.ptr_type(AddressSpace::default());
2453                    let as_ptr = self
2454                        .builder
2455                        .build_int_to_ptr(addr_i64, ptr_ty, "alias_as_ptr")
2456                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2457                    return Ok(as_ptr.into());
2458                }
2459
2460                // Then check if it's a concrete script-defined variable
2461                if self.variable_exists(var_name) {
2462                    debug!("compile_expr: Found script variable: {}", var_name);
2463                    let loaded_value = self.load_variable(var_name)?;
2464                    debug!(
2465                        "compile_expr: Loaded variable '{}' with type: {:?}",
2466                        var_name,
2467                        loaded_value.get_type()
2468                    );
2469                    match &loaded_value {
2470                        BasicValueEnum::IntValue(iv) => debug!(
2471                            "compile_expr: Variable '{}' is IntValue with bit width {}",
2472                            var_name,
2473                            iv.get_type().get_bit_width()
2474                        ),
2475                        BasicValueEnum::FloatValue(_) => {
2476                            debug!("compile_expr: Variable '{}' is FloatValue", var_name)
2477                        }
2478                        BasicValueEnum::PointerValue(_) => {
2479                            debug!("compile_expr: Variable '{}' is PointerValue", var_name)
2480                        }
2481                        _ => debug!("compile_expr: Variable '{}' is other type", var_name),
2482                    }
2483                    return Ok(loaded_value);
2484                }
2485
2486                // If not found in script variables nor alias map, try DWARF variables
2487                debug!(
2488                    "Variable '{}' not found in script variables, checking DWARF",
2489                    var_name
2490                );
2491                // If not a DWARF variable either, treat as out-of-scope script name for friendliness
2492                match self.query_dwarf_for_variable(var_name) {
2493                    Ok(Some(_)) => self.compile_dwarf_expression(expr),
2494                    Ok(None) => Err(CodeGenError::VariableNotInScope(var_name.clone())),
2495                    Err(e) => Err(CodeGenError::DwarfError(e.to_string())),
2496                }
2497            }
2498            Expr::SpecialVar(name) => {
2499                // Accept both "$pid" and "pid" forms from the parser
2500                let sanitized = name.trim_start_matches('$');
2501                self.handle_special_variable(sanitized)
2502            }
2503            Expr::BuiltinCall { name, args } => match self.plan_builtin_call(name, args)? {
2504                BuiltinCallPlan::Memcmp => {
2505                    self.compile_memcmp_builtin(&args[0], &args[1], &args[2])
2506                }
2507                BuiltinCallPlan::Strncmp => {
2508                    // Accept string on either side: string literal or script string variable
2509                    fn extract_script_string(
2510                        this: &mut EbpfContext<'_, '_>,
2511                        e: &Expr,
2512                    ) -> Option<String> {
2513                        match e {
2514                            Expr::String(s) => Some(s.clone()),
2515                            Expr::Variable(name) => this
2516                                .get_variable_type(name)
2517                                .is_some_and(|t| matches!(t, crate::script::VarType::String))
2518                                .then(|| {
2519                                    this.get_string_variable_bytes(name).map(|b| {
2520                                        let cut = b.iter().position(|&x| x == 0).unwrap_or(b.len());
2521                                        String::from_utf8_lossy(&b[..cut]).to_string()
2522                                    })
2523                                })
2524                                .flatten(),
2525                            _ => None,
2526                        }
2527                    }
2528                    let left_str = extract_script_string(self, &args[0]);
2529                    let right_str = extract_script_string(self, &args[1]);
2530                    match (left_str, right_str) {
2531                        (Some(ls), Some(rs)) => {
2532                            let left_expr = Expr::String(ls);
2533                            self.compile_strncmp_builtin(&left_expr, &rs, &args[2])
2534                        }
2535                        (Some(ls), None) => self.compile_strncmp_builtin(&args[1], &ls, &args[2]),
2536                        (None, Some(rs)) => self.compile_strncmp_builtin(&args[0], &rs, &args[2]),
2537                        (None, None) => Err(CodeGenError::TypeError(
2538                            "strncmp requires at least one string argument (string literal or script string variable) as the first or second parameter".into(),
2539                        )),
2540                    }
2541                }
2542                BuiltinCallPlan::StartsWith => {
2543                    // Accept string on either side (literal or script string var)
2544                    fn extract_script_string(
2545                        this: &mut EbpfContext<'_, '_>,
2546                        e: &Expr,
2547                    ) -> Option<String> {
2548                        match e {
2549                            Expr::String(s) => Some(s.clone()),
2550                            Expr::Variable(name) => this
2551                                .get_variable_type(name)
2552                                .is_some_and(|t| matches!(t, crate::script::VarType::String))
2553                                .then(|| {
2554                                    this.get_string_variable_bytes(name).map(|b| {
2555                                        let cut = b.iter().position(|&x| x == 0).unwrap_or(b.len());
2556                                        String::from_utf8_lossy(&b[..cut]).to_string()
2557                                    })
2558                                })
2559                                .flatten(),
2560                            _ => None,
2561                        }
2562                    }
2563                    let s0 = extract_script_string(self, &args[0]);
2564                    let s1 = extract_script_string(self, &args[1]);
2565                    match (s0, s1) {
2566                        (Some(a), Some(b)) => {
2567                            // both strings -> compile-time fold
2568                            let ok = a.as_bytes().starts_with(b.as_bytes());
2569                            let bv = self.context.bool_type().const_int(ok as u64, false);
2570                            Ok(bv.into())
2571                        }
2572                        (Some(a), None) => {
2573                            let n_expr = Expr::Int(a.len() as i64);
2574                            self.compile_strncmp_builtin(&args[1], &a, &n_expr)
2575                        }
2576                        (None, Some(b)) => {
2577                            let n_expr = Expr::Int(b.len() as i64);
2578                            self.compile_strncmp_builtin(&args[0], &b, &n_expr)
2579                        }
2580                        (None, None) => Err(CodeGenError::TypeError(
2581                            "starts_with requires at least one string argument (string literal or script string variable) as the first or second parameter".into(),
2582                        )),
2583                    }
2584                }
2585            },
2586            Expr::BinaryOp { left, op, right } => {
2587                let binary_plan = self.plan_binary_expr(left, op, right)?;
2588                if let BinaryEmitKind::StringComparison(string_plan) = &binary_plan.emit_kind {
2589                    let other = if string_plan.literal_on_left {
2590                        right.as_ref()
2591                    } else {
2592                        left.as_ref()
2593                    };
2594                    return self.compile_string_comparison(
2595                        other,
2596                        &string_plan.literal,
2597                        string_plan.equal,
2598                    );
2599                }
2600                // Implement short-circuit for logical OR (||) and logical AND (&&)
2601                if matches!(&binary_plan.emit_kind, BinaryEmitKind::LogicalOr) {
2602                    // Evaluate LHS to boolean (non-zero => true). Accept integer or pointer.
2603                    let lhs_val = self.compile_expr(left)?;
2604                    let lhs_int = match lhs_val {
2605                        BasicValueEnum::IntValue(iv) => iv,
2606                        BasicValueEnum::PointerValue(pv) => self
2607                            .builder
2608                            .build_ptr_to_int(pv, self.context.i64_type(), "lor_lhs_ptr_as_i64")
2609                            .map_err(|e| CodeGenError::Builder(e.to_string()))?,
2610                        _ => {
2611                            return Err(CodeGenError::TypeError(
2612                                "Logical OR requires integer or pointer operands".to_string(),
2613                            ))
2614                        }
2615                    };
2616                    let lhs_zero = lhs_int.get_type().const_zero();
2617                    let lhs_bool = self
2618                        .builder
2619                        .build_int_compare(
2620                            inkwell::IntPredicate::NE,
2621                            lhs_int,
2622                            lhs_zero,
2623                            "lor_lhs_nz",
2624                        )
2625                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2626
2627                    // Prepare control flow blocks
2628                    let curr_block = self.builder.get_insert_block().ok_or_else(|| {
2629                        CodeGenError::LLVMError("No current basic block".to_string())
2630                    })?;
2631                    let func = curr_block
2632                        .get_parent()
2633                        .ok_or_else(|| CodeGenError::LLVMError("No parent function".to_string()))?;
2634                    let rhs_block = self.context.append_basic_block(func, "lor_rhs");
2635                    let merge_block = self.context.append_basic_block(func, "lor_merge");
2636
2637                    // If lhs is true, jump directly to merge (short-circuit)
2638                    self.builder
2639                        .build_conditional_branch(lhs_bool, merge_block, rhs_block)
2640                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2641
2642                    // RHS path: compute boolean only if needed
2643                    self.builder.position_at_end(rhs_block);
2644                    let rhs_val = self.compile_expr(right)?;
2645                    let rhs_int = match rhs_val {
2646                        BasicValueEnum::IntValue(iv) => iv,
2647                        BasicValueEnum::PointerValue(pv) => self
2648                            .builder
2649                            .build_ptr_to_int(pv, self.context.i64_type(), "lor_rhs_ptr_as_i64")
2650                            .map_err(|e| CodeGenError::Builder(e.to_string()))?,
2651                        _ => {
2652                            return Err(CodeGenError::TypeError(
2653                                "Logical OR requires integer or pointer operands".to_string(),
2654                            ))
2655                        }
2656                    };
2657                    let rhs_zero = rhs_int.get_type().const_zero();
2658                    let rhs_bool = self
2659                        .builder
2660                        .build_int_compare(
2661                            inkwell::IntPredicate::NE,
2662                            rhs_int,
2663                            rhs_zero,
2664                            "lor_rhs_nz",
2665                        )
2666                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2667                    // Capture the actual block where RHS computation ended
2668                    let rhs_end_block = self.builder.get_insert_block().ok_or_else(|| {
2669                        CodeGenError::LLVMError("No current basic block after RHS".to_string())
2670                    })?;
2671                    self.builder
2672                        .build_unconditional_branch(merge_block)
2673                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2674
2675                    // Merge: phi of i1: true from LHS-true, RHS bool from rhs_block
2676                    self.builder.position_at_end(merge_block);
2677                    let i1 = self.context.bool_type();
2678                    let phi = self
2679                        .builder
2680                        .build_phi(i1, "lor_phi")
2681                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2682                    let one = i1.const_int(1, false);
2683                    phi.add_incoming(&[(&one, curr_block), (&rhs_bool, rhs_end_block)]);
2684                    return Ok(phi.as_basic_value());
2685                } else if matches!(&binary_plan.emit_kind, BinaryEmitKind::LogicalAnd) {
2686                    // Evaluate LHS to boolean (non-zero => true). Accept integer or pointer.
2687                    let lhs_val = self.compile_expr(left)?;
2688                    let lhs_int = match lhs_val {
2689                        BasicValueEnum::IntValue(iv) => iv,
2690                        BasicValueEnum::PointerValue(pv) => self
2691                            .builder
2692                            .build_ptr_to_int(pv, self.context.i64_type(), "land_lhs_ptr_as_i64")
2693                            .map_err(|e| CodeGenError::Builder(e.to_string()))?,
2694                        _ => {
2695                            return Err(CodeGenError::TypeError(
2696                                "Logical AND requires integer or pointer operands".to_string(),
2697                            ))
2698                        }
2699                    };
2700                    let lhs_zero = lhs_int.get_type().const_zero();
2701                    let lhs_bool = self
2702                        .builder
2703                        .build_int_compare(
2704                            inkwell::IntPredicate::NE,
2705                            lhs_int,
2706                            lhs_zero,
2707                            "land_lhs_nz",
2708                        )
2709                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2710
2711                    // Prepare control flow: if lhs is true, evaluate rhs; else short-circuit to false
2712                    let curr_block = self.builder.get_insert_block().ok_or_else(|| {
2713                        CodeGenError::LLVMError("No current basic block".to_string())
2714                    })?;
2715                    let func = curr_block
2716                        .get_parent()
2717                        .ok_or_else(|| CodeGenError::LLVMError("No parent function".to_string()))?;
2718                    let rhs_block = self.context.append_basic_block(func, "land_rhs");
2719                    let merge_block = self.context.append_basic_block(func, "land_merge");
2720
2721                    // If lhs is true, go compute rhs; else jump to merge with false
2722                    self.builder
2723                        .build_conditional_branch(lhs_bool, rhs_block, merge_block)
2724                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2725
2726                    // RHS path
2727                    self.builder.position_at_end(rhs_block);
2728                    let rhs_val = self.compile_expr(right)?;
2729                    let rhs_int = match rhs_val {
2730                        BasicValueEnum::IntValue(iv) => iv,
2731                        BasicValueEnum::PointerValue(pv) => self
2732                            .builder
2733                            .build_ptr_to_int(pv, self.context.i64_type(), "land_rhs_ptr_as_i64")
2734                            .map_err(|e| CodeGenError::Builder(e.to_string()))?,
2735                        _ => {
2736                            return Err(CodeGenError::TypeError(
2737                                "Logical AND requires integer or pointer operands".to_string(),
2738                            ))
2739                        }
2740                    };
2741                    let rhs_zero = rhs_int.get_type().const_zero();
2742                    let rhs_bool = self
2743                        .builder
2744                        .build_int_compare(
2745                            inkwell::IntPredicate::NE,
2746                            rhs_int,
2747                            rhs_zero,
2748                            "land_rhs_nz",
2749                        )
2750                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2751                    let rhs_end_block = self.builder.get_insert_block().ok_or_else(|| {
2752                        CodeGenError::LLVMError("No current basic block after RHS".to_string())
2753                    })?;
2754                    self.builder
2755                        .build_unconditional_branch(merge_block)
2756                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2757
2758                    // Merge: phi(i1) with false from LHS=false path, RHS bool from rhs path
2759                    self.builder.position_at_end(merge_block);
2760                    let i1 = self.context.bool_type();
2761                    let phi = self
2762                        .builder
2763                        .build_phi(i1, "land_phi")
2764                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
2765                    let zero = i1.const_zero();
2766                    phi.add_incoming(&[(&rhs_bool, rhs_end_block), (&zero, curr_block)]);
2767                    return Ok(phi.as_basic_value());
2768                }
2769
2770                // Default eager evaluation for other binary ops
2771                let left_val = self.compile_expr(left)?;
2772                let right_val = self.compile_expr(right)?;
2773                self.compile_binary_op_with_ordering(
2774                    left_val,
2775                    binary_plan.op,
2776                    right_val,
2777                    binary_plan.integer_semantics,
2778                )
2779            }
2780            Expr::MemberAccess(_, _) => {
2781                // Use unified DWARF expression compilation
2782                self.compile_dwarf_expression(expr)
2783            }
2784            Expr::PointerDeref(_) => {
2785                // Use unified DWARF expression compilation
2786                self.compile_dwarf_expression(expr)
2787            }
2788            Expr::AddressOf(inner) => {
2789                // Address-of computes runtime addresses using the module origin carried by the plan.
2790                // Transparently support alias variables: &alias -> address of aliased DWARF expression
2791                let target_inner: &Expr = if let Expr::Variable(var_name) = inner.as_ref() {
2792                    if self.alias_variable_exists(var_name) {
2793                        // Use the aliased target expression (by-value) and query DWARF on it
2794                        let aliased = self
2795                            .get_alias_variable(var_name)
2796                            .expect("alias existence just checked");
2797                        let var =
2798                            self.query_dwarf_for_complex_expr(&aliased)?
2799                                .ok_or_else(|| {
2800                                    super::context::CodeGenError::TypeError(
2801                                        "cannot take address of unresolved expression".to_string(),
2802                                    )
2803                                })?;
2804                        let pc_address = self.get_compile_time_context()?.pc_address;
2805                        match self.variable_read_plan_to_runtime_address(&var, pc_address, None) {
2806                            Ok(address) => {
2807                                let ptr_ty = self.context.ptr_type(AddressSpace::default());
2808                                let as_ptr = self
2809                                    .builder
2810                                    .build_int_to_ptr(address.value, ptr_ty, "addr_as_ptr")
2811                                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2812                                return Ok(as_ptr.into());
2813                            }
2814                            Err(_) => {
2815                                return Err(super::context::CodeGenError::TypeError(
2816                                    "cannot take address of rvalue".to_string(),
2817                                ));
2818                            }
2819                        }
2820                    } else {
2821                        inner.as_ref()
2822                    }
2823                } else {
2824                    inner.as_ref()
2825                };
2826
2827                if let Some(lvalue) = self.dynamic_lvalue_address_and_type(target_inner)? {
2828                    let ptr_ty = self.context.ptr_type(AddressSpace::default());
2829                    let as_ptr = self
2830                        .builder
2831                        .build_int_to_ptr(lvalue.address.value, ptr_ty, "addr_as_ptr")
2832                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2833                    return Ok(as_ptr.into());
2834                }
2835
2836                let var = self
2837                    .query_dwarf_for_complex_expr(target_inner)?
2838                    .ok_or_else(|| {
2839                        super::context::CodeGenError::TypeError(
2840                            "cannot take address of unresolved expression".to_string(),
2841                        )
2842                    })?;
2843                let pc_address = self.get_compile_time_context()?.pc_address;
2844                match self.variable_read_plan_to_runtime_address(&var, pc_address, None) {
2845                    Ok(address) => {
2846                        let ptr_ty = self.context.ptr_type(AddressSpace::default());
2847                        let as_ptr = self
2848                            .builder
2849                            .build_int_to_ptr(address.value, ptr_ty, "addr_as_ptr")
2850                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2851                        Ok(as_ptr.into())
2852                    }
2853                    Err(_) => Err(super::context::CodeGenError::TypeError(
2854                        "cannot take address of rvalue".to_string(),
2855                    )),
2856                }
2857            }
2858            Expr::ArrayAccess(_, _) => {
2859                // Use unified DWARF expression compilation
2860                self.compile_dwarf_expression(expr)
2861            }
2862            Expr::Cast {
2863                expr: inner,
2864                target_type,
2865            } => self.compile_cast_expr_value(inner, target_type),
2866            Expr::ChainAccess(_) => {
2867                // Use unified DWARF expression compilation
2868                self.compile_dwarf_expression(expr)
2869            }
2870        }
2871    }
2872
2873    /// Handle special variables like $pid, $tid, etc.
2874    pub fn handle_special_variable(&mut self, name: &str) -> Result<BasicValueEnum<'ctx>> {
2875        match self.plan_special_variable(name)? {
2876            SpecialVarPlan::Pid => {
2877                let (pid, _tid) = self.get_special_pid_tid_values()?;
2878                Ok(pid.into())
2879            }
2880            SpecialVarPlan::Tid => {
2881                let (_pid, tid) = self.get_special_pid_tid_values()?;
2882                Ok(tid.into())
2883            }
2884            SpecialVarPlan::HostPid => {
2885                let (host_pid, _host_tid) = self.get_host_pid_tid_values()?;
2886                let host_pid = self
2887                    .builder
2888                    .build_int_z_extend(host_pid, self.context.i64_type(), "selected_host_pid")
2889                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2890                Ok(host_pid.into())
2891            }
2892            SpecialVarPlan::InputPid => {
2893                let input_pid = self.compile_options.input_pid.ok_or_else(|| {
2894                    CodeGenError::NotImplemented(
2895                        "Special variable '$input_pid' is only available in -p mode".to_string(),
2896                    )
2897                })?;
2898                Ok(self
2899                    .context
2900                    .i64_type()
2901                    .const_int(input_pid as u64, false)
2902                    .into())
2903            }
2904            SpecialVarPlan::Timestamp => {
2905                // Use BPF helper to get current timestamp
2906                let ts = self.get_current_timestamp()?;
2907                Ok(ts.into())
2908            }
2909            SpecialVarPlan::Pc => self.load_special_register_value(16),
2910            SpecialVarPlan::Sp => self.load_special_register_value(7),
2911        }
2912    }
2913
2914    fn load_special_register_value(&mut self, dwarf_reg: u16) -> Result<BasicValueEnum<'ctx>> {
2915        let pt_regs = self.get_pt_regs_parameter()?;
2916        self.load_register_value(dwarf_reg, pt_regs)
2917    }
2918
2919    /// Compile binary operations
2920    pub fn compile_binary_op(
2921        &mut self,
2922        left: BasicValueEnum<'ctx>,
2923        op: BinaryOp,
2924        right: BasicValueEnum<'ctx>,
2925    ) -> Result<BasicValueEnum<'ctx>> {
2926        self.compile_binary_op_with_ordering(left, op, right, BinaryIntegerSemantics::default())
2927    }
2928
2929    pub(crate) fn build_signed_int_div_via_udiv(
2930        &mut self,
2931        left: IntValue<'ctx>,
2932        right: IntValue<'ctx>,
2933        name: &str,
2934    ) -> Result<IntValue<'ctx>> {
2935        let int_type = left.get_type();
2936        let zero = int_type.const_zero();
2937        let left_is_neg = self
2938            .builder
2939            .build_int_compare(inkwell::IntPredicate::SLT, left, zero, "sdiv_lhs_neg")
2940            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2941        let right_is_neg = self
2942            .builder
2943            .build_int_compare(inkwell::IntPredicate::SLT, right, zero, "sdiv_rhs_neg")
2944            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2945        let neg_left = self
2946            .builder
2947            .build_int_sub(zero, left, "sdiv_lhs_negated")
2948            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2949        let neg_right = self
2950            .builder
2951            .build_int_sub(zero, right, "sdiv_rhs_negated")
2952            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2953        let abs_left = self
2954            .builder
2955            .build_select::<BasicValueEnum<'ctx>, _>(
2956                left_is_neg,
2957                neg_left.into(),
2958                left.into(),
2959                "sdiv_lhs_abs",
2960            )
2961            .map_err(|e| CodeGenError::Builder(e.to_string()))?
2962            .into_int_value();
2963        let abs_right = self
2964            .builder
2965            .build_select::<BasicValueEnum<'ctx>, _>(
2966                right_is_neg,
2967                neg_right.into(),
2968                right.into(),
2969                "sdiv_rhs_abs",
2970            )
2971            .map_err(|e| CodeGenError::Builder(e.to_string()))?
2972            .into_int_value();
2973        let abs_quotient = self
2974            .builder
2975            .build_int_unsigned_div(abs_left, abs_right, "sdiv_abs_udiv")
2976            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2977        let negative_result = self
2978            .builder
2979            .build_xor(left_is_neg, right_is_neg, "sdiv_result_neg")
2980            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2981        let neg_quotient = self
2982            .builder
2983            .build_int_sub(zero, abs_quotient, "sdiv_negated")
2984            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
2985        self.builder
2986            .build_select::<BasicValueEnum<'ctx>, _>(
2987                negative_result,
2988                neg_quotient.into(),
2989                abs_quotient.into(),
2990                name,
2991            )
2992            .map_err(|e| CodeGenError::Builder(e.to_string()))
2993            .map(|value| value.into_int_value())
2994    }
2995
2996    pub(crate) fn build_signed_int_rem_via_urem(
2997        &mut self,
2998        left: IntValue<'ctx>,
2999        right: IntValue<'ctx>,
3000        name: &str,
3001    ) -> Result<IntValue<'ctx>> {
3002        let int_type = left.get_type();
3003        let zero = int_type.const_zero();
3004        let left_is_neg = self
3005            .builder
3006            .build_int_compare(inkwell::IntPredicate::SLT, left, zero, "srem_lhs_neg")
3007            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3008        let right_is_neg = self
3009            .builder
3010            .build_int_compare(inkwell::IntPredicate::SLT, right, zero, "srem_rhs_neg")
3011            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3012        let neg_left = self
3013            .builder
3014            .build_int_sub(zero, left, "srem_lhs_negated")
3015            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3016        let neg_right = self
3017            .builder
3018            .build_int_sub(zero, right, "srem_rhs_negated")
3019            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3020        let abs_left = self
3021            .builder
3022            .build_select::<BasicValueEnum<'ctx>, _>(
3023                left_is_neg,
3024                neg_left.into(),
3025                left.into(),
3026                "srem_lhs_abs",
3027            )
3028            .map_err(|e| CodeGenError::Builder(e.to_string()))?
3029            .into_int_value();
3030        let abs_right = self
3031            .builder
3032            .build_select::<BasicValueEnum<'ctx>, _>(
3033                right_is_neg,
3034                neg_right.into(),
3035                right.into(),
3036                "srem_rhs_abs",
3037            )
3038            .map_err(|e| CodeGenError::Builder(e.to_string()))?
3039            .into_int_value();
3040        let abs_remainder = self
3041            .builder
3042            .build_int_unsigned_rem(abs_left, abs_right, "srem_abs_urem")
3043            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3044        let neg_remainder = self
3045            .builder
3046            .build_int_sub(zero, abs_remainder, "srem_negated")
3047            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3048        self.builder
3049            .build_select::<BasicValueEnum<'ctx>, _>(
3050                left_is_neg,
3051                neg_remainder.into(),
3052                abs_remainder.into(),
3053                name,
3054            )
3055            .map_err(|e| CodeGenError::Builder(e.to_string()))
3056            .map(|value| value.into_int_value())
3057    }
3058
3059    fn normalize_int_for_unsigned_compare(
3060        &mut self,
3061        value: IntValue<'ctx>,
3062        bit_width: u32,
3063        name: &str,
3064    ) -> Result<IntValue<'ctx>> {
3065        let current_width = value.get_type().get_bit_width();
3066        if current_width == bit_width {
3067            return Ok(value);
3068        }
3069
3070        let target_type = self.context.custom_width_int_type(bit_width);
3071        if current_width > bit_width {
3072            self.builder
3073                .build_int_truncate(value, target_type, name)
3074                .map_err(|e| CodeGenError::Builder(e.to_string()))
3075        } else {
3076            self.builder
3077                .build_int_z_extend(value, target_type, name)
3078                .map_err(|e| CodeGenError::Builder(e.to_string()))
3079        }
3080    }
3081
3082    fn align_int_widths_for_binary_op(
3083        &mut self,
3084        left: IntValue<'ctx>,
3085        right: IntValue<'ctx>,
3086    ) -> Result<(IntValue<'ctx>, IntValue<'ctx>)> {
3087        let left_width = left.get_type().get_bit_width();
3088        let right_width = right.get_type().get_bit_width();
3089        if left_width == right_width {
3090            return Ok((left, right));
3091        }
3092
3093        let target_width = left_width.max(right_width);
3094        let target_type = self.context.custom_width_int_type(target_width);
3095        let left = if left_width < target_width {
3096            self.builder
3097                .build_int_z_extend(left, target_type, "lhs_width_align")
3098                .map_err(|e| CodeGenError::Builder(e.to_string()))?
3099        } else {
3100            left
3101        };
3102        let right = if right_width < target_width {
3103            self.builder
3104                .build_int_z_extend(right, target_type, "rhs_width_align")
3105                .map_err(|e| CodeGenError::Builder(e.to_string()))?
3106        } else {
3107            right
3108        };
3109        Ok((left, right))
3110    }
3111
3112    fn mask_shift_amount(&mut self, amount: IntValue<'ctx>, name: &str) -> Result<IntValue<'ctx>> {
3113        let bit_width = amount.get_type().get_bit_width();
3114        let mask = amount
3115            .get_type()
3116            .const_int(u64::from(bit_width.saturating_sub(1)), false);
3117        self.builder
3118            .build_and(amount, mask, name)
3119            .map_err(|e| CodeGenError::Builder(e.to_string()))
3120    }
3121
3122    fn normalize_ints_for_unsigned_width(
3123        &mut self,
3124        left: IntValue<'ctx>,
3125        right: IntValue<'ctx>,
3126        bit_width: u32,
3127        name: &str,
3128    ) -> Result<(IntValue<'ctx>, IntValue<'ctx>)> {
3129        let left = self.normalize_int_for_unsigned_compare(
3130            left,
3131            bit_width,
3132            &format!("{name}_lhs_unsigned"),
3133        )?;
3134        let right = self.normalize_int_for_unsigned_compare(
3135            right,
3136            bit_width,
3137            &format!("{name}_rhs_unsigned"),
3138        )?;
3139        Ok((left, right))
3140    }
3141
3142    fn zero_extend_int_to_i64_if_needed(
3143        &mut self,
3144        value: IntValue<'ctx>,
3145        name: &str,
3146    ) -> Result<IntValue<'ctx>> {
3147        if value.get_type().get_bit_width() >= 64 {
3148            return Ok(value);
3149        }
3150        self.builder
3151            .build_int_z_extend(value, self.context.i64_type(), name)
3152            .map_err(|e| CodeGenError::Builder(e.to_string()))
3153    }
3154
3155    fn compile_binary_op_with_ordering(
3156        &mut self,
3157        left: BasicValueEnum<'ctx>,
3158        op: BinaryOp,
3159        right: BasicValueEnum<'ctx>,
3160        integer_semantics: BinaryIntegerSemantics,
3161    ) -> Result<BasicValueEnum<'ctx>> {
3162        use inkwell::values::BasicValueEnum::*;
3163
3164        // Debug logging to understand the actual types
3165        debug!("compile_binary_op: op={:?}", op);
3166        debug!("compile_binary_op: left type = {:?}", left.get_type());
3167        debug!("compile_binary_op: right type = {:?}", right.get_type());
3168        match &left {
3169            IntValue(iv) => debug!(
3170                "compile_binary_op: left is IntValue with bit width {}",
3171                iv.get_type().get_bit_width()
3172            ),
3173            FloatValue(_) => debug!("compile_binary_op: left is FloatValue"),
3174            PointerValue(_) => debug!("compile_binary_op: left is PointerValue"),
3175            _ => debug!("compile_binary_op: left is other type"),
3176        }
3177        match &right {
3178            IntValue(iv) => debug!(
3179                "compile_binary_op: right is IntValue with bit width {}",
3180                iv.get_type().get_bit_width()
3181            ),
3182            FloatValue(_) => debug!("compile_binary_op: right is FloatValue"),
3183            PointerValue(_) => debug!("compile_binary_op: right is PointerValue"),
3184            _ => debug!("compile_binary_op: right is other type"),
3185        }
3186
3187        match (left, right) {
3188            (IntValue(left_int), IntValue(right_int)) => {
3189                let (left_int, right_int) =
3190                    self.align_int_widths_for_binary_op(left_int, right_int)?;
3191                let unsigned_cmp_values = if let Some(bit_width) =
3192                    integer_semantics.unsigned_ordering_width
3193                {
3194                    Some((
3195                        self.normalize_int_for_unsigned_compare(
3196                            left_int,
3197                            bit_width,
3198                            "lhs_unsigned_cmp",
3199                        )?,
3200                        self.normalize_int_for_unsigned_compare(
3201                            right_int,
3202                            bit_width,
3203                            "rhs_unsigned_cmp",
3204                        )?,
3205                    ))
3206                } else {
3207                    None
3208                };
3209                let result = match op {
3210                    BinaryOp::Add => self
3211                        .builder
3212                        .build_int_add(left_int, right_int, "add")
3213                        .map_err(|e| CodeGenError::Builder(e.to_string()))?,
3214                    BinaryOp::Subtract => self
3215                        .builder
3216                        .build_int_sub(left_int, right_int, "sub")
3217                        .map_err(|e| CodeGenError::Builder(e.to_string()))?,
3218                    BinaryOp::Multiply => self
3219                        .builder
3220                        .build_int_mul(left_int, right_int, "mul")
3221                        .map_err(|e| CodeGenError::Builder(e.to_string()))?,
3222                    BinaryOp::Divide => {
3223                        if let Some(bit_width) = integer_semantics.unsigned_division_width {
3224                            let (left_int, right_int) = self.normalize_ints_for_unsigned_width(
3225                                left_int,
3226                                right_int,
3227                                bit_width,
3228                                "div",
3229                            )?;
3230                            let result = self
3231                                .builder
3232                                .build_int_unsigned_div(left_int, right_int, "div")
3233                                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3234                            self.zero_extend_int_to_i64_if_needed(result, "div_zext_i64")?
3235                        } else {
3236                            self.build_signed_int_div_via_udiv(left_int, right_int, "div")?
3237                        }
3238                    }
3239                    BinaryOp::Modulo => {
3240                        if let Some(bit_width) = integer_semantics.unsigned_division_width {
3241                            let (left_int, right_int) = self.normalize_ints_for_unsigned_width(
3242                                left_int,
3243                                right_int,
3244                                bit_width,
3245                                "mod",
3246                            )?;
3247                            let result = self
3248                                .builder
3249                                .build_int_unsigned_rem(left_int, right_int, "mod")
3250                                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3251                            self.zero_extend_int_to_i64_if_needed(result, "mod_zext_i64")?
3252                        } else {
3253                            self.build_signed_int_rem_via_urem(left_int, right_int, "mod")?
3254                        }
3255                    }
3256                    BinaryOp::BitAnd => {
3257                        let (left_int, right_int) =
3258                            if let Some(bit_width) = integer_semantics.unsigned_bitwise_width {
3259                                self.normalize_ints_for_unsigned_width(
3260                                    left_int,
3261                                    right_int,
3262                                    bit_width,
3263                                    "bitand",
3264                                )?
3265                            } else {
3266                                (left_int, right_int)
3267                            };
3268                        let result = self
3269                            .builder
3270                            .build_and(left_int, right_int, "bitand")
3271                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3272                        if integer_semantics.unsigned_bitwise_width.is_some() {
3273                            self.zero_extend_int_to_i64_if_needed(result, "bitand_zext_i64")?
3274                        } else {
3275                            result
3276                        }
3277                    }
3278                    BinaryOp::BitXor => {
3279                        let (left_int, right_int) =
3280                            if let Some(bit_width) = integer_semantics.unsigned_bitwise_width {
3281                                self.normalize_ints_for_unsigned_width(
3282                                    left_int,
3283                                    right_int,
3284                                    bit_width,
3285                                    "bitxor",
3286                                )?
3287                            } else {
3288                                (left_int, right_int)
3289                            };
3290                        let result = self
3291                            .builder
3292                            .build_xor(left_int, right_int, "bitxor")
3293                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3294                        if integer_semantics.unsigned_bitwise_width.is_some() {
3295                            self.zero_extend_int_to_i64_if_needed(result, "bitxor_zext_i64")?
3296                        } else {
3297                            result
3298                        }
3299                    }
3300                    BinaryOp::BitOr => {
3301                        let (left_int, right_int) =
3302                            if let Some(bit_width) = integer_semantics.unsigned_bitwise_width {
3303                                self.normalize_ints_for_unsigned_width(
3304                                    left_int,
3305                                    right_int,
3306                                    bit_width,
3307                                    "bitor",
3308                                )?
3309                            } else {
3310                                (left_int, right_int)
3311                            };
3312                        let result = self
3313                            .builder
3314                            .build_or(left_int, right_int, "bitor")
3315                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3316                        if integer_semantics.unsigned_bitwise_width.is_some() {
3317                            self.zero_extend_int_to_i64_if_needed(result, "bitor_zext_i64")?
3318                        } else {
3319                            result
3320                        }
3321                    }
3322                    BinaryOp::ShiftLeft => {
3323                        let right_int = self.mask_shift_amount(right_int, "shl_rhs_mask")?;
3324                        self.builder
3325                            .build_left_shift(left_int, right_int, "shl")
3326                            .map_err(|e| CodeGenError::Builder(e.to_string()))?
3327                    }
3328                    BinaryOp::ShiftRight => {
3329                        let right_int = self.mask_shift_amount(right_int, "shr_rhs_mask")?;
3330                        self.builder
3331                            .build_right_shift(
3332                                left_int,
3333                                right_int,
3334                                integer_semantics.unsigned_right_shift_width.is_none(),
3335                                "shr",
3336                            )
3337                            .map_err(|e| CodeGenError::Builder(e.to_string()))?
3338                    }
3339                    // Comparison operators
3340                    BinaryOp::Equal => {
3341                        let result = self
3342                            .builder
3343                            .build_int_compare(inkwell::IntPredicate::EQ, left_int, right_int, "eq")
3344                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3345                        return Ok(result.into());
3346                    }
3347                    BinaryOp::NotEqual => {
3348                        let result = self
3349                            .builder
3350                            .build_int_compare(inkwell::IntPredicate::NE, left_int, right_int, "ne")
3351                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3352                        return Ok(result.into());
3353                    }
3354                    BinaryOp::LessThan => {
3355                        let predicate = if integer_semantics.unsigned_ordering_width.is_some() {
3356                            inkwell::IntPredicate::ULT
3357                        } else {
3358                            inkwell::IntPredicate::SLT
3359                        };
3360                        let (left_cmp, right_cmp) =
3361                            unsigned_cmp_values.unwrap_or((left_int, right_int));
3362                        let result = self
3363                            .builder
3364                            .build_int_compare(predicate, left_cmp, right_cmp, "lt")
3365                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3366                        return Ok(result.into());
3367                    }
3368                    BinaryOp::LessEqual => {
3369                        let predicate = if integer_semantics.unsigned_ordering_width.is_some() {
3370                            inkwell::IntPredicate::ULE
3371                        } else {
3372                            inkwell::IntPredicate::SLE
3373                        };
3374                        let (left_cmp, right_cmp) =
3375                            unsigned_cmp_values.unwrap_or((left_int, right_int));
3376                        let result = self
3377                            .builder
3378                            .build_int_compare(predicate, left_cmp, right_cmp, "le")
3379                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3380                        return Ok(result.into());
3381                    }
3382                    BinaryOp::GreaterThan => {
3383                        let predicate = if integer_semantics.unsigned_ordering_width.is_some() {
3384                            inkwell::IntPredicate::UGT
3385                        } else {
3386                            inkwell::IntPredicate::SGT
3387                        };
3388                        let (left_cmp, right_cmp) =
3389                            unsigned_cmp_values.unwrap_or((left_int, right_int));
3390                        let result = self
3391                            .builder
3392                            .build_int_compare(predicate, left_cmp, right_cmp, "gt")
3393                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3394                        return Ok(result.into());
3395                    }
3396                    BinaryOp::GreaterEqual => {
3397                        let predicate = if integer_semantics.unsigned_ordering_width.is_some() {
3398                            inkwell::IntPredicate::UGE
3399                        } else {
3400                            inkwell::IntPredicate::SGE
3401                        };
3402                        let (left_cmp, right_cmp) =
3403                            unsigned_cmp_values.unwrap_or((left_int, right_int));
3404                        let result = self
3405                            .builder
3406                            .build_int_compare(predicate, left_cmp, right_cmp, "ge")
3407                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3408                        return Ok(result.into());
3409                    }
3410                    // Logical operators with boolean semantics (non-zero is true)
3411                    BinaryOp::LogicalAnd => {
3412                        let lz = left_int.get_type().const_zero();
3413                        let rz = right_int.get_type().const_zero();
3414                        let lbool = self
3415                            .builder
3416                            .build_int_compare(inkwell::IntPredicate::NE, left_int, lz, "lhs_nz")
3417                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3418                        let rbool = self
3419                            .builder
3420                            .build_int_compare(inkwell::IntPredicate::NE, right_int, rz, "rhs_nz")
3421                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3422                        let result = self
3423                            .builder
3424                            .build_and(lbool, rbool, "and_bool")
3425                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3426                        return Ok(result.into());
3427                    }
3428                    BinaryOp::LogicalOr => {
3429                        let lz = left_int.get_type().const_zero();
3430                        let rz = right_int.get_type().const_zero();
3431                        let lbool = self
3432                            .builder
3433                            .build_int_compare(inkwell::IntPredicate::NE, left_int, lz, "lhs_nz")
3434                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3435                        let rbool = self
3436                            .builder
3437                            .build_int_compare(inkwell::IntPredicate::NE, right_int, rz, "rhs_nz")
3438                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3439                        let result = self
3440                            .builder
3441                            .build_or(lbool, rbool, "or_bool")
3442                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3443                        return Ok(result.into());
3444                    }
3445                };
3446                Ok(result.into())
3447            }
3448            // Pointer equality/inequality comparisons
3449            (PointerValue(lp), IntValue(ri)) | (IntValue(ri), PointerValue(lp)) => {
3450                match op {
3451                    BinaryOp::Equal | BinaryOp::NotEqual => {
3452                        let lpi64 = self
3453                            .builder
3454                            .build_ptr_to_int(lp, self.context.i64_type(), "ptr_as_i64")
3455                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3456                        // Normalize RHS to i64
3457                        let rbw = ri.get_type().get_bit_width();
3458                        let ri64 = if rbw < 64 {
3459                            self.builder
3460                                .build_int_z_extend(ri, self.context.i64_type(), "rhs_zext_i64")
3461                                .map_err(|e| CodeGenError::Builder(e.to_string()))?
3462                        } else if rbw > 64 {
3463                            self.builder
3464                                .build_int_truncate(ri, self.context.i64_type(), "rhs_trunc_i64")
3465                                .map_err(|e| CodeGenError::Builder(e.to_string()))?
3466                        } else {
3467                            ri
3468                        };
3469                        let pred = if matches!(op, BinaryOp::Equal) {
3470                            inkwell::IntPredicate::EQ
3471                        } else {
3472                            inkwell::IntPredicate::NE
3473                        };
3474                        let cmp = self
3475                            .builder
3476                            .build_int_compare(pred, lpi64, ri64, "ptr_cmp")
3477                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3478                        Ok(cmp.into())
3479                    }
3480                    _ => Err(CodeGenError::TypeError(
3481                        "Unsupported operation between aggregate address/pointer and integer: only '==' and '!=' are allowed. If you meant to offset an address, use '&expr +/- <integer literal>' in an alias/address context, or access a scalar field.".to_string(),
3482                    )),
3483                }
3484            }
3485            (PointerValue(lp), PointerValue(rp)) => match op {
3486                BinaryOp::Equal | BinaryOp::NotEqual => {
3487                    let lpi64 = self
3488                        .builder
3489                        .build_ptr_to_int(lp, self.context.i64_type(), "l_ptr_as_i64")
3490                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3491                    let rpi64 = self
3492                        .builder
3493                        .build_ptr_to_int(rp, self.context.i64_type(), "r_ptr_as_i64")
3494                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3495                    let pred = if matches!(op, BinaryOp::Equal) {
3496                        inkwell::IntPredicate::EQ
3497                    } else {
3498                        inkwell::IntPredicate::NE
3499                    };
3500                    let cmp = self
3501                        .builder
3502                        .build_int_compare(pred, lpi64, rpi64, "ptr_ptr_cmp")
3503                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3504                    Ok(cmp.into())
3505                }
3506                _ => Err(CodeGenError::TypeError(
3507                    "Pointer ordered comparison ('<', '<=', '>', '>=') is not supported. Use '==' or '!=' to compare addresses. If you need to adjust an address, use '&expr +/- <integer literal>' in an alias/address context; to compare values, select a scalar field (e.g., 'obj.field')."
3508                        .to_string(),
3509                )),
3510            },
3511            (FloatValue(left_float), FloatValue(right_float)) => match op {
3512                BinaryOp::Add => {
3513                    let result = self
3514                        .builder
3515                        .build_float_add(left_float, right_float, "add")
3516                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3517                    Ok(result.into())
3518                }
3519                BinaryOp::Subtract => {
3520                    let result = self
3521                        .builder
3522                        .build_float_sub(left_float, right_float, "sub")
3523                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3524                    Ok(result.into())
3525                }
3526                BinaryOp::Multiply => {
3527                    let result = self
3528                        .builder
3529                        .build_float_mul(left_float, right_float, "mul")
3530                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3531                    Ok(result.into())
3532                }
3533                BinaryOp::Divide => {
3534                    let result = self
3535                        .builder
3536                        .build_float_div(left_float, right_float, "div")
3537                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3538                    Ok(result.into())
3539                }
3540                // Float comparison operators
3541                BinaryOp::Equal => {
3542                    let result = self
3543                        .builder
3544                        .build_float_compare(
3545                            inkwell::FloatPredicate::OEQ,
3546                            left_float,
3547                            right_float,
3548                            "eq",
3549                        )
3550                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3551                    Ok(result.into())
3552                }
3553                BinaryOp::NotEqual => {
3554                    let result = self
3555                        .builder
3556                        .build_float_compare(
3557                            inkwell::FloatPredicate::ONE,
3558                            left_float,
3559                            right_float,
3560                            "ne",
3561                        )
3562                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3563                    Ok(result.into())
3564                }
3565                BinaryOp::LessThan => {
3566                    let result = self
3567                        .builder
3568                        .build_float_compare(
3569                            inkwell::FloatPredicate::OLT,
3570                            left_float,
3571                            right_float,
3572                            "lt",
3573                        )
3574                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3575                    Ok(result.into())
3576                }
3577                BinaryOp::LessEqual => {
3578                    let result = self
3579                        .builder
3580                        .build_float_compare(
3581                            inkwell::FloatPredicate::OLE,
3582                            left_float,
3583                            right_float,
3584                            "le",
3585                        )
3586                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3587                    Ok(result.into())
3588                }
3589                BinaryOp::GreaterThan => {
3590                    let result = self
3591                        .builder
3592                        .build_float_compare(
3593                            inkwell::FloatPredicate::OGT,
3594                            left_float,
3595                            right_float,
3596                            "gt",
3597                        )
3598                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3599                    Ok(result.into())
3600                }
3601                BinaryOp::GreaterEqual => {
3602                    let result = self
3603                        .builder
3604                        .build_float_compare(
3605                            inkwell::FloatPredicate::OGE,
3606                            left_float,
3607                            right_float,
3608                            "ge",
3609                        )
3610                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
3611                    Ok(result.into())
3612                }
3613                _ => Err(CodeGenError::NotImplemented(format!(
3614                    "Float binary operation {op:?} not implemented"
3615                ))),
3616            },
3617            _ => Err(CodeGenError::TypeError(format!(
3618                "Type mismatch in binary operation {op:?}"
3619            ))),
3620        }
3621    }
3622
3623    /// Compile member access (struct.field)
3624    pub fn compile_member_access(
3625        &mut self,
3626        obj_expr: &Expr,
3627        field: &str,
3628    ) -> Result<BasicValueEnum<'ctx>> {
3629        // Create a MemberAccess expression and use the unified DWARF compilation
3630        let member_access_expr = Expr::MemberAccess(Box::new(obj_expr.clone()), field.to_string());
3631        self.compile_dwarf_expression(&member_access_expr)
3632    }
3633
3634    /// Compile pointer dereference (*ptr)
3635    pub fn compile_pointer_deref(&mut self, expr: &Expr) -> Result<BasicValueEnum<'ctx>> {
3636        // Create a PointerDeref expression and use the unified DWARF compilation
3637        let pointer_deref_expr = Expr::PointerDeref(Box::new(expr.clone()));
3638        self.compile_dwarf_expression(&pointer_deref_expr)
3639    }
3640
3641    /// Compile array access (arr[index])
3642    pub fn compile_array_access(
3643        &mut self,
3644        array_expr: &Expr,
3645        index_expr: &Expr,
3646    ) -> Result<BasicValueEnum<'ctx>> {
3647        if let Some((value, _element_type)) =
3648            self.compile_dynamic_array_access_value(array_expr, index_expr)?
3649        {
3650            return Ok(value);
3651        }
3652
3653        // Create an ArrayAccess expression and use the unified DWARF compilation
3654        let array_access_expr =
3655            Expr::ArrayAccess(Box::new(array_expr.clone()), Box::new(index_expr.clone()));
3656        self.compile_dwarf_expression(&array_access_expr)
3657    }
3658
3659    /// Compile chain access (person.name.first)
3660    pub fn compile_chain_access(&mut self, chain: &[String]) -> Result<BasicValueEnum<'ctx>> {
3661        // Create a ChainAccess expression and use the unified DWARF compilation
3662        let chain_access_expr = Expr::ChainAccess(chain.to_vec());
3663        self.compile_dwarf_expression(&chain_access_expr)
3664    }
3665
3666    /// Unified DWARF expression compilation
3667    pub fn compile_dwarf_expression(
3668        &mut self,
3669        expr: &crate::script::Expr,
3670    ) -> Result<BasicValueEnum<'ctx>> {
3671        debug!(
3672            "compile_dwarf_expression: Compiling complex expression: {:?}",
3673            expr
3674        );
3675
3676        if let crate::script::Expr::Cast {
3677            expr: inner,
3678            target_type,
3679        } = expr
3680        {
3681            return self.compile_cast_expr_value(inner, target_type);
3682        }
3683
3684        if let crate::script::Expr::ArrayAccess(array_expr, index_expr) = expr {
3685            if let Some((value, _element_type)) =
3686                self.compile_dynamic_array_access_value(array_expr, index_expr)?
3687            {
3688                return Ok(value);
3689            }
3690        }
3691        if let crate::script::Expr::MemberAccess(obj_expr, field) = expr {
3692            if let Some((value, _member_type)) =
3693                self.compile_dynamic_member_access_value(obj_expr, field)?
3694            {
3695                return Ok(value);
3696            }
3697        }
3698        if matches!(expr, crate::script::Expr::PointerDeref(_)) {
3699            if let Some(lvalue) = self.dynamic_lvalue_address_and_type(expr)? {
3700                return self
3701                    .read_dynamic_address_value(lvalue.address, &lvalue.type_info.dwarf_type);
3702            }
3703        }
3704
3705        // Query DWARF for the complex expression
3706        let compile_context = self.get_compile_time_context()?.clone();
3707        let variable_plan = match self.query_dwarf_for_complex_expr(expr)? {
3708            Some(var) => var,
3709            None => {
3710                let expr_str = Self::expr_to_debug_string(expr);
3711                return Err(CodeGenError::VariableNotFound(expr_str));
3712            }
3713        };
3714
3715        let materialized =
3716            self.variable_read_plan_to_materialization(variable_plan, compile_context.pc_address)?;
3717        let dwarf_type = materialized.dwarf_type.as_ref().ok_or_else(|| {
3718            CodeGenError::DwarfError("Expression has no DWARF type information".to_string())
3719        })?;
3720
3721        debug!(
3722            "compile_dwarf_expression: Found DWARF info for expression '{}' with type: {:?}",
3723            materialized.name, dwarf_type
3724        );
3725
3726        self.variable_materialization_to_llvm_value(&materialized, compile_context.pc_address, None)
3727    }
3728
3729    pub(super) fn compile_dynamic_array_access_value(
3730        &mut self,
3731        array_expr: &Expr,
3732        index_expr: &Expr,
3733    ) -> Result<Option<(BasicValueEnum<'ctx>, DwarfType)>> {
3734        let Some(element_lvalue) =
3735            self.compile_dynamic_array_element_address(array_expr, index_expr)?
3736        else {
3737            return Ok(None);
3738        };
3739
3740        let value = self.read_dynamic_address_value(
3741            element_lvalue.address,
3742            &element_lvalue.type_info.dwarf_type,
3743        )?;
3744        Ok(Some((value, element_lvalue.type_info.dwarf_type)))
3745    }
3746
3747    pub(super) fn compile_dynamic_member_access_value(
3748        &mut self,
3749        obj_expr: &Expr,
3750        field: &str,
3751    ) -> Result<Option<(BasicValueEnum<'ctx>, DwarfType)>> {
3752        let Some(object_lvalue) = self.dynamic_lvalue_address_and_type(obj_expr)? else {
3753            return Ok(None);
3754        };
3755
3756        let Some(element_lvalue) = self.dynamic_member_base_address_and_type(object_lvalue)? else {
3757            return Ok(None);
3758        };
3759        let (member_offset, member_type) =
3760            self.dynamic_member_offset_and_type(&element_lvalue.type_info, field)?;
3761
3762        let member_offset = self.context.i64_type().const_int(member_offset, false);
3763        let member_address = self
3764            .builder
3765            .build_int_add(
3766                element_lvalue.address.value,
3767                member_offset,
3768                "dynamic_member_address",
3769            )
3770            .map_err(|err| CodeGenError::Builder(err.to_string()))?;
3771        let value = self.read_dynamic_address_value(
3772            element_lvalue.address.with_value(member_address),
3773            &member_type,
3774        )?;
3775        Ok(Some((value, member_type)))
3776    }
3777
3778    pub(super) fn dynamic_lvalue_address_and_type(
3779        &mut self,
3780        expr: &Expr,
3781    ) -> Result<Option<DynamicLvalue<'ctx>>> {
3782        if let Expr::Variable(name) = expr {
3783            if self.alias_variable_exists(name) {
3784                let expanded = self.expand_alias_variable_expr(expr)?;
3785                return self.dynamic_lvalue_address_and_type(&expanded);
3786            }
3787        }
3788
3789        if let Expr::Cast {
3790            expr: inner,
3791            target_type,
3792        } = expr
3793        {
3794            return self
3795                .cast_lvalue_address_and_type(inner, target_type)
3796                .map(Some);
3797        }
3798
3799        if let Expr::PointerDeref(inner) = expr {
3800            let expanded_inner = self.expand_alias_variable_expr(inner)?;
3801            if matches!(expanded_inner, Expr::Cast { .. }) {
3802                return self.dynamic_lvalue_address_and_type(&expanded_inner);
3803            }
3804            if let Expr::BinaryOp { .. } = expanded_inner {
3805                if let Some(lvalue) = self.dynamic_lvalue_address_and_type(&expanded_inner)? {
3806                    return Ok(Some(lvalue));
3807                }
3808            }
3809        }
3810
3811        if let Expr::ArrayAccess(array_expr, index_expr) = expr {
3812            return self.compile_dynamic_array_element_address(array_expr, index_expr);
3813        }
3814
3815        if let Some(lvalue) = self.dynamic_lvalue_from_const_pointer_arithmetic(expr)? {
3816            return Ok(Some(lvalue));
3817        }
3818
3819        if self.expands_to_nonliteral_pointer_arithmetic(expr)? {
3820            let Some(element_info) = self.indexable_element_type_and_stride(expr)? else {
3821                return Ok(None);
3822            };
3823            let element_address = self.resolve_runtime_address_from_expr(expr)?;
3824            return Ok(Some(DynamicLvalue {
3825                address: element_address,
3826                type_info: DynamicTypeInfo {
3827                    dwarf_type: element_info.element_type,
3828                    module_path: element_info.module_path,
3829                },
3830            }));
3831        }
3832
3833        if let Expr::MemberAccess(obj_expr, field) = expr {
3834            let Some(object_lvalue) = self.dynamic_lvalue_address_and_type(obj_expr)? else {
3835                return Ok(None);
3836            };
3837            let Some(base_lvalue) = self.dynamic_member_base_address_and_type(object_lvalue)?
3838            else {
3839                return Ok(None);
3840            };
3841            let (member_offset, member_type) =
3842                self.dynamic_member_offset_and_type(&base_lvalue.type_info, field)?;
3843            let member_offset = self.context.i64_type().const_int(member_offset, false);
3844            let member_address = self
3845                .builder
3846                .build_int_add(
3847                    base_lvalue.address.value,
3848                    member_offset,
3849                    "dynamic_member_lvalue_address",
3850                )
3851                .map_err(|err| CodeGenError::Builder(err.to_string()))?;
3852            return Ok(Some(DynamicLvalue {
3853                address: base_lvalue.address.with_value(member_address),
3854                type_info: DynamicTypeInfo {
3855                    dwarf_type: member_type,
3856                    module_path: base_lvalue.type_info.module_path,
3857                },
3858            }));
3859        }
3860
3861        Ok(None)
3862    }
3863
3864    fn dynamic_member_base_address_and_type(
3865        &mut self,
3866        object: DynamicLvalue<'ctx>,
3867    ) -> Result<Option<DynamicLvalue<'ctx>>> {
3868        let module_path = object.type_info.module_path.clone();
3869        let object_type = self.complete_dynamic_member_element_type(
3870            object.type_info.dwarf_type,
3871            module_path.as_deref(),
3872        );
3873        match ghostscope_dwarf::strip_type_aliases(&object_type) {
3874            DwarfType::StructType { .. } | DwarfType::UnionType { .. } => Ok(Some(DynamicLvalue {
3875                address: object.address,
3876                type_info: DynamicTypeInfo {
3877                    dwarf_type: object_type,
3878                    module_path,
3879                },
3880            })),
3881            DwarfType::PointerType { target_type, .. } => {
3882                let pointer_value =
3883                    self.read_dynamic_address_value(object.address, &object_type)?;
3884                let pointer_value = match pointer_value {
3885                    BasicValueEnum::IntValue(value) => {
3886                        self.normalize_int_to_i64(value, "dynamic_member_pointer_i64")?
3887                    }
3888                    BasicValueEnum::PointerValue(value) => self
3889                        .builder
3890                        .build_ptr_to_int(
3891                            value,
3892                            self.context.i64_type(),
3893                            "dynamic_member_pointer_ptr",
3894                        )
3895                        .map_err(|err| CodeGenError::Builder(err.to_string()))?,
3896                    _ => {
3897                        return Err(CodeGenError::TypeError(
3898                            "dynamic member pointer base did not compile to an address".to_string(),
3899                        ))
3900                    }
3901                };
3902                let target_type = self.complete_dynamic_member_element_type(
3903                    target_type.as_ref().clone(),
3904                    module_path.as_deref(),
3905                );
3906                Ok(Some(DynamicLvalue {
3907                    address: RuntimeAddress::available(pointer_value, self.context),
3908                    type_info: DynamicTypeInfo {
3909                        dwarf_type: target_type,
3910                        module_path,
3911                    },
3912                }))
3913            }
3914            _ => Ok(None),
3915        }
3916    }
3917
3918    fn dynamic_member_offset_and_type(
3919        &self,
3920        aggregate: &DynamicTypeInfo,
3921        field: &str,
3922    ) -> Result<(u64, DwarfType)> {
3923        let aggregate_type = self.complete_dynamic_member_element_type(
3924            aggregate.dwarf_type.clone(),
3925            aggregate.module_path.as_deref(),
3926        );
3927        match ghostscope_dwarf::member_layout(&aggregate_type, field) {
3928            Ok(layout) => Ok((layout.offset, layout.member_type)),
3929            Err(err @ TypeLayoutError::UnknownMember { .. }) => {
3930                Err(CodeGenError::DwarfError(err.to_string()))
3931            }
3932            Err(err @ TypeLayoutError::InvalidMemberBase { .. }) => {
3933                Err(CodeGenError::TypeError(err.to_string()))
3934            }
3935        }
3936    }
3937
3938    fn dynamic_array_base_from_plan(
3939        &mut self,
3940        array_plan: &VariableReadPlan,
3941        pc_address: u64,
3942        status_ptr: Option<PointerValue<'ctx>>,
3943        static_index: i64,
3944    ) -> Result<(IndexableElementInfo, RuntimeAddress<'ctx>, i64)> {
3945        let module_path = array_plan.module_path.clone();
3946        let array_type = array_plan.dwarf_type.as_ref().ok_or_else(|| {
3947            CodeGenError::DwarfError("Array expression has no DWARF type information".to_string())
3948        })?;
3949        let element_info =
3950            Self::indexable_info_from_type(array_type, module_path).ok_or_else(|| {
3951                CodeGenError::TypeError(format!(
3952                    "dynamic array index requires array or pointer type, got '{}'",
3953                    array_type.type_name()
3954                ))
3955            })?;
3956
3957        match ghostscope_dwarf::strip_type_aliases(array_type) {
3958            DwarfType::ArrayType { .. } => {
3959                let base_address =
3960                    self.variable_read_plan_to_runtime_address(array_plan, pc_address, status_ptr)?;
3961                Ok((element_info, base_address, static_index))
3962            }
3963            DwarfType::PointerType { .. } => {
3964                let pointer_value =
3965                    self.variable_read_plan_to_llvm_value(array_plan, pc_address, status_ptr)?;
3966                let base_address = self.compiled_pointer_value_to_runtime_address(
3967                    pointer_value,
3968                    "dynamic_array_base_i64",
3969                    "dynamic_array_base_ptr",
3970                    "array base pointer did not compile to an address",
3971                )?;
3972                Ok((element_info, base_address, static_index))
3973            }
3974            _ => unreachable!("indexable_info_from_type accepts only array or pointer types"),
3975        }
3976    }
3977
3978    fn compile_dynamic_array_element_address(
3979        &mut self,
3980        array_expr: &Expr,
3981        index_expr: &Expr,
3982    ) -> Result<Option<DynamicLvalue<'ctx>>> {
3983        let literal_index = Self::integer_literal_value(index_expr);
3984        let expanded_array_expr = self.expand_alias_variable_expr(array_expr)?;
3985        let has_dynamic_base =
3986            self.expands_to_nonliteral_pointer_arithmetic(&expanded_array_expr)?;
3987        let cast_base = self.cast_index_base(&expanded_array_expr)?;
3988
3989        if literal_index.is_some() && !has_dynamic_base && cast_base.is_none() {
3990            return Ok(None);
3991        }
3992
3993        let compile_context = self.get_compile_time_context()?.clone();
3994        let status_ptr = if self.condition_context_active {
3995            Some(self.get_or_create_cond_error_global())
3996        } else {
3997            None
3998        };
3999
4000        let (element_info, base_address, static_index) = if let Some((element_info, base_address)) =
4001            cast_base
4002        {
4003            (element_info, base_address, 0)
4004        } else {
4005            match self.query_dwarf_for_complex_expr(array_expr)? {
4006                Some(array_plan) => self.dynamic_array_base_from_plan(
4007                    &array_plan,
4008                    compile_context.pc_address,
4009                    status_ptr,
4010                    0,
4011                )?,
4012                None => {
4013                    if let Some((base_expr, static_index)) =
4014                        self.pointer_arithmetic_parts_expanding_aliases(&expanded_array_expr)?
4015                    {
4016                        let array_plan = self
4017                            .query_dwarf_for_complex_expr(&base_expr)?
4018                            .ok_or_else(|| {
4019                                CodeGenError::VariableNotFound(Self::expr_to_debug_string(
4020                                    &base_expr,
4021                                ))
4022                            })?;
4023                        self.dynamic_array_base_from_plan(
4024                            &array_plan,
4025                            compile_context.pc_address,
4026                            status_ptr,
4027                            static_index,
4028                        )?
4029                    } else if has_dynamic_base {
4030                        let element_info = self
4031                            .indexable_element_type_and_stride(&expanded_array_expr)?
4032                            .ok_or_else(|| {
4033                                CodeGenError::VariableNotFound(Self::expr_to_debug_string(
4034                                    array_expr,
4035                                ))
4036                            })?;
4037                        let base_address =
4038                            self.resolve_runtime_address_from_expr(&expanded_array_expr)?;
4039                        (element_info, base_address, 0)
4040                    } else if let Some(array_lvalue) =
4041                        self.dynamic_lvalue_address_and_type(&expanded_array_expr)?
4042                    {
4043                        let module_path = array_lvalue.type_info.module_path.clone();
4044                        let element_info = Self::indexable_info_from_type(
4045                            &array_lvalue.type_info.dwarf_type,
4046                            module_path,
4047                        )
4048                        .ok_or_else(|| {
4049                            CodeGenError::TypeError(format!(
4050                                "dynamic array index requires array or pointer type, got '{}'",
4051                                array_lvalue.type_info.dwarf_type.type_name()
4052                            ))
4053                        })?;
4054                        match ghostscope_dwarf::strip_type_aliases(
4055                            &array_lvalue.type_info.dwarf_type,
4056                        ) {
4057                            DwarfType::ArrayType { .. } => (element_info, array_lvalue.address, 0),
4058                            DwarfType::PointerType { .. } => {
4059                                let pointer_value = self.read_dynamic_address_value(
4060                                    array_lvalue.address,
4061                                    &array_lvalue.type_info.dwarf_type,
4062                                )?;
4063                                let base_address = self.compiled_pointer_value_to_runtime_address(
4064                                    pointer_value,
4065                                    "dynamic_array_member_ptr_i64",
4066                                    "dynamic_array_member_ptr",
4067                                    "array member pointer did not compile to an address",
4068                                )?;
4069                                (element_info, base_address, 0)
4070                            }
4071                            _ => unreachable!(
4072                                "indexable_info_from_type accepts only array or pointer types"
4073                            ),
4074                        }
4075                    } else {
4076                        return Err(CodeGenError::VariableNotFound(Self::expr_to_debug_string(
4077                            array_expr,
4078                        )));
4079                    }
4080                }
4081            }
4082        };
4083
4084        let index_value = if let Some(index) = literal_index {
4085            self.context.i64_type().const_int(index as u64, true)
4086        } else {
4087            match self.compile_expr(index_expr)? {
4088                BasicValueEnum::IntValue(value) => {
4089                    self.normalize_int_to_i64(value, "dynamic_array_index_i64")?
4090                }
4091                _ => {
4092                    return Err(CodeGenError::TypeError(
4093                        "array index expression must compile to an integer".to_string(),
4094                    ))
4095                }
4096            }
4097        };
4098        let index_value = if static_index == 0 {
4099            index_value
4100        } else {
4101            let static_index_value = self.context.i64_type().const_int(static_index as u64, true);
4102            self.builder
4103                .build_int_add(
4104                    index_value,
4105                    static_index_value,
4106                    "dynamic_array_static_index",
4107                )
4108                .map_err(|err| CodeGenError::Builder(err.to_string()))?
4109        };
4110        let stride_value = self
4111            .context
4112            .i64_type()
4113            .const_int(element_info.stride, false);
4114        let byte_offset = self
4115            .builder
4116            .build_int_mul(index_value, stride_value, "dynamic_array_byte_offset")
4117            .map_err(|err| CodeGenError::Builder(err.to_string()))?;
4118        let element_address = self
4119            .builder
4120            .build_int_add(
4121                base_address.value,
4122                byte_offset,
4123                "dynamic_array_element_address",
4124            )
4125            .map_err(|err| CodeGenError::Builder(err.to_string()))?;
4126
4127        Ok(Some(DynamicLvalue {
4128            address: base_address.with_value(element_address),
4129            type_info: DynamicTypeInfo {
4130                dwarf_type: element_info.element_type,
4131                module_path: element_info.module_path,
4132            },
4133        }))
4134    }
4135
4136    fn indexable_element_type_and_stride(
4137        &mut self,
4138        expr: &Expr,
4139    ) -> Result<Option<IndexableElementInfo>> {
4140        use crate::script::ast::BinaryOp as BO;
4141        use crate::script::ast::Expr as E;
4142
4143        let expanded = self.expand_alias_variable_expr(expr)?;
4144
4145        if let Some((element_info, _base_address)) = self.cast_index_base(&expanded)? {
4146            return Ok(Some(element_info));
4147        }
4148
4149        if let Some(plan) = self.query_dwarf_for_complex_expr(&expanded)? {
4150            if let Some(dwarf_type) = plan.dwarf_type.as_ref() {
4151                if let Some(info) =
4152                    Self::indexable_info_from_type(dwarf_type, plan.module_path.clone())
4153                {
4154                    return Ok(Some(info));
4155                }
4156            }
4157        }
4158
4159        if let Some((base_expr, _static_index)) =
4160            self.pointer_arithmetic_parts_expanding_aliases(&expanded)?
4161        {
4162            if let Some(plan) = self.query_dwarf_for_complex_expr(&base_expr)? {
4163                if let Some(dwarf_type) = plan.dwarf_type.as_ref() {
4164                    if let Some(info) =
4165                        Self::indexable_info_from_type(dwarf_type, plan.module_path.clone())
4166                    {
4167                        return Ok(Some(info));
4168                    }
4169                }
4170            }
4171        }
4172
4173        match expanded {
4174            E::BinaryOp {
4175                ref left,
4176                op: BO::Add,
4177                ref right,
4178            } => {
4179                if let Some(info) = self.indexable_element_type_and_stride(left)? {
4180                    return Ok(Some(info));
4181                }
4182                self.indexable_element_type_and_stride(right)
4183            }
4184            E::BinaryOp {
4185                ref left,
4186                op: BO::Subtract,
4187                ..
4188            } => self.indexable_element_type_and_stride(left),
4189            _ => Ok(None),
4190        }
4191    }
4192
4193    fn complete_dynamic_member_element_type(
4194        &self,
4195        element_type: DwarfType,
4196        module_path: Option<&Path>,
4197    ) -> DwarfType {
4198        let Some(analyzer) = self.process_analyzer else {
4199            return element_type;
4200        };
4201        let fallback_module_path = self
4202            .current_compile_time_context
4203            .as_ref()
4204            .map(|ctx| PathBuf::from(&ctx.module_path));
4205        let lookup_module_path = module_path.or(fallback_module_path.as_deref());
4206
4207        if let Some(module_path) = lookup_module_path {
4208            analyzer.complete_shallow_unknown_aggregate_type_in_module(module_path, element_type)
4209        } else {
4210            analyzer.complete_shallow_unknown_aggregate_type(element_type)
4211        }
4212    }
4213
4214    fn read_dynamic_address_value(
4215        &mut self,
4216        address: RuntimeAddress<'ctx>,
4217        dwarf_type: &DwarfType,
4218    ) -> Result<BasicValueEnum<'ctx>> {
4219        if ghostscope_dwarf::is_c_aggregate_type(dwarf_type) {
4220            let ptr_ty = self.context.ptr_type(AddressSpace::default());
4221            let as_ptr = self
4222                .builder
4223                .build_int_to_ptr(address.value, ptr_ty, "dynamic_aggregate_ptr")
4224                .map_err(|err| CodeGenError::Builder(err.to_string()))?;
4225            return Ok(as_ptr.into());
4226        }
4227
4228        let access_size = self.dwarf_type_to_memory_access_size(dwarf_type);
4229        let value = if self.condition_context_active {
4230            self.generate_memory_read_with_status(address, access_size)?
4231        } else {
4232            self.generate_memory_read(address, access_size, None)?
4233        };
4234        self.sign_extend_memory_read_if_needed(value, dwarf_type, access_size)
4235    }
4236
4237    fn expand_alias_variable_expr(&self, expr: &Expr) -> Result<Expr> {
4238        let mut expanded = expr.clone();
4239        let mut visited = std::collections::HashSet::new();
4240
4241        loop {
4242            let Expr::Variable(name) = &expanded else {
4243                return Ok(expanded);
4244            };
4245            if !self.alias_variable_exists(name) {
4246                return Ok(expanded);
4247            }
4248            if !visited.insert(name.clone()) {
4249                return Err(CodeGenError::TypeError(format!(
4250                    "alias cycle detected for '{name}'"
4251                )));
4252            }
4253            let Some(target) = self.get_alias_variable(name) else {
4254                return Ok(expanded);
4255            };
4256            expanded = target;
4257        }
4258    }
4259
4260    fn normalize_int_to_i64(&self, value: IntValue<'ctx>, name: &str) -> Result<IntValue<'ctx>> {
4261        let width = value.get_type().get_bit_width();
4262        if width == 64 {
4263            return Ok(value);
4264        }
4265
4266        if width < 64 {
4267            return self
4268                .builder
4269                .build_int_s_extend(value, self.context.i64_type(), name)
4270                .map_err(|err| CodeGenError::Builder(err.to_string()));
4271        }
4272
4273        self.builder
4274            .build_int_truncate(value, self.context.i64_type(), name)
4275            .map_err(|err| CodeGenError::Builder(err.to_string()))
4276    }
4277
4278    pub(crate) fn dwarf_expression_unavailable_error(
4279        name: &str,
4280        availability: &Availability,
4281        pc_address: u64,
4282    ) -> CodeGenError {
4283        let reason = Self::format_availability_reason(availability);
4284        CodeGenError::VariableUnavailable(format!(
4285            "'{name}' is {reason}; cannot use it as a value expression at PC 0x{pc_address:x}"
4286        ))
4287    }
4288
4289    pub(crate) fn dwarf_lvalue_address_unavailable_error(
4290        name: &str,
4291        availability: &Availability,
4292        pc_address: u64,
4293    ) -> CodeGenError {
4294        let reason = Self::format_availability_reason(availability);
4295        CodeGenError::VariableUnavailable(format!(
4296            "'{name}' is {reason}; cannot take its address at PC 0x{pc_address:x}"
4297        ))
4298    }
4299
4300    fn format_availability_reason(availability: &Availability) -> String {
4301        match availability {
4302            Availability::OptimizedOut => "optimized out at the selected probe PC".to_string(),
4303            Availability::NotInScope => "not in scope at the selected probe PC".to_string(),
4304            Availability::Unsupported(reason) => {
4305                format!(
4306                    "unsupported DWARF semantic shape: {}",
4307                    Self::format_unsupported_reason(reason)
4308                )
4309            }
4310            Availability::Requires(requirement) => {
4311                format!(
4312                    "requires unavailable runtime support: {}",
4313                    Self::format_runtime_requirement(requirement)
4314                )
4315            }
4316            Availability::Ambiguous(reason) => {
4317                format!(
4318                    "ambiguous DWARF semantic result: {}",
4319                    Self::format_ambiguity_reason(reason)
4320                )
4321            }
4322            Availability::Available | Availability::PartiallyAvailable => "available".to_string(),
4323        }
4324    }
4325
4326    fn format_unsupported_reason(reason: &UnsupportedReason) -> String {
4327        match reason {
4328            UnsupportedReason::DwarfOp { op } => format!("unsupported DWARF op {op}"),
4329            UnsupportedReason::ExpressionShape { detail } => {
4330                format!("unsupported DWARF expression shape: {detail}")
4331            }
4332            UnsupportedReason::TypeLayout { detail } => {
4333                format!("unsupported type layout: {detail}")
4334            }
4335            UnsupportedReason::AddressClass { detail } => {
4336                format!("unsupported address class: {detail}")
4337            }
4338            UnsupportedReason::RegisterMapping { dwarf_reg } => {
4339                format!("unsupported DWARF register mapping for register {dwarf_reg}")
4340            }
4341        }
4342    }
4343
4344    fn format_runtime_requirement(requirement: &RuntimeRequirement) -> &'static str {
4345        match requirement {
4346            RuntimeRequirement::CallerFrame => "caller-frame recovery",
4347            RuntimeRequirement::SleepableUprobe => "sleepable uprobe support",
4348            RuntimeRequirement::UserMemoryRead => "user-memory read support",
4349            RuntimeRequirement::DwarfCfiRecovery => "DWARF CFI recovery",
4350        }
4351    }
4352
4353    fn format_ambiguity_reason(reason: &AmbiguityReason) -> String {
4354        match reason {
4355            AmbiguityReason::InlineContext { detail } => {
4356                format!("ambiguous inline context: {detail}")
4357            }
4358            AmbiguityReason::VariableDeclaration { detail } => {
4359                format!("ambiguous variable declaration: {detail}")
4360            }
4361            AmbiguityReason::TypeResolution { detail } => {
4362                format!("ambiguous type resolution: {detail}")
4363            }
4364        }
4365    }
4366
4367    /// Helper: Convert expression to string for debugging
4368    fn expr_to_debug_string(expr: &crate::script::Expr) -> String {
4369        use crate::script::Expr;
4370
4371        match expr {
4372            Expr::Variable(name) => name.clone(),
4373            Expr::MemberAccess(obj, field) => {
4374                format!("{}.{}", Self::expr_to_debug_string(obj), field)
4375            }
4376            Expr::ArrayAccess(arr, _) => format!("{}[index]", Self::expr_to_debug_string(arr)),
4377            Expr::Cast { expr, target_type } => format!(
4378                "cast({}, \"{}\")",
4379                Self::expr_to_debug_string(expr),
4380                target_type
4381            ),
4382            Expr::ChainAccess(chain) => chain.join("."),
4383            Expr::PointerDeref(expr) => format!("*{}", Self::expr_to_debug_string(expr)),
4384            _ => "expr".to_string(),
4385        }
4386    }
4387}
4388
4389impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> {
4390    /// Compile comparison between a DWARF-side expression and a script string literal.
4391    /// Supports char* and char[N] according to design in string_comparison.md.
4392    fn compile_string_comparison(
4393        &mut self,
4394        dwarf_expr: &Expr,
4395        lit: &str,
4396        is_equal: bool,
4397    ) -> Result<BasicValueEnum<'ctx>> {
4398        use ghostscope_dwarf::TypeInfo as TI;
4399
4400        // Query DWARF for the non-string side to obtain evaluation and type info
4401        let var = self
4402            .query_dwarf_for_complex_expr(dwarf_expr)?
4403            .ok_or_else(|| {
4404                CodeGenError::TypeError(
4405                    "string comparison requires DWARF variable/expression".into(),
4406                )
4407            })?;
4408        // Try DWARF type first; if unavailable, fall back to type_name string parsing
4409        let dwarf_type_opt = var.dwarf_type.as_ref();
4410
4411        enum ParsedKind {
4412            PtrChar,
4413            ArrChar(Option<u32>),
4414            Other,
4415        }
4416        fn parse_type_name(name: &str) -> ParsedKind {
4417            let lower = name.to_lowercase();
4418            let has_char = lower.contains("char");
4419            let is_ptr = lower.contains('*');
4420            if has_char && is_ptr {
4421                return ParsedKind::PtrChar;
4422            }
4423            if has_char && lower.contains('[') {
4424                // Try to extract N inside brackets
4425                let mut n: Option<u32> = None;
4426                if let Some(start) = lower.find('[') {
4427                    if let Some(end) = lower[start + 1..].find(']') {
4428                        let inside = &lower[start + 1..start + 1 + end];
4429                        let digits: String =
4430                            inside.chars().filter(|c| c.is_ascii_digit()).collect();
4431                        if !digits.is_empty() {
4432                            if let Ok(v) = digits.parse::<u32>() {
4433                                n = Some(v);
4434                            }
4435                        }
4436                    }
4437                }
4438                return ParsedKind::ArrChar(n);
4439            }
4440            ParsedKind::Other
4441        }
4442
4443        let lit_bytes = lit.as_bytes();
4444        let lit_len = lit_bytes.len() as u32;
4445        let one = self.context.bool_type().const_int(1, false);
4446        let zero = self.context.bool_type().const_zero();
4447
4448        // Build final boolean accumulator
4449        let result = match dwarf_type_opt.map(ghostscope_dwarf::strip_type_aliases) {
4450            // char* / const char*
4451            Some(TI::PointerType { target_type, .. }) => {
4452                // Ensure pointee is char-like
4453                let base = ghostscope_dwarf::strip_type_aliases(target_type.as_ref());
4454                let is_char_like = matches!(base, TI::BaseType { name, size, .. } if name.contains("char") && *size == 1);
4455                if !is_char_like {
4456                    return Err(CodeGenError::TypeError(
4457                        "automatic string comparison only supports char*".into(),
4458                    ));
4459                }
4460
4461                // Evaluate expression to pointer value and read up to L+1 bytes
4462                let pc_address = self.get_compile_time_context()?.pc_address;
4463                let val_any = self.variable_read_plan_to_llvm_value(&var, pc_address, None)?;
4464                let ptr_i64 = match val_any {
4465                    BasicValueEnum::IntValue(iv) => iv,
4466                    BasicValueEnum::PointerValue(pv) => self
4467                        .builder
4468                        .build_ptr_to_int(pv, self.context.i64_type(), "ptr_as_i64")
4469                        .map_err(|e| CodeGenError::Builder(e.to_string()))?,
4470                    _ => {
4471                        return Err(CodeGenError::TypeError(
4472                            "pointer value must be integer or pointer".into(),
4473                        ))
4474                    }
4475                };
4476                let need = lit_len + 1;
4477                let (buf_global, ret_len, arr_ty) = self.read_user_cstr_into_buffer(
4478                    RuntimeAddress::available(ptr_i64, self.context),
4479                    need,
4480                    "_gs_strbuf",
4481                )?;
4482
4483                // ret_len must equal L+1
4484                let i64_ty = self.context.i64_type();
4485                let expect_len = i64_ty.const_int(need as u64, false);
4486                let len_ok = self
4487                    .builder
4488                    .build_int_compare(inkwell::IntPredicate::EQ, ret_len, expect_len, "str_len_ok")
4489                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4490
4491                // buf[L] must be '\0'
4492                let i32_ty = self.context.i32_type();
4493                let idx0 = i32_ty.const_zero();
4494                let idx_l = i32_ty.const_int(lit_len as u64, false);
4495                // SAFETY: the string read requested lit_len + 1 bytes, so index
4496                // lit_len is within the scratch buffer.
4497                let char_ptr = unsafe {
4498                    self.builder
4499                        .build_gep(arr_ty, buf_global, &[idx0, idx_l], "nul_ptr")
4500                        .map_err(|e| CodeGenError::Builder(e.to_string()))?
4501                };
4502                let c = self
4503                    .builder
4504                    .build_load(self.context.i8_type(), char_ptr, "c_l")
4505                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4506                let c = match c {
4507                    BasicValueEnum::IntValue(iv) => iv,
4508                    _ => return Err(CodeGenError::LLVMError("load did not return i8".into())),
4509                };
4510                let nul_ok = self
4511                    .builder
4512                    .build_int_compare(
4513                        inkwell::IntPredicate::EQ,
4514                        c,
4515                        self.context.i8_type().const_zero(),
4516                        "nul_ok",
4517                    )
4518                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4519
4520                // Compare first L bytes using XOR/OR accumulation to reduce branchiness
4521                let mut acc = self.context.i8_type().const_zero();
4522                for (i, b) in lit_bytes.iter().enumerate() {
4523                    let idx_i = i32_ty.const_int(i as u64, false);
4524                    // SAFETY: lit_bytes length is bounded by the scratch buffer size.
4525                    let ptr_i = unsafe {
4526                        self.builder
4527                            .build_gep(arr_ty, buf_global, &[idx0, idx_i], "ch_ptr")
4528                            .map_err(|e| CodeGenError::Builder(e.to_string()))?
4529                    };
4530                    let ch = self
4531                        .builder
4532                        .build_load(self.context.i8_type(), ptr_i, "ch")
4533                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4534                    let ch = match ch {
4535                        BasicValueEnum::IntValue(iv) => iv,
4536                        _ => return Err(CodeGenError::LLVMError("load did not return i8".into())),
4537                    };
4538                    let expect = self.context.i8_type().const_int(*b as u64, false);
4539                    let diff = self
4540                        .builder
4541                        .build_xor(ch, expect, "diff")
4542                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4543                    acc = self
4544                        .builder
4545                        .build_or(acc, diff, "acc_or")
4546                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4547                }
4548                let eq_bytes = self
4549                    .builder
4550                    .build_int_compare(
4551                        inkwell::IntPredicate::EQ,
4552                        acc,
4553                        self.context.i8_type().const_zero(),
4554                        "acc_zero",
4555                    )
4556                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4557                let ok1 = self
4558                    .builder
4559                    .build_and(len_ok, nul_ok, "ok_len_nul")
4560                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4561                self.builder
4562                    .build_and(ok1, eq_bytes, "str_eq")
4563                    .map_err(|e| CodeGenError::Builder(e.to_string()))?
4564            }
4565            // char[N]
4566            Some(TI::ArrayType {
4567                element_type,
4568                element_count,
4569                total_size,
4570            }) => {
4571                let elem = ghostscope_dwarf::strip_type_aliases(element_type.as_ref());
4572                let is_char_like = matches!(elem, TI::BaseType { name, size, .. } if name.contains("char") && *size == 1);
4573                if !is_char_like {
4574                    return Err(CodeGenError::TypeError(
4575                        "automatic string comparison only supports char[N]".into(),
4576                    ));
4577                }
4578                // Determine N (element count)
4579                let n_opt = element_count.or_else(|| total_size.map(|ts| ts));
4580                let n = if let Some(nv) = n_opt { nv as u32 } else { 0 };
4581                if n == 0 {
4582                    return Err(CodeGenError::TypeError(
4583                        "array size unknown for char[N] comparison".into(),
4584                    ));
4585                }
4586                // If L+1 > N, compile-time false
4587                if lit_len + 1 > n {
4588                    // Return const false (or true if '!=' requested)
4589                    return Ok((if is_equal { zero } else { one }).into());
4590                }
4591                let status_ptr = if self.condition_context_active {
4592                    Some(self.get_or_create_cond_error_global())
4593                } else {
4594                    None
4595                };
4596                let pc_address = self.get_compile_time_context()?.pc_address;
4597                let addr =
4598                    self.variable_read_plan_to_runtime_address(&var, pc_address, status_ptr)?;
4599                // Read exactly L+1 bytes
4600                let (buf_global, status, arr_ty) =
4601                    self.read_user_bytes_into_buffer(addr, lit_len + 1, "_gs_arrbuf")?;
4602                // status == 0
4603                let status_ok = self
4604                    .builder
4605                    .build_int_compare(
4606                        inkwell::IntPredicate::EQ,
4607                        status,
4608                        self.context.i64_type().const_zero(),
4609                        "rd_ok",
4610                    )
4611                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4612                // buf[L] must be '\0'
4613                let i32_ty = self.context.i32_type();
4614                let idx0 = i32_ty.const_zero();
4615                let idx_l = i32_ty.const_int(lit_len as u64, false);
4616                // SAFETY: the string read requested lit_len + 1 bytes, so index
4617                // lit_len is within the scratch buffer.
4618                let char_ptr = unsafe {
4619                    self.builder
4620                        .build_gep(arr_ty, buf_global, &[idx0, idx_l], "nul_ptr")
4621                        .map_err(|e| CodeGenError::Builder(e.to_string()))?
4622                };
4623                let c = self
4624                    .builder
4625                    .build_load(self.context.i8_type(), char_ptr, "c_l")
4626                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4627                let c = match c {
4628                    BasicValueEnum::IntValue(iv) => iv,
4629                    _ => return Err(CodeGenError::LLVMError("load did not return i8".into())),
4630                };
4631                let nul_ok = self
4632                    .builder
4633                    .build_int_compare(
4634                        inkwell::IntPredicate::EQ,
4635                        c,
4636                        self.context.i8_type().const_zero(),
4637                        "nul_ok",
4638                    )
4639                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4640                // Compare first L bytes using XOR/OR accumulation
4641                let mut acc = self.context.i8_type().const_zero();
4642                for (i, b) in lit_bytes.iter().enumerate() {
4643                    let idx_i = i32_ty.const_int(i as u64, false);
4644                    // SAFETY: lit_bytes length is bounded by the scratch buffer size.
4645                    let ptr_i = unsafe {
4646                        self.builder
4647                            .build_gep(arr_ty, buf_global, &[idx0, idx_i], "ch_ptr")
4648                            .map_err(|e| CodeGenError::Builder(e.to_string()))?
4649                    };
4650                    let ch = self
4651                        .builder
4652                        .build_load(self.context.i8_type(), ptr_i, "ch")
4653                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4654                    let ch = match ch {
4655                        BasicValueEnum::IntValue(iv) => iv,
4656                        _ => return Err(CodeGenError::LLVMError("load did not return i8".into())),
4657                    };
4658                    let expect = self.context.i8_type().const_int(*b as u64, false);
4659                    let diff = self
4660                        .builder
4661                        .build_xor(ch, expect, "diff")
4662                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4663                    acc = self
4664                        .builder
4665                        .build_or(acc, diff, "acc_or")
4666                        .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4667                }
4668                let eq_bytes = self
4669                    .builder
4670                    .build_int_compare(
4671                        inkwell::IntPredicate::EQ,
4672                        acc,
4673                        self.context.i8_type().const_zero(),
4674                        "acc_zero",
4675                    )
4676                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4677                let ok1 = self
4678                    .builder
4679                    .build_and(status_ok, nul_ok, "ok_len_nul")
4680                    .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4681                self.builder
4682                    .build_and(ok1, eq_bytes, "arr_eq")
4683                    .map_err(|e| CodeGenError::Builder(e.to_string()))?
4684            }
4685            None => {
4686                let status_ptr = if self.condition_context_active {
4687                    Some(self.get_or_create_cond_error_global())
4688                } else {
4689                    None
4690                };
4691                let pc_address = self.get_compile_time_context()?.pc_address;
4692                let addr =
4693                    self.variable_read_plan_to_runtime_address(&var, pc_address, status_ptr)?;
4694                // Fallback using type_name string
4695                match parse_type_name(&var.type_name) {
4696                    ParsedKind::PtrChar => {
4697                        // Load pointer value from variable location (assume 64-bit)
4698                        let ptr_any = self.generate_memory_read(
4699                            addr,
4700                            ghostscope_dwarf::MemoryAccessSize::U64,
4701                            None,
4702                        )?;
4703                        let ptr_i64 = match ptr_any {
4704                            BasicValueEnum::IntValue(iv) => iv,
4705                            _ => {
4706                                return Err(CodeGenError::LLVMError(
4707                                    "pointer load did not return integer".to_string(),
4708                                ))
4709                            }
4710                        };
4711                        let need = lit_len + 1;
4712                        let (buf_global, ret_len, arr_ty) = self.read_user_cstr_into_buffer(
4713                            RuntimeAddress::available(ptr_i64, self.context),
4714                            need,
4715                            "_gs_strbuf",
4716                        )?;
4717
4718                        let i64_ty = self.context.i64_type();
4719                        let expect_len = i64_ty.const_int(need as u64, false);
4720                        let len_ok = self
4721                            .builder
4722                            .build_int_compare(
4723                                inkwell::IntPredicate::EQ,
4724                                ret_len,
4725                                expect_len,
4726                                "str_len_ok",
4727                            )
4728                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4729
4730                        let i32_ty = self.context.i32_type();
4731                        let idx0 = i32_ty.const_zero();
4732                        let idx_l = i32_ty.const_int(lit_len as u64, false);
4733                        // SAFETY: the string read requested lit_len + 1 bytes, so
4734                        // index lit_len is within the scratch buffer.
4735                        let char_ptr = unsafe {
4736                            self.builder
4737                                .build_gep(arr_ty, buf_global, &[idx0, idx_l], "nul_ptr")
4738                                .map_err(|e| CodeGenError::Builder(e.to_string()))?
4739                        };
4740                        let c = self
4741                            .builder
4742                            .build_load(self.context.i8_type(), char_ptr, "c_l")
4743                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4744                        let c = match c {
4745                            BasicValueEnum::IntValue(iv) => iv,
4746                            _ => {
4747                                return Err(CodeGenError::LLVMError(
4748                                    "load did not return i8".into(),
4749                                ))
4750                            }
4751                        };
4752                        let nul_ok = self
4753                            .builder
4754                            .build_int_compare(
4755                                inkwell::IntPredicate::EQ,
4756                                c,
4757                                self.context.i8_type().const_zero(),
4758                                "nul_ok",
4759                            )
4760                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4761
4762                        let mut acc = self.context.i8_type().const_zero();
4763                        for (i, b) in lit_bytes.iter().enumerate() {
4764                            let idx_i = i32_ty.const_int(i as u64, false);
4765                            // SAFETY: lit_bytes length is bounded by the scratch buffer size.
4766                            let ptr_i = unsafe {
4767                                self.builder
4768                                    .build_gep(arr_ty, buf_global, &[idx0, idx_i], "ch_ptr")
4769                                    .map_err(|e| CodeGenError::Builder(e.to_string()))?
4770                            };
4771                            let ch = self
4772                                .builder
4773                                .build_load(self.context.i8_type(), ptr_i, "ch")
4774                                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4775                            let ch = match ch {
4776                                BasicValueEnum::IntValue(iv) => iv,
4777                                _ => {
4778                                    return Err(CodeGenError::LLVMError(
4779                                        "load did not return i8".into(),
4780                                    ))
4781                                }
4782                            };
4783                            let expect = self.context.i8_type().const_int(*b as u64, false);
4784                            let diff = self
4785                                .builder
4786                                .build_xor(ch, expect, "diff")
4787                                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4788                            acc = self
4789                                .builder
4790                                .build_or(acc, diff, "acc_or")
4791                                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4792                        }
4793                        let eq_bytes = self
4794                            .builder
4795                            .build_int_compare(
4796                                inkwell::IntPredicate::EQ,
4797                                acc,
4798                                self.context.i8_type().const_zero(),
4799                                "acc_zero",
4800                            )
4801                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4802                        let ok1 = self
4803                            .builder
4804                            .build_and(len_ok, nul_ok, "ok_len_nul")
4805                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4806                        self.builder
4807                            .build_and(ok1, eq_bytes, "str_eq")
4808                            .map_err(|e| CodeGenError::Builder(e.to_string()))?
4809                    }
4810                    ParsedKind::ArrChar(n_opt) => {
4811                        // If we know N and L+1>N, return false; else read L+1 bytes
4812                        if let Some(n) = n_opt {
4813                            if lit_len + 1 > n {
4814                                return Ok((if is_equal { zero } else { one }).into());
4815                            }
4816                        }
4817                        let (buf_global, status, arr_ty) =
4818                            self.read_user_bytes_into_buffer(addr, lit_len + 1, "_gs_arrbuf")?;
4819                        let status_ok = self
4820                            .builder
4821                            .build_int_compare(
4822                                inkwell::IntPredicate::EQ,
4823                                status,
4824                                self.context.i64_type().const_zero(),
4825                                "rd_ok",
4826                            )
4827                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4828                        let i32_ty = self.context.i32_type();
4829                        let idx0 = i32_ty.const_zero();
4830                        let idx_l = i32_ty.const_int(lit_len as u64, false);
4831                        // SAFETY: the string read requested lit_len + 1 bytes, so
4832                        // index lit_len is within the scratch buffer.
4833                        let char_ptr = unsafe {
4834                            self.builder
4835                                .build_gep(arr_ty, buf_global, &[idx0, idx_l], "nul_ptr")
4836                                .map_err(|e| CodeGenError::Builder(e.to_string()))?
4837                        };
4838                        let c = self
4839                            .builder
4840                            .build_load(self.context.i8_type(), char_ptr, "c_l")
4841                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4842                        let c = match c {
4843                            BasicValueEnum::IntValue(iv) => iv,
4844                            _ => {
4845                                return Err(CodeGenError::LLVMError(
4846                                    "load did not return i8".into(),
4847                                ))
4848                            }
4849                        };
4850                        let nul_ok = self
4851                            .builder
4852                            .build_int_compare(
4853                                inkwell::IntPredicate::EQ,
4854                                c,
4855                                self.context.i8_type().const_zero(),
4856                                "nul_ok",
4857                            )
4858                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4859                        let mut acc = self.context.i8_type().const_zero();
4860                        for (i, b) in lit_bytes.iter().enumerate() {
4861                            let idx_i = i32_ty.const_int(i as u64, false);
4862                            // SAFETY: lit_bytes length is bounded by the scratch buffer size.
4863                            let ptr_i = unsafe {
4864                                self.builder
4865                                    .build_gep(arr_ty, buf_global, &[idx0, idx_i], "ch_ptr")
4866                                    .map_err(|e| CodeGenError::Builder(e.to_string()))?
4867                            };
4868                            let ch = self
4869                                .builder
4870                                .build_load(self.context.i8_type(), ptr_i, "ch")
4871                                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4872                            let ch = match ch {
4873                                BasicValueEnum::IntValue(iv) => iv,
4874                                _ => {
4875                                    return Err(CodeGenError::LLVMError(
4876                                        "load did not return i8".into(),
4877                                    ))
4878                                }
4879                            };
4880                            let expect = self.context.i8_type().const_int(*b as u64, false);
4881                            let diff = self
4882                                .builder
4883                                .build_xor(ch, expect, "diff")
4884                                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4885                            acc = self
4886                                .builder
4887                                .build_or(acc, diff, "acc_or")
4888                                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4889                        }
4890                        let eq_bytes = self
4891                            .builder
4892                            .build_int_compare(
4893                                inkwell::IntPredicate::EQ,
4894                                acc,
4895                                self.context.i8_type().const_zero(),
4896                                "acc_zero",
4897                            )
4898                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4899                        let ok1 = self
4900                            .builder
4901                            .build_and(status_ok, nul_ok, "ok_len_nul")
4902                            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
4903                        self.builder
4904                            .build_and(ok1, eq_bytes, "arr_eq")
4905                            .map_err(|e| CodeGenError::Builder(e.to_string()))?
4906                    }
4907                    ParsedKind::Other => {
4908                        return Err(CodeGenError::TypeError(format!(
4909                            "string comparison unsupported for type name '{}' without DWARF type",
4910                            var.type_name
4911                        )));
4912                    }
4913                }
4914            }
4915            Some(_) => {
4916                return Err(CodeGenError::TypeError(
4917                    "string comparison only supports char* or char[N]".into(),
4918                ));
4919            }
4920        };
4921
4922        // Apply == / !=
4923        let final_bool = if is_equal {
4924            result
4925        } else {
4926            self.builder
4927                .build_not(result, "not_eq")
4928                .map_err(|e| CodeGenError::Builder(e.to_string()))?
4929        };
4930        Ok(final_bool.into())
4931    }
4932}