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