ghostscope_compiler/ebpf/
context.rs

1//! eBPF LLVM context and core infrastructure
2//!
3//! This module provides the main code generation context and basic LLVM
4//! infrastructure for eBPF program generation.
5
6use super::maps::MapManager;
7use crate::script::{VarType, VariableContext};
8use ghostscope_dwarf::DwarfAnalyzer;
9use inkwell::builder::Builder;
10use inkwell::context::Context;
11use inkwell::debug_info::DebugInfoBuilder;
12use inkwell::module::Module;
13use inkwell::targets::{Target, TargetTriple};
14use inkwell::values::{FunctionValue, IntValue, PointerValue};
15use inkwell::AddressSpace;
16use inkwell::OptimizationLevel;
17use std::collections::HashMap;
18use thiserror::Error;
19use tracing::info;
20
21/// Compile-time context containing PC address and module information for DWARF queries
22#[derive(Debug, Clone)]
23pub struct CompileTimeContext {
24    pub pc_address: u64,
25    pub module_path: String,
26}
27
28#[derive(Error, Debug)]
29pub enum CodeGenError {
30    #[error("LLVM compilation error: {0}")]
31    LLVMError(String),
32    #[error("Unsupported evaluation result: {0}")]
33    UnsupportedEvaluation(String),
34    #[error("Register mapping error: {0}")]
35    RegisterMappingError(String),
36    #[error("Memory access error: {0}")]
37    MemoryAccessError(String),
38    #[error("Builder error: {0}")]
39    Builder(String),
40
41    // === Legacy variable management errors ===
42    #[error("Variable not found: {0}")]
43    VariableNotFound(String),
44    #[error("Variable not in scope: {0}")]
45    VariableNotInScope(String),
46    #[error("Type error: {0}")]
47    TypeError(String),
48    #[error("Not implemented: {0}")]
49    NotImplemented(String),
50    #[error("DWARF expression error: {0}")]
51    DwarfError(String),
52    #[error("Type size not available for variable: {0}")]
53    TypeSizeNotAvailable(String),
54}
55
56pub type Result<T> = std::result::Result<T, CodeGenError>;
57
58/// eBPF LLVM code generation context
59pub struct EbpfContext<'ctx> {
60    pub context: &'ctx Context,
61    pub module: Module<'ctx>,
62    pub builder: Builder<'ctx>,
63
64    // eBPF-specific function declarations
65    pub trace_printk_fn: FunctionValue<'ctx>,
66
67    // Map manager for eBPF maps
68    pub map_manager: MapManager<'ctx>,
69
70    // Debug infrastructure
71    pub di_builder: DebugInfoBuilder<'ctx>,
72    pub compile_unit: inkwell::debug_info::DICompileUnit<'ctx>,
73
74    // Register cache for pt_regs access
75    pub register_cache: HashMap<u16, IntValue<'ctx>>,
76
77    // === Complete Variable Management System ===
78    pub variables: HashMap<String, PointerValue<'ctx>>, // Variable name -> LLVM pointer
79    pub var_types: HashMap<String, VarType>,            // Variable name -> type
80    pub optimized_out_vars: HashMap<String, bool>,      // Optimized out variables
81    pub var_pc_addresses: HashMap<String, u64>,         // Variable -> PC address
82    pub variable_context: Option<VariableContext>,      // Scope validation context
83    pub process_analyzer: Option<*mut DwarfAnalyzer>,   // Multi-module DWARF analyzer
84    pub current_trace_id: Option<u32>,                  // Current trace_id being compiled
85    pub current_compile_time_context: Option<CompileTimeContext>, // PC address and module for DWARF queries
86
87    // === New instruction-based compilation system ===
88    pub trace_context: ghostscope_protocol::TraceContext, // Trace context for optimized transmission
89    pub current_resolved_var_module_path: Option<String>,
90
91    // Per-invocation stack key for proc_module_offsets lookups (allocated in entry block)
92    pub pm_key_alloca: Option<inkwell::values::PointerValue<'ctx>>, // [3 x i32] alloca
93    // Per-invocation event accumulation offset (u32) stored on stack (entry block)
94    pub event_offset_alloca: Option<inkwell::values::PointerValue<'ctx>>,
95    // Tracks whether the last proc_module_offsets lookup succeeded (used to skip reads)
96    pub offsets_found_flag: Option<inkwell::values::PointerValue<'ctx>>,
97
98    // Compilation options (includes eBPF map configuration)
99    pub compile_options: crate::CompileOptions,
100
101    // === Control-flow expression error capture (soft abort) ===
102    pub condition_context_active: bool,
103
104    // === DWARF alias variables (script-level symbolic references) ===
105    // These variables do not store pointer values; instead they remember the RHS
106    // expression and are resolved to runtime addresses at use sites.
107    pub alias_vars: HashMap<String, crate::script::Expr>,
108
109    // === Script string variables (store literal bytes for content printing) ===
110    // When a variable is bound from a string literal (or copied from another string var),
111    // we keep its bytes (including optional NUL) here for content printing via ImmediateBytes.
112    pub string_vars: HashMap<String, Vec<u8>>,
113
114    // === Lexical scoping for immutable variables ===
115    // Each scope frame records names declared in that scope.
116    pub scope_stack: Vec<std::collections::HashSet<String>>,
117}
118
119// Temporary alias for backward compatibility during refactoring
120pub type NewCodeGen<'ctx> = EbpfContext<'ctx>;
121
122impl<'ctx> EbpfContext<'ctx> {
123    /// Create a new eBPF code generation context
124    pub fn new(
125        context: &'ctx Context,
126        module_name: &str,
127        trace_id: Option<u32>,
128        compile_options: &crate::CompileOptions,
129    ) -> Result<Self> {
130        let module = context.create_module(module_name);
131        let builder = context.create_builder();
132
133        // Initialize standard BPF target
134        Target::initialize_bpf(&Default::default());
135
136        // Create BPF target triple
137        let triple = TargetTriple::create("bpf-pc-linux");
138
139        // Get target and create target machine
140        let target = Target::from_triple(&triple).map_err(|e| {
141            CodeGenError::LLVMError(format!("Failed to get target from triple: {e}"))
142        })?;
143        let target_machine = target
144            .create_target_machine(
145                &triple,
146                "generic",
147                "+alu32",
148                OptimizationLevel::Default,
149                inkwell::targets::RelocMode::PIC,
150                inkwell::targets::CodeModel::Small,
151            )
152            .ok_or_else(|| {
153                CodeGenError::LLVMError("Failed to create target machine".to_string())
154            })?;
155
156        // Set module data layout and triple
157        let data_layout = target_machine.get_target_data().get_data_layout();
158        module.set_data_layout(&data_layout);
159        module.set_triple(&triple);
160
161        // Initialize debug info
162        let (di_builder, compile_unit) = module.create_debug_info_builder(
163            true,                                         // allow_unresolved
164            inkwell::debug_info::DWARFSourceLanguage::C,  // language
165            "ghostscope_generated.c",                     // filename
166            ".",                                          // directory
167            "ghostscope-compiler",                        // producer
168            false,                                        // is_optimized
169            "",                                           // flags
170            1,                                            // runtime_version
171            "",                                           // split_name
172            inkwell::debug_info::DWARFEmissionKind::Full, // kind
173            0,                                            // dwo_id
174            false,                                        // split_debug_inlining
175            false,                                        // debug_info_for_profiling
176            "",                                           // sysroot
177            "",                                           // sdk
178        );
179
180        let map_manager = MapManager::new(context);
181
182        // Declare eBPF helper functions
183        let trace_printk_fn = Self::declare_trace_printk(context, &module);
184
185        Ok(Self {
186            context,
187            module,
188            builder,
189            trace_printk_fn,
190            map_manager,
191            di_builder,
192            compile_unit,
193            register_cache: HashMap::new(),
194
195            // Initialize variable management system
196            variables: HashMap::new(),
197            var_types: HashMap::new(),
198            optimized_out_vars: HashMap::new(),
199            var_pc_addresses: HashMap::new(),
200            variable_context: None,
201            process_analyzer: None,
202            current_trace_id: trace_id,
203            current_compile_time_context: None,
204
205            // Initialize new instruction-based compilation system
206            trace_context: ghostscope_protocol::TraceContext::new(),
207            current_resolved_var_module_path: None,
208            pm_key_alloca: None,
209            event_offset_alloca: None,
210            offsets_found_flag: None,
211            compile_options: compile_options.clone(),
212
213            // Control-flow expression context
214            condition_context_active: false,
215
216            // Alias variables
217            alias_vars: HashMap::new(),
218            // String variables
219            string_vars: HashMap::new(),
220
221            // Scopes
222            scope_stack: Vec::new(),
223        })
224    }
225
226    /// Enter a new lexical scope
227    pub fn enter_scope(&mut self) {
228        self.scope_stack.push(std::collections::HashSet::new());
229    }
230
231    /// Exit current lexical scope and drop all names declared within
232    pub fn exit_scope(&mut self) {
233        if let Some(names) = self.scope_stack.pop() {
234            for name in names {
235                self.variables.remove(&name);
236                self.var_types.remove(&name);
237                self.alias_vars.remove(&name);
238                self.string_vars.remove(&name);
239                self.optimized_out_vars.remove(&name);
240                self.var_pc_addresses.remove(&name);
241            }
242        }
243    }
244
245    /// Check if a name exists in any active scope
246    pub fn is_name_in_any_scope(&self, name: &str) -> bool {
247        self.scope_stack.iter().any(|s| s.contains(name))
248    }
249
250    /// Check if a name exists in current (top) scope
251    pub fn is_name_in_current_scope(&self, name: &str) -> bool {
252        match self.scope_stack.last() {
253            Some(top) => top.contains(name),
254            None => false,
255        }
256    }
257
258    /// Declare a name in the current scope. Disallow same-scope redeclaration and shadowing.
259    pub fn declare_name_in_current_scope(&mut self, name: &str) -> Result<()> {
260        if self.scope_stack.is_empty() {
261            // Initialize a root scope if not present
262            self.enter_scope();
263        }
264        if self.is_name_in_current_scope(name) {
265            return Err(CodeGenError::TypeError(format!(
266                "Redeclaration in the same scope is not allowed: '{name}'"
267            )));
268        }
269        if self.is_name_in_any_scope(name) {
270            return Err(CodeGenError::TypeError(format!(
271                "Shadowing is not allowed for immutable variables: '{name}'"
272            )));
273        }
274        if let Some(top) = self.scope_stack.last_mut() {
275            top.insert(name.to_string());
276        }
277        Ok(())
278    }
279
280    /// Create a new code generator with DWARF analyzer support
281    pub fn new_with_process_analyzer(
282        context: &'ctx Context,
283        module_name: &str,
284        process_analyzer: Option<&mut DwarfAnalyzer>,
285        trace_id: Option<u32>,
286        compile_options: &crate::CompileOptions,
287    ) -> Result<Self> {
288        let mut codegen = Self::new(context, module_name, trace_id, compile_options)?;
289        codegen.process_analyzer = process_analyzer.map(|pa| pa as *const _ as *mut _);
290        Ok(codegen)
291    }
292
293    /// Set compile-time context for DWARF queries
294    pub fn set_compile_time_context(&mut self, pc_address: u64, module_path: String) {
295        self.current_compile_time_context = Some(CompileTimeContext {
296            pc_address,
297            module_path,
298        });
299    }
300
301    /// Get compile-time context for DWARF queries
302    pub fn get_compile_time_context(&self) -> Result<&CompileTimeContext> {
303        self.current_compile_time_context
304            .as_ref()
305            .ok_or_else(|| CodeGenError::DwarfError("No compile-time context set".to_string()))
306    }
307
308    /// Take and clear the current module hint for offsets (if any)
309    pub fn take_module_hint(&mut self) -> Option<String> {
310        self.current_resolved_var_module_path.take()
311    }
312
313    /// Declare trace_printk eBPF helper function
314    fn declare_trace_printk(context: &'ctx Context, module: &Module<'ctx>) -> FunctionValue<'ctx> {
315        let i32_type = context.i32_type();
316        let ptr_type = context.ptr_type(AddressSpace::default());
317        let i64_type = context.i64_type();
318
319        // int bpf_trace_printk(const char *fmt, u32 fmt_size, ...)
320        let fn_type = i32_type.fn_type(&[ptr_type.into(), i64_type.into()], true);
321
322        module.add_function("bpf_trace_printk", fn_type, None)
323    }
324
325    /// Create basic eBPF function with proper signature
326    pub fn create_basic_ebpf_function(&mut self, function_name: &str) -> Result<()> {
327        let i32_type = self.context.i32_type();
328        let ptr_type = self.context.ptr_type(AddressSpace::default());
329
330        // eBPF function signature: int function(struct pt_regs *ctx)
331        let fn_type = i32_type.fn_type(&[ptr_type.into()], false);
332
333        let function = self.module.add_function(function_name, fn_type, None);
334
335        // Set section attribute for uprobe
336        function.add_attribute(
337            inkwell::attributes::AttributeLoc::Function,
338            self.context.create_string_attribute("section", "uprobe"),
339        );
340
341        // Create basic block
342        let basic_block = self.context.append_basic_block(function, "entry");
343        self.builder.position_at_end(basic_block);
344
345        info!("Created eBPF function: {}", function_name);
346        Ok(())
347    }
348
349    /// Test helper: ensure proc_module_offsets map exists in the module
350    #[cfg(test)]
351    pub fn __test_ensure_proc_offsets_map(&mut self) -> Result<()> {
352        self.map_manager
353            .create_proc_module_offsets_map(
354                &self.module,
355                &self.di_builder,
356                &self.compile_unit,
357                "proc_module_offsets",
358                self.compile_options.proc_module_offsets_max_entries,
359            )
360            .map_err(|e| {
361                CodeGenError::LLVMError(format!(
362                    "Failed to create proc_module_offsets map in test: {e}"
363                ))
364            })
365    }
366
367    /// Test helper: allocate per-invocation pm_key on the entry block like create_main_function
368    #[cfg(test)]
369    pub fn __test_alloc_pm_key(&mut self) -> Result<()> {
370        let i32_type = self.context.i32_type();
371        let key_arr_ty = i32_type.array_type(4);
372        let key_alloca = self
373            .builder
374            .build_alloca(key_arr_ty, "pm_key")
375            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
376        self.pm_key_alloca = Some(key_alloca);
377        Ok(())
378    }
379
380    /// Get the LLVM module reference
381    pub fn get_module(&self) -> &Module<'ctx> {
382        &self.module
383    }
384
385    /// Get the string table after compilation
386    pub fn get_trace_context(&self) -> ghostscope_protocol::TraceContext {
387        self.trace_context.clone()
388    }
389
390    /// Get pt_regs parameter from current function
391    pub fn get_pt_regs_parameter(&self) -> Result<PointerValue<'ctx>> {
392        let current_function = self
393            .builder
394            .get_insert_block()
395            .ok_or_else(|| CodeGenError::Builder("No current basic block".to_string()))?
396            .get_parent()
397            .ok_or_else(|| CodeGenError::Builder("No parent function".to_string()))?;
398
399        let pt_regs_param = current_function
400            .get_first_param()
401            .ok_or_else(|| CodeGenError::Builder("Function has no parameters".to_string()))?
402            .into_pointer_value();
403
404        Ok(pt_regs_param)
405    }
406
407    /// Compile a complete program with statements
408    pub fn compile_program(
409        &mut self,
410        _program: &crate::script::Program,
411        function_name: &str,
412        trace_statements: &[crate::script::Statement],
413        target_pid: Option<u32>,
414        compile_time_pc: Option<u64>,
415        module_path: Option<&str>,
416    ) -> Result<(FunctionValue<'ctx>, ghostscope_protocol::TraceContext)> {
417        info!(
418            "Starting program compilation with function: {}",
419            function_name
420        );
421
422        // Set the current trace_id and compile-time context for code generation
423        self.current_compile_time_context =
424            if let (Some(pc), Some(path)) = (compile_time_pc, module_path) {
425                Some(CompileTimeContext {
426                    pc_address: pc,
427                    module_path: path.to_string(),
428                })
429            } else {
430                None
431            };
432
433        // Create required maps - critical for eBPF loader
434        // Create event output map based on compile options
435        match self.compile_options.event_map_type {
436            crate::EventMapType::RingBuf => {
437                self.map_manager
438                    .create_ringbuf_map(
439                        &self.module,
440                        &self.di_builder,
441                        &self.compile_unit,
442                        "ringbuf",
443                        self.compile_options.ringbuf_size,
444                    )
445                    .map_err(|e| {
446                        CodeGenError::LLVMError(format!("Failed to create ringbuf map: {e}"))
447                    })?;
448            }
449            crate::EventMapType::PerfEventArray => {
450                self.map_manager
451                    .create_perf_event_array_map(
452                        &self.module,
453                        &self.di_builder,
454                        &self.compile_unit,
455                        "events",
456                    )
457                    .map_err(|e| {
458                        CodeGenError::LLVMError(format!(
459                            "Failed to create perf event array map: {e}"
460                        ))
461                    })?;
462            }
463        }
464
465        // Create per-CPU accumulation maps for single-record event emission
466        //  - event_accum_buffer: value size = max_trace_event_size bytes, entries = 1
467        //  - event_accum_offset: value size = 4 bytes (u32), entries = 1
468        self.map_manager
469            .create_percpu_array_map(
470                &self.module,
471                &self.di_builder,
472                &self.compile_unit,
473                "event_accum_buffer",
474                1,
475                self.compile_options.max_trace_event_size as u64,
476            )
477            .map_err(|e| {
478                CodeGenError::LLVMError(format!("Failed to create event_accum_buffer: {e}"))
479            })?;
480
481        // Create ASLR offsets map for (pid,module) → section offsets
482        self.map_manager
483            .create_proc_module_offsets_map(
484                &self.module,
485                &self.di_builder,
486                &self.compile_unit,
487                "proc_module_offsets",
488                self.compile_options.proc_module_offsets_max_entries,
489            )
490            .map_err(|e| {
491                CodeGenError::LLVMError(format!("Failed to create proc_module_offsets map: {e}"))
492            })?;
493
494        // Variables are now queried on-demand when accessed in expressions
495        // No need to pre-populate DWARF variables
496
497        // Create main function
498        let main_function = self.create_main_function(function_name)?;
499
500        // Add PID filtering if target_pid is specified
501        if let Some(pid) = target_pid {
502            self.add_pid_filter(pid)?;
503        }
504
505        // Use new staged transmission system for all statements
506        let program = crate::script::ast::Program {
507            statements: trace_statements.to_vec(),
508        };
509
510        // Collect variable types from DWARF analysis
511        let variable_types = std::collections::HashMap::new(); // Empty for now, will be populated by codegen
512
513        // Generate staged transmission code using new architecture
514        let trace_context =
515            self.compile_program_with_staged_transmission(&program, variable_types)?;
516        info!(
517            "Generated TraceContext with {} strings",
518            trace_context.string_count()
519        );
520
521        // Return success
522        let i32_type = self.context.i32_type();
523        let return_value = i32_type.const_int(0, false);
524        self.builder
525            .build_return(Some(&return_value))
526            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
527
528        info!(
529            "Successfully compiled program with function: {} and TraceContext",
530            function_name
531        );
532        Ok((main_function, trace_context))
533    }
534
535    /// Create the main eBPF function
536    fn create_main_function(&mut self, function_name: &str) -> Result<FunctionValue<'ctx>> {
537        let i32_type = self.context.i32_type();
538        let ptr_type = self.context.ptr_type(AddressSpace::default());
539
540        // Create function type: int function_name(void *ctx)
541        let fn_type = i32_type.fn_type(&[ptr_type.into()], false);
542        let function = self.module.add_function(function_name, fn_type, None);
543
544        // CRITICAL: Set section name for eBPF loader to find the function
545        function.set_section(Some("uprobe"));
546
547        // Create basic block and position builder
548        let basic_block = self.context.append_basic_block(function, "entry");
549        self.builder.position_at_end(basic_block);
550
551        // Allocate fixed-size per-invocation key buffer on the eBPF stack (entry block)
552        // Layout: [ pid:u32, pad:u32, cookie_lo:u32, cookie_hi:u32 ] to match struct {u32; u64}
553        let key_arr_ty = i32_type.array_type(4);
554        let key_alloca = self
555            .builder
556            .build_alloca(key_arr_ty, "pm_key")
557            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
558        self.pm_key_alloca = Some(key_alloca);
559
560        // Allocate per-invocation event_offset (u32) and initialize to 0
561        let event_off_alloca = self
562            .builder
563            .build_alloca(i32_type, "event_offset")
564            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
565        self.builder
566            .build_store(event_off_alloca, i32_type.const_zero())
567            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
568        self.event_offset_alloca = Some(event_off_alloca);
569
570        info!("Created main function: {}", function_name);
571        Ok(function)
572    }
573
574    /// Add PID filtering logic to the current function
575    /// This generates LLVM IR to check current PID against target PID and early return if not matching
576    fn add_pid_filter(&mut self, target_pid: u32) -> Result<()> {
577        info!("Adding PID filter for target PID: {}", target_pid);
578
579        // Get current function and entry block
580        let current_fn = self
581            .builder
582            .get_insert_block()
583            .unwrap()
584            .get_parent()
585            .unwrap();
586
587        // Create basic blocks for control flow
588        let continue_block = self
589            .context
590            .append_basic_block(current_fn, "continue_execution");
591        let early_return_block = self
592            .context
593            .append_basic_block(current_fn, "pid_mismatch_return");
594
595        // Get current PID/TID using bpf_get_current_pid_tgid helper
596        let pid_tgid_value = self.get_current_pid_tgid()?;
597
598        // Extract TGID (high 32 bits) by right shifting 32 bits
599        let shift_amount = self.context.i64_type().const_int(32, false);
600        let current_tgid = self
601            .builder
602            .build_right_shift(pid_tgid_value, shift_amount, false, "current_tgid")
603            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
604
605        // Convert target_pid to i64 and compare
606        let target_pid_value = self.context.i64_type().const_int(target_pid as u64, false);
607        let pid_matches = self
608            .builder
609            .build_int_compare(
610                inkwell::IntPredicate::EQ,
611                current_tgid,
612                target_pid_value,
613                "pid_matches",
614            )
615            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
616
617        // Conditional branch: if pid matches, continue; else early return
618        self.builder
619            .build_conditional_branch(pid_matches, continue_block, early_return_block)
620            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
621
622        // Early return block - just return 0
623        self.builder.position_at_end(early_return_block);
624        self.builder
625            .build_return(Some(&self.context.i32_type().const_int(0, false)))
626            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
627
628        // Position at continue block for the rest of the function
629        self.builder.position_at_end(continue_block);
630
631        info!(
632            "PID filter added successfully for target PID: {}",
633            target_pid
634        );
635        Ok(())
636    }
637
638    /// Get or create a global i8 flag by name, initialized to 0
639    pub fn get_or_create_flag_global(&mut self, name: &str) -> PointerValue<'ctx> {
640        if let Some(g) = self.module.get_global(name) {
641            return g.as_pointer_value();
642        }
643        let i8_type = self.context.i8_type();
644        let global = self
645            .module
646            .add_global(i8_type, Some(AddressSpace::default()), name);
647        global.set_initializer(&i8_type.const_zero());
648        global.as_pointer_value()
649    }
650
651    /// Set a flag global to a constant u8 value at runtime
652    pub fn store_flag_value(&mut self, name: &str, value: u8) -> Result<()> {
653        let ptr = self.get_or_create_flag_global(name);
654        self.builder
655            .build_store(ptr, self.context.i8_type().const_int(value as u64, false))
656            .map_err(|e| CodeGenError::LLVMError(format!("Failed to store flag {name}: {e}")))?;
657        Ok(())
658    }
659
660    /// Mark that at least one variable succeeded (status==0)
661    pub fn mark_any_success(&mut self) -> Result<()> {
662        self.store_flag_value("_gs_any_success", 1)
663    }
664
665    /// Mark that at least one variable failed (status!=0)
666    pub fn mark_any_fail(&mut self) -> Result<()> {
667        self.store_flag_value("_gs_any_fail", 1)
668    }
669
670    /// Get (and create if needed) the global flag tracking the last proc_module_offsets lookup.
671    /// Stored as i8 where 0 = miss, 1 = found.
672    pub fn get_or_create_offsets_found_flag(&mut self) -> inkwell::values::PointerValue<'ctx> {
673        if let Some(ptr) = self.offsets_found_flag {
674            return ptr;
675        }
676        let i8_type = self.context.i8_type();
677        // TODO: Replace this global flag with explicit control-flow checks when memory-read emission is refactored.
678        let global =
679            self.module
680                .add_global(i8_type, Some(AddressSpace::default()), "_gs_offsets_found");
681        global.set_initializer(&i8_type.const_int(1, false));
682        let ptr = global.as_pointer_value();
683        self.offsets_found_flag = Some(ptr);
684        ptr
685    }
686
687    /// Store a boolean into the offsets-found flag (true => found, false => miss).
688    pub fn store_offsets_found_flag(
689        &mut self,
690        flag: inkwell::values::IntValue<'ctx>,
691    ) -> Result<()> {
692        let ptr = self.get_or_create_offsets_found_flag();
693        let i8_type = self.context.i8_type();
694        let flag_i8 = self
695            .builder
696            .build_int_z_extend(flag, i8_type, "offset_flag_i8")
697            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
698        self.builder
699            .build_store(ptr, flag_i8)
700            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
701        Ok(())
702    }
703
704    /// Store a constant boolean value into the offsets-found flag.
705    pub fn store_offsets_found_const(&mut self, value: bool) -> Result<()> {
706        let ptr = self.get_or_create_offsets_found_flag();
707        let i8_type = self.context.i8_type();
708        self.builder
709            .build_store(ptr, i8_type.const_int(value as u64, false))
710            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
711        Ok(())
712    }
713
714    /// Load the current offsets-found flag as i1 (true => found, false => miss).
715    pub fn load_offsets_found_flag(&mut self) -> Result<inkwell::values::IntValue<'ctx>> {
716        let ptr = self.get_or_create_offsets_found_flag();
717        let i8_type = self.context.i8_type();
718        let raw = self
719            .builder
720            .build_load(i8_type, ptr, "offsets_found_raw")
721            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
722            .into_int_value();
723        let is_non_zero = self
724            .builder
725            .build_int_compare(
726                inkwell::IntPredicate::NE,
727                raw,
728                i8_type.const_zero(),
729                "offsets_found_bool",
730            )
731            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
732        Ok(is_non_zero)
733    }
734
735    /// Get or create global for condition error code (i8). Name: _gs_cond_error
736    pub fn get_or_create_cond_error_global(&mut self) -> PointerValue<'ctx> {
737        if let Some(g) = self.module.get_global("_gs_cond_error") {
738            return g.as_pointer_value();
739        }
740        let i8_type = self.context.i8_type();
741        let global =
742            self.module
743                .add_global(i8_type, Some(AddressSpace::default()), "_gs_cond_error");
744        global.set_initializer(&i8_type.const_zero());
745        global.as_pointer_value()
746    }
747
748    /// Reset condition error to 0 (only meaningful when condition_context_active=true)
749    pub fn reset_condition_error(&mut self) -> Result<()> {
750        let ptr = self.get_or_create_cond_error_global();
751        self.builder
752            .build_store(ptr, self.context.i8_type().const_zero())
753            .map_err(|e| CodeGenError::LLVMError(format!("Failed to reset _gs_cond_error: {e}")))?;
754        // Also reset error address
755        let aptr = self.get_or_create_cond_error_addr_global();
756        self.builder
757            .build_store(aptr, self.context.i64_type().const_zero())
758            .map_err(|e| {
759                CodeGenError::LLVMError(format!("Failed to reset _gs_cond_error_addr: {e}"))
760            })?;
761        // Also reset flags
762        let fptr = self.get_or_create_cond_error_flags_global();
763        self.builder
764            .build_store(fptr, self.context.i8_type().const_zero())
765            .map_err(|e| {
766                CodeGenError::LLVMError(format!("Failed to reset _gs_cond_error_flags: {e}"))
767            })?;
768        Ok(())
769    }
770
771    /// Get or create global for condition error address (i64). Name: _gs_cond_error_addr
772    pub fn get_or_create_cond_error_addr_global(&mut self) -> PointerValue<'ctx> {
773        if let Some(g) = self.module.get_global("_gs_cond_error_addr") {
774            return g.as_pointer_value();
775        }
776        let i64_type = self.context.i64_type();
777        let global = self.module.add_global(
778            i64_type,
779            Some(AddressSpace::default()),
780            "_gs_cond_error_addr",
781        );
782        global.set_initializer(&i64_type.const_zero());
783        global.as_pointer_value()
784    }
785
786    /// If in condition context, set error code when it's currently 0 (first error wins)
787    pub fn set_condition_error_if_unset(&mut self, code: u8) -> Result<()> {
788        if !self.condition_context_active {
789            return Ok(());
790        }
791        let ptr = self.get_or_create_cond_error_global();
792        let cur = self
793            .builder
794            .build_load(self.context.i8_type(), ptr, "cond_err_cur")
795            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
796            .into_int_value();
797        let is_zero = self
798            .builder
799            .build_int_compare(
800                inkwell::IntPredicate::EQ,
801                cur,
802                self.context.i8_type().const_zero(),
803                "cond_err_is_zero",
804            )
805            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
806        let newv_bv: inkwell::values::BasicValueEnum =
807            self.context.i8_type().const_int(code as u64, false).into();
808        let sel = self
809            .builder
810            .build_select::<inkwell::values::BasicValueEnum, _>(
811                is_zero,
812                newv_bv,
813                cur.into(),
814                "cond_err_new",
815            )
816            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
817        self.builder
818            .build_store(ptr, sel)
819            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
820        Ok(())
821    }
822
823    /// Read the current condition error as i1 predicate: (error != 0)
824    pub fn build_condition_error_predicate(&mut self) -> Result<inkwell::values::IntValue<'ctx>> {
825        let ptr = self.get_or_create_cond_error_global();
826        let cur = self
827            .builder
828            .build_load(self.context.i8_type(), ptr, "cond_err_cur")
829            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
830            .into_int_value();
831        self.builder
832            .build_int_compare(
833                inkwell::IntPredicate::NE,
834                cur,
835                self.context.i8_type().const_zero(),
836                "cond_err_nonzero",
837            )
838            .map_err(|e| CodeGenError::LLVMError(e.to_string()))
839    }
840
841    /// Get or create global for condition error flags (i8). Name: _gs_cond_error_flags
842    pub fn get_or_create_cond_error_flags_global(&mut self) -> PointerValue<'ctx> {
843        if let Some(g) = self.module.get_global("_gs_cond_error_flags") {
844            return g.as_pointer_value();
845        }
846        let i8_type = self.context.i8_type();
847        let global = self.module.add_global(
848            i8_type,
849            Some(AddressSpace::default()),
850            "_gs_cond_error_flags",
851        );
852        global.set_initializer(&i8_type.const_zero());
853        global.as_pointer_value()
854    }
855
856    /// OR into condition error flags (BV must be i8)
857    pub fn or_condition_error_flags(&mut self, flags: IntValue<'ctx>) -> Result<()> {
858        if !self.condition_context_active {
859            return Ok(());
860        }
861        let ptr = self.get_or_create_cond_error_flags_global();
862        let cur = self
863            .builder
864            .build_load(self.context.i8_type(), ptr, "cond_err_flags_cur")
865            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
866            .into_int_value();
867        let newv = self
868            .builder
869            .build_or(cur, flags, "cond_err_flags_or")
870            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
871        self.builder
872            .build_store(ptr, newv)
873            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
874        Ok(())
875    }
876
877    /// If in condition context, record failing address (first win). addr must be i64
878    pub fn set_condition_error_addr_if_unset(&mut self, addr: IntValue<'ctx>) -> Result<()> {
879        if !self.condition_context_active {
880            return Ok(());
881        }
882        let ptr = self.get_or_create_cond_error_addr_global();
883        let cur = self
884            .builder
885            .build_load(self.context.i64_type(), ptr, "cond_err_addr_cur")
886            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
887            .into_int_value();
888        let is_zero = self
889            .builder
890            .build_int_compare(
891                inkwell::IntPredicate::EQ,
892                cur,
893                self.context.i64_type().const_zero(),
894                "cond_err_addr_is_zero",
895            )
896            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
897        let sel = self
898            .builder
899            .build_select::<IntValue<'ctx>, _>(is_zero, addr, cur, "cond_err_addr_new")
900            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
901        self.builder
902            .build_store(ptr, sel)
903            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
904        Ok(())
905    }
906}