use super::context::{CodeGenError, EbpfContext, Result, RuntimeAddress};
use super::expression_plan::{
BinaryEmitKind, BinaryIntegerSemantics, BuiltinCallPlan, SpecialVarPlan,
};
use crate::script::{BinaryOp, Expr};
use aya_ebpf_bindings::bindings::bpf_func_id::BPF_FUNC_probe_read_user;
use ghostscope_dwarf::{
AmbiguityReason, Availability, CIntegerComparisonPlan, CIntegerComparisonType,
RuntimeRequirement, TypeInfo as DwarfType, TypeLayoutError, UnsupportedReason,
VariableReadPlan,
};
use inkwell::values::{BasicValueEnum, IntValue, PointerValue};
use inkwell::AddressSpace;
use std::path::{Path, PathBuf};
use tracing::debug;
#[derive(Clone)]
pub(super) struct DynamicTypeInfo {
pub(super) dwarf_type: DwarfType,
pub(super) module_path: Option<PathBuf>,
}
pub(super) struct DynamicLvalue<'ctx> {
pub(super) address: RuntimeAddress<'ctx>,
pub(super) type_info: DynamicTypeInfo,
}
struct IndexableElementInfo {
element_type: DwarfType,
stride: u64,
module_path: Option<PathBuf>,
}
impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> {
pub(crate) fn get_host_pid_tid_values(&mut self) -> Result<(IntValue<'ctx>, IntValue<'ctx>)> {
let i32_type = self.context.i32_type();
let i64_type = self.context.i64_type();
let host_pid_tgid = self.get_current_pid_tgid()?;
let host_tid = self
.builder
.build_and(
host_pid_tgid,
i64_type.const_int(0xFFFF_FFFF, false),
"host_tid",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let host_pid = self
.builder
.build_right_shift(
host_pid_tgid,
i64_type.const_int(32, false),
false,
"host_pid",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let host_pid_i32 = self
.builder
.build_int_truncate(host_pid, i32_type, "host_pid_i32")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let host_tid_i32 = self
.builder
.build_int_truncate(host_tid, i32_type, "host_tid_i32")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok((host_pid_i32, host_tid_i32))
}
pub(crate) fn get_special_pid_tid_values(
&mut self,
) -> Result<(IntValue<'ctx>, IntValue<'ctx>)> {
const BPF_FUNC_GET_NS_CURRENT_PID_TGID: u64 = 120;
const BPF_PIDNS_INFO_SIZE: u64 = 8;
let i32_type = self.context.i32_type();
let i64_type = self.context.i64_type();
let (host_pid_i32, host_tid_i32) = self.get_host_pid_tid_values()?;
let ns_spec = if let Some(crate::PidFilterSpec::NamespaceTgid { pid_ns, .. }) =
self.compile_options.pid_filter_spec
{
pid_ns.helper_dev_inode()
} else {
self.compile_options
.special_pid_ns
.and_then(|pid_ns| pid_ns.helper_dev_inode())
};
let Some((pid_ns_dev, pid_ns_inode)) = ns_spec else {
let host_pid = self
.builder
.build_int_z_extend(host_pid_i32, i64_type, "selected_host_pid")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let host_tid = self
.builder
.build_int_z_extend(host_tid_i32, i64_type, "selected_host_tid")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
return Ok((host_pid, host_tid));
};
let ptr_type = self.context.ptr_type(AddressSpace::default());
let key_arr_ty = i32_type.array_type(4);
let key_alloca = self.pm_key_alloca.ok_or_else(|| {
CodeGenError::LLVMError("pm_key not allocated in entry block".to_string())
})?;
self.builder
.build_store(key_alloca, key_arr_ty.const_zero())
.map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
let pidns_info_ptr = self
.builder
.build_bit_cast(key_alloca, ptr_type, "special_pidns_info_ptr")
.map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
let helper_args = [
i64_type.const_int(pid_ns_dev, false).into(),
i64_type.const_int(pid_ns_inode, false).into(),
pidns_info_ptr,
i64_type.const_int(BPF_PIDNS_INFO_SIZE, false).into(),
];
let helper_ret = self.create_bpf_helper_call(
BPF_FUNC_GET_NS_CURRENT_PID_TGID,
&helper_args,
i64_type.into(),
"special_ns_pid_tgid_ret",
)?;
let helper_ret = match helper_ret {
BasicValueEnum::IntValue(v) => v,
_ => {
return Err(CodeGenError::LLVMError(
"bpf_get_ns_current_pid_tgid did not return integer".to_string(),
))
}
};
let helper_ok = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
helper_ret,
i64_type.const_zero(),
"special_ns_helper_ok",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let ns_pid_ptr = unsafe {
self.builder.build_gep(
key_arr_ty,
key_alloca,
&[i32_type.const_zero(), i32_type.const_zero()],
"special_ns_pid_ptr",
)
}
.map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
let ns_tgid_ptr = unsafe {
self.builder.build_gep(
key_arr_ty,
key_alloca,
&[i32_type.const_zero(), i32_type.const_int(1, false)],
"special_ns_tgid_ptr",
)
}
.map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
let ns_pid = self
.builder
.build_load(i32_type, ns_pid_ptr, "special_ns_pid")
.map_err(|e| CodeGenError::LLVMError(e.to_string()))?
.into_int_value();
let ns_tgid = self
.builder
.build_load(i32_type, ns_tgid_ptr, "special_ns_tgid")
.map_err(|e| CodeGenError::LLVMError(e.to_string()))?
.into_int_value();
let selected_pid_i32 = self
.builder
.build_select(helper_ok, ns_tgid, host_pid_i32, "selected_pid_i32")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_int_value();
let selected_tid_i32 = self
.builder
.build_select(helper_ok, ns_pid, host_tid_i32, "selected_tid_i32")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_int_value();
let selected_pid = self
.builder
.build_int_z_extend(selected_pid_i32, i64_type, "selected_pid")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let selected_tid = self
.builder
.build_int_z_extend(selected_tid_i32, i64_type, "selected_tid")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok((selected_pid, selected_tid))
}
pub(super) fn is_dwarf_aggregate_expr(&mut self, expr: &Expr) -> bool {
if let Expr::Cast { target_type, .. } = expr {
return self
.resolve_cast_target_type(target_type)
.ok()
.is_some_and(|ty| ghostscope_dwarf::is_c_aggregate_type(&ty));
}
if let Ok(Some(var)) = self.query_dwarf_for_complex_expr(expr) {
if let Some(ref ty) = var.dwarf_type {
return ghostscope_dwarf::is_c_aggregate_type(ty);
}
}
false
}
pub(super) fn is_pointer_like_expr(&mut self, expr: &Expr) -> bool {
use crate::script::Expr as E;
match expr {
E::AddressOf(_) => return true,
E::String(_) => return true,
E::Cast { target_type, .. } => {
if self
.resolve_cast_target_type(target_type)
.ok()
.is_some_and(|ty| {
matches!(
ghostscope_dwarf::strip_type_aliases(&ty),
DwarfType::PointerType { .. } | DwarfType::ArrayType { .. }
)
})
{
return true;
}
}
E::Variable(name) => {
if self.alias_variable_exists(name) {
return true;
}
}
_ => {}
}
if let Ok(Some(var)) = self.query_dwarf_for_complex_expr(expr) {
if let Some(ref ty) = var.dwarf_type {
if ghostscope_dwarf::is_c_pointer_or_array_type(ty) {
return true;
}
}
}
false
}
pub(super) fn resolve_cast_target_type(&self, target_type: &str) -> Result<DwarfType> {
let analyzer = self.process_analyzer;
let resolved = if let Some(context) = self.current_compile_time_context.as_ref() {
let module_path = Path::new(&context.module_path);
analyzer
.map(|analyzer| analyzer.try_resolve_type_spec_in_module(module_path, target_type))
.transpose()
.map_err(|err| CodeGenError::DwarfError(err.to_string()))?
.flatten()
.or_else(|| ghostscope_dwarf::DwarfAnalyzer::resolve_builtin_type_spec(target_type))
} else {
analyzer
.map(|analyzer| analyzer.try_resolve_type_spec(target_type))
.transpose()
.map_err(|err| CodeGenError::DwarfError(err.to_string()))?
.flatten()
.or_else(|| ghostscope_dwarf::DwarfAnalyzer::resolve_builtin_type_spec(target_type))
};
resolved.ok_or_else(|| {
CodeGenError::DwarfError(format!("cast target type '{target_type}' was not found"))
})
}
pub(super) fn cast_pointer_target_type(target_type: &DwarfType) -> Option<DwarfType> {
match ghostscope_dwarf::strip_type_aliases(target_type) {
DwarfType::PointerType { target_type, .. } => Some(target_type.as_ref().clone()),
_ => None,
}
}
fn is_float_dwarf_type(target_type: &DwarfType) -> bool {
match ghostscope_dwarf::strip_type_aliases(target_type) {
DwarfType::BaseType { encoding, .. } => {
*encoding == ghostscope_dwarf::constants::DW_ATE_float.0 as u16
}
_ => false,
}
}
fn is_bool_dwarf_type(target_type: &DwarfType) -> bool {
match ghostscope_dwarf::strip_type_aliases(target_type) {
DwarfType::BaseType { encoding, .. } => {
*encoding == ghostscope_dwarf::constants::DW_ATE_boolean.0 as u16
}
_ => false,
}
}
pub(super) fn cast_value_byte_len(target_type: &DwarfType) -> Option<usize> {
if matches!(
ghostscope_dwarf::strip_type_aliases(target_type),
DwarfType::PointerType { .. }
) {
return Some(8);
}
if let Some(integer_type) = ghostscope_dwarf::c_integer_comparison_type(target_type) {
return Some(integer_type.size.clamp(1, 8) as usize);
}
None
}
pub(super) fn cast_source_pointer_value(
&mut self,
expr: &Expr,
) -> Result<RuntimeAddress<'ctx>> {
if let Ok(address) = self.resolve_runtime_address_from_expr(expr) {
return Ok(address);
}
match self.compile_expr(expr)? {
BasicValueEnum::IntValue(value) => Ok(RuntimeAddress::available(
self.normalize_int_to_i64(value, "cast_ptr_i64")?,
self.context,
)),
BasicValueEnum::PointerValue(value) => self
.builder
.build_ptr_to_int(value, self.context.i64_type(), "cast_ptr_value")
.map(|value| RuntimeAddress::available(value, self.context))
.map_err(|err| CodeGenError::Builder(err.to_string())),
_ => Err(CodeGenError::TypeError(
"cast source expression did not produce an address-sized value".to_string(),
)),
}
}
pub(super) fn cast_source_memory_address(
&mut self,
expr: &Expr,
) -> Result<RuntimeAddress<'ctx>> {
if let Ok(address) = self.resolve_runtime_address_from_expr(expr) {
return Ok(address);
}
if let Some(plan) = self.query_dwarf_for_complex_expr(expr)? {
let status_ptr = if self.condition_context_active {
Some(self.get_or_create_cond_error_global())
} else {
None
};
let pc_address = self.get_compile_time_context()?.pc_address;
if let Ok(address) =
self.variable_read_plan_to_runtime_address(&plan, pc_address, status_ptr)
{
return Ok(address);
}
}
match self.compile_expr(expr)? {
BasicValueEnum::IntValue(value) => Ok(RuntimeAddress::available(
self.normalize_int_to_i64(value, "cast_mem_i64")?,
self.context,
)),
BasicValueEnum::PointerValue(value) => self
.builder
.build_ptr_to_int(value, self.context.i64_type(), "cast_mem_ptr")
.map(|value| RuntimeAddress::available(value, self.context))
.map_err(|err| CodeGenError::Builder(err.to_string())),
_ => Err(CodeGenError::TypeError(
"cast source expression is not addressable".to_string(),
)),
}
}
fn cast_lvalue_address_and_type(
&mut self,
expr: &Expr,
target_type: &str,
) -> Result<DynamicLvalue<'ctx>> {
let target_type = self.resolve_cast_target_type(target_type)?;
let module_path = self
.current_compile_time_context
.as_ref()
.map(|context| PathBuf::from(&context.module_path));
if let Some(pointee_type) = Self::cast_pointer_target_type(&target_type) {
let address = self.cast_source_pointer_value(expr)?;
return Ok(DynamicLvalue {
address,
type_info: DynamicTypeInfo {
dwarf_type: pointee_type,
module_path,
},
});
}
let address = self.cast_source_memory_address(expr)?;
Ok(DynamicLvalue {
address,
type_info: DynamicTypeInfo {
dwarf_type: target_type,
module_path,
},
})
}
fn cast_index_base(
&mut self,
expr: &Expr,
) -> Result<Option<(IndexableElementInfo, RuntimeAddress<'ctx>)>> {
let Expr::Cast {
expr: source_expr,
target_type,
} = expr
else {
return Ok(None);
};
let target_type = self.resolve_cast_target_type(target_type)?;
let module_path = self
.current_compile_time_context
.as_ref()
.map(|context| PathBuf::from(&context.module_path));
match ghostscope_dwarf::strip_type_aliases(&target_type) {
DwarfType::PointerType { .. } => {
let Some(element_info) = Self::indexable_info_from_type(&target_type, module_path)
else {
return Ok(None);
};
let base_address = self.cast_source_pointer_value(source_expr)?;
Ok(Some((element_info, base_address)))
}
DwarfType::ArrayType { .. } => {
let Some(element_info) = Self::indexable_info_from_type(&target_type, module_path)
else {
return Ok(None);
};
let base_address = self.cast_source_memory_address(source_expr)?;
Ok(Some((element_info, base_address)))
}
_ => Ok(None),
}
}
fn indexable_info_from_type(
dwarf_type: &DwarfType,
module_path: Option<PathBuf>,
) -> Option<IndexableElementInfo> {
ghostscope_dwarf::indexable_element_layout(dwarf_type).map(|layout| IndexableElementInfo {
element_type: layout.element_type,
stride: layout.stride,
module_path,
})
}
fn compiled_pointer_value_to_runtime_address(
&mut self,
value: BasicValueEnum<'ctx>,
int_name: &str,
ptr_name: &str,
error_message: &'static str,
) -> Result<RuntimeAddress<'ctx>> {
match value {
BasicValueEnum::IntValue(value) => Ok(RuntimeAddress::available(
self.normalize_int_to_i64(value, int_name)?,
self.context,
)),
BasicValueEnum::PointerValue(value) => self
.builder
.build_ptr_to_int(value, self.context.i64_type(), ptr_name)
.map(|value| RuntimeAddress::available(value, self.context))
.map_err(|err| CodeGenError::Builder(err.to_string())),
_ => Err(CodeGenError::TypeError(error_message.to_string())),
}
}
fn dynamic_lvalue_from_indexable_base(
&mut self,
element_info: IndexableElementInfo,
base_address: RuntimeAddress<'ctx>,
index_value: IntValue<'ctx>,
name: &str,
) -> Result<DynamicLvalue<'ctx>> {
let stride_value = self
.context
.i64_type()
.const_int(element_info.stride, false);
let byte_offset = self
.builder
.build_int_mul(index_value, stride_value, &format!("{name}_byte_offset"))
.map_err(|err| CodeGenError::Builder(err.to_string()))?;
let element_address = self
.builder
.build_int_add(
base_address.value,
byte_offset,
&format!("{name}_element_address"),
)
.map_err(|err| CodeGenError::Builder(err.to_string()))?;
Ok(DynamicLvalue {
address: base_address.with_value(element_address),
type_info: DynamicTypeInfo {
dwarf_type: element_info.element_type,
module_path: element_info.module_path,
},
})
}
fn dynamic_lvalue_from_const_pointer_arithmetic(
&mut self,
expr: &Expr,
) -> Result<Option<DynamicLvalue<'ctx>>> {
let Some((base_expr, index)) = self.pointer_arithmetic_parts_expanding_aliases(expr)?
else {
return Ok(None);
};
let Some((element_info, base_address)) = self.cast_index_base(&base_expr)? else {
return Ok(None);
};
let index_value = self.context.i64_type().const_int(index as u64, true);
self.dynamic_lvalue_from_indexable_base(
element_info,
base_address,
index_value,
"dynamic_cast_ptr_arith",
)
.map(Some)
}
fn compile_cast_integer_value(
&mut self,
expr: &Expr,
target_type: &DwarfType,
) -> Result<IntValue<'ctx>> {
let value = match self.compile_expr(expr)? {
BasicValueEnum::IntValue(value) => value,
BasicValueEnum::PointerValue(value) => self
.builder
.build_ptr_to_int(value, self.context.i64_type(), "cast_int_ptr")
.map_err(|err| CodeGenError::Builder(err.to_string()))?,
_ => {
return Err(CodeGenError::TypeError(
"integer cast source must be an integer or pointer".to_string(),
))
}
};
if Self::is_bool_dwarf_type(target_type) {
let value = self.normalize_int_to_i64(value, "cast_bool_i64")?;
return self
.builder
.build_int_compare(
inkwell::IntPredicate::NE,
value,
self.context.i64_type().const_zero(),
"cast_bool",
)
.map_err(|err| CodeGenError::Builder(err.to_string()));
}
let Some(integer_type) = ghostscope_dwarf::c_integer_comparison_type(target_type) else {
return Err(CodeGenError::TypeError(format!(
"cast target '{}' is not an integer type",
target_type.type_name()
)));
};
let bit_width = integer_type.size.saturating_mul(8).clamp(1, 64) as u32;
let target_int_type = self.context.custom_width_int_type(bit_width);
let current_width = value.get_type().get_bit_width();
let narrowed = if current_width > bit_width {
self.builder
.build_int_truncate(value, target_int_type, "cast_int_trunc")
.map_err(|err| CodeGenError::Builder(err.to_string()))?
} else if current_width < bit_width {
if integer_type.is_unsigned || current_width == 1 {
self.builder
.build_int_z_extend(value, target_int_type, "cast_int_zext")
.map_err(|err| CodeGenError::Builder(err.to_string()))?
} else {
self.builder
.build_int_s_extend(value, target_int_type, "cast_int_sext")
.map_err(|err| CodeGenError::Builder(err.to_string()))?
}
} else {
value
};
if bit_width == 64 {
return Ok(narrowed);
}
if integer_type.is_unsigned {
self.builder
.build_int_z_extend(narrowed, self.context.i64_type(), "cast_int_zext_i64")
.map_err(|err| CodeGenError::Builder(err.to_string()))
} else {
self.builder
.build_int_s_extend(narrowed, self.context.i64_type(), "cast_int_sext_i64")
.map_err(|err| CodeGenError::Builder(err.to_string()))
}
}
fn compile_cast_expr_value(
&mut self,
expr: &Expr,
target_type: &str,
) -> Result<BasicValueEnum<'ctx>> {
let target_type = self.resolve_cast_target_type(target_type)?;
if Self::cast_pointer_target_type(&target_type).is_some() {
let address = self.cast_source_pointer_value(expr)?;
let ptr_ty = self.context.ptr_type(AddressSpace::default());
return self
.builder
.build_int_to_ptr(address.value, ptr_ty, "cast_as_ptr")
.map(|value| value.into())
.map_err(|err| CodeGenError::Builder(err.to_string()));
}
if ghostscope_dwarf::is_c_aggregate_type(&target_type) {
let address = self.cast_source_memory_address(expr)?;
let ptr_ty = self.context.ptr_type(AddressSpace::default());
return self
.builder
.build_int_to_ptr(address.value, ptr_ty, "cast_aggregate_ptr")
.map(|value| value.into())
.map_err(|err| CodeGenError::Builder(err.to_string()));
}
if ghostscope_dwarf::c_integer_comparison_type(&target_type).is_some() {
return self
.compile_cast_integer_value(expr, &target_type)
.map(|value| value.into());
}
if Self::is_float_dwarf_type(&target_type) {
return Err(CodeGenError::TypeError(
"floating-point casts are only supported for memory reads/printing".to_string(),
));
}
Err(CodeGenError::TypeError(format!(
"cast target '{}' is not supported as a value expression",
target_type.type_name()
)))
}
pub(crate) fn integer_literal_value(expr: &Expr) -> Option<i64> {
use crate::script::ast::BinaryOp as BO;
use crate::script::ast::Expr as E;
match expr {
E::Int(value) => Some(*value),
E::BinaryOp {
left,
op: BO::Add,
right,
} => {
Self::integer_literal_value(left)?.checked_add(Self::integer_literal_value(right)?)
}
E::BinaryOp {
left,
op: BO::Subtract,
right,
} => {
Self::integer_literal_value(left)?.checked_sub(Self::integer_literal_value(right)?)
}
E::BinaryOp {
left,
op: BO::Multiply,
right,
} => {
Self::integer_literal_value(left)?.checked_mul(Self::integer_literal_value(right)?)
}
E::BinaryOp {
left,
op: BO::Divide,
right,
} => {
Self::integer_literal_value(left)?.checked_div(Self::integer_literal_value(right)?)
}
E::BinaryOp {
left,
op: BO::Modulo,
right,
} => {
Self::integer_literal_value(left)?.checked_rem(Self::integer_literal_value(right)?)
}
E::BinaryOp {
left,
op: BO::BitAnd,
right,
} => Some(Self::integer_literal_value(left)? & Self::integer_literal_value(right)?),
E::BinaryOp {
left,
op: BO::BitXor,
right,
} => Some(Self::integer_literal_value(left)? ^ Self::integer_literal_value(right)?),
E::BinaryOp {
left,
op: BO::BitOr,
right,
} => Some(Self::integer_literal_value(left)? | Self::integer_literal_value(right)?),
E::BinaryOp {
left,
op: BO::ShiftLeft,
right,
} => {
let shift = u32::try_from(Self::integer_literal_value(right)?).ok()?;
Self::integer_literal_value(left)?.checked_shl(shift)
}
E::BinaryOp {
left,
op: BO::ShiftRight,
right,
} => {
let shift = u32::try_from(Self::integer_literal_value(right)?).ok()?;
Self::integer_literal_value(left)?.checked_shr(shift)
}
E::UnaryBitNot(inner) => Some(!Self::integer_literal_value(inner)?),
_ => None,
}
}
pub(crate) fn pointer_arithmetic_parts(expr: &Expr) -> Option<(&Expr, i64)> {
use crate::script::ast::Expr as E;
fn collect_offset(expr: &Expr, acc: i64) -> Option<(&Expr, i64)> {
use crate::script::ast::BinaryOp as BO;
use crate::script::ast::Expr as E;
match expr {
E::BinaryOp {
left,
op: BO::Add,
right,
} => match (&**left, &**right) {
(ptr_side, int_expr)
if EbpfContext::<'static, 'static>::integer_literal_value(int_expr)
.is_some() =>
{
let index =
EbpfContext::<'static, 'static>::integer_literal_value(int_expr)?;
collect_offset(ptr_side, acc.checked_add(index)?)
}
(int_expr, ptr_side)
if EbpfContext::<'static, 'static>::integer_literal_value(int_expr)
.is_some() =>
{
let index =
EbpfContext::<'static, 'static>::integer_literal_value(int_expr)?;
collect_offset(ptr_side, acc.checked_add(index)?)
}
_ => Some((expr, acc)),
},
E::BinaryOp {
left,
op: BO::Subtract,
right,
} => match &**right {
int_expr
if EbpfContext::<'static, 'static>::integer_literal_value(int_expr)
.is_some() =>
{
let index =
EbpfContext::<'static, 'static>::integer_literal_value(int_expr)?;
collect_offset(left, acc.checked_sub(index)?)
}
_ => Some((expr, acc)),
},
_ => Some((expr, acc)),
}
}
let E::BinaryOp { .. } = expr else {
return None;
};
let (base, index) = collect_offset(expr, 0)?;
match base {
E::BinaryOp { .. } => None,
_ => Some((base, index)),
}
}
pub(crate) fn pointer_arithmetic_parts_expanding_aliases(
&self,
expr: &Expr,
) -> Result<Option<(Expr, i64)>> {
let Some((base, index)) = Self::pointer_arithmetic_parts(expr) else {
return Ok(None);
};
let mut base = base.clone();
let mut index = index;
let mut visited = std::collections::HashSet::new();
loop {
let Expr::Variable(name) = &base else {
break;
};
if !self.alias_variable_exists(name) {
break;
}
if !visited.insert(name.clone()) {
return Err(CodeGenError::TypeError(format!(
"alias cycle detected for '{name}'"
)));
}
let Some(target) = self.get_alias_variable(name) else {
break;
};
if let Some((alias_base, alias_index)) = Self::pointer_arithmetic_parts(&target) {
index = alias_index.checked_add(index).ok_or_else(|| {
CodeGenError::TypeError("pointer arithmetic offset overflow".to_string())
})?;
base = alias_base.clone();
} else {
base = target;
}
}
Ok(Some((base, index)))
}
fn is_dwarf_pointer_or_array_arg(&mut self, expr: &Expr) -> Result<bool> {
let Some(var) = self.query_dwarf_for_complex_expr(expr)? else {
return Ok(false);
};
let Some(ty) = var.dwarf_type.as_ref() else {
return Ok(false);
};
let ty = ghostscope_dwarf::strip_type_aliases(ty);
Ok(matches!(
ty,
DwarfType::PointerType { .. } | DwarfType::ArrayType { .. }
))
}
fn dwarf_integer_comparison_expr(&mut self, expr: &Expr) -> Option<CIntegerComparisonType> {
if let Expr::Cast { target_type, .. } = expr {
return self
.resolve_cast_target_type(target_type)
.ok()
.and_then(|ty| ghostscope_dwarf::c_integer_comparison_type(&ty));
}
if let Ok(Some(var)) = self.query_dwarf_for_complex_expr(expr) {
if let Some(ref ty) = var.dwarf_type {
return ghostscope_dwarf::c_integer_comparison_type(ty);
}
}
None
}
fn integer_comparison_plan_for_exprs(
&mut self,
left: &Expr,
right: &Expr,
) -> Option<CIntegerComparisonPlan> {
let left_ty = self.dwarf_integer_comparison_expr(left);
let right_ty = self.dwarf_integer_comparison_expr(right);
if left_ty.is_none() && right_ty.is_none() {
return None;
}
Some(ghostscope_dwarf::usual_c_arithmetic_comparison_plan(
left_ty.unwrap_or_else(CIntegerComparisonType::signed_i64),
right_ty.unwrap_or_else(CIntegerComparisonType::signed_i64),
))
}
pub(super) fn unsigned_ordering_width_for_exprs(
&mut self,
left: &Expr,
right: &Expr,
) -> Option<u32> {
let plan = self.integer_comparison_plan_for_exprs(left, right)?;
if plan.is_unsigned {
Some((plan.size * 8) as u32)
} else {
None
}
}
pub(super) fn unsigned_shift_width_for_expr(&mut self, expr: &Expr) -> Option<u32> {
let c_type = self.dwarf_integer_comparison_expr(expr)?.promoted();
if c_type.is_unsigned {
Some((c_type.size * 8) as u32)
} else {
None
}
}
fn ensure_dwarf_pointer_arg(&mut self, e: &Expr, where_ctx: &str) -> Result<()> {
if matches!(e, Expr::AddressOf(_)) {
return Ok(());
}
if let Some((ptr_side, _)) = self.pointer_arithmetic_parts_expanding_aliases(e)? {
if matches!(&ptr_side, Expr::AddressOf(_))
|| self
.is_dwarf_pointer_or_array_arg(&ptr_side)
.unwrap_or(false)
{
return Ok(());
}
}
if self.is_dynamic_pointer_arithmetic_expr(e)?
|| self.expands_to_nonliteral_pointer_arithmetic(e)?
{
return Ok(());
}
match self.query_dwarf_for_complex_expr(e) {
Ok(Some(var)) => {
let Some(ty) = var.dwarf_type.as_ref() else {
return Err(CodeGenError::TypeError(format!(
"{where_ctx}: DWARF variable has no type information"
)));
};
let ty = ghostscope_dwarf::strip_type_aliases(ty);
if !matches!(
ty,
DwarfType::PointerType { .. } | DwarfType::ArrayType { .. }
) {
return Err(CodeGenError::TypeError(format!(
"{where_ctx}: only pointer or array DWARF variables are supported"
)));
}
Ok(())
}
Ok(None) | Err(_) => match self.compile_expr(e) {
Ok(BasicValueEnum::PointerValue(_)) => Ok(()),
_ => Err(CodeGenError::TypeError(format!(
"{where_ctx}: expression is not a pointer"
))),
},
}
}
fn is_dynamic_pointer_arithmetic_expr(&mut self, expr: &Expr) -> Result<bool> {
use crate::script::ast::BinaryOp as BO;
use crate::script::ast::Expr as E;
let E::BinaryOp { left, op, right } = expr else {
return Ok(false);
};
match op {
BO::Add => Ok(self.is_dynamic_indexable_pointer_base(left)?
|| self.is_dynamic_indexable_pointer_base(right)?),
BO::Subtract => self.is_dynamic_indexable_pointer_base(left),
_ => Ok(false),
}
}
fn is_dynamic_indexable_pointer_base(&mut self, expr: &Expr) -> Result<bool> {
if matches!(expr, Expr::AddressOf(_)) {
return Ok(true);
}
if self.cast_index_base(expr)?.is_some() {
return Ok(true);
}
if self
.query_dwarf_for_complex_expr(expr)
.ok()
.flatten()
.and_then(|var| var.dwarf_type)
.is_some_and(|ty| ghostscope_dwarf::is_c_pointer_or_array_type(&ty))
{
return Ok(true);
}
let expanded = self.expand_alias_variable_expr(expr)?;
if matches!(expanded, Expr::AddressOf(_)) {
return Ok(true);
}
let Some((base_expr, _static_index)) =
self.pointer_arithmetic_parts_expanding_aliases(&expanded)?
else {
return Ok(false);
};
Ok(self
.query_dwarf_for_complex_expr(&base_expr)
.ok()
.flatten()
.and_then(|var| var.dwarf_type)
.is_some_and(|ty| ghostscope_dwarf::is_c_pointer_or_array_type(&ty)))
}
fn expands_to_nonliteral_pointer_arithmetic(&mut self, expr: &Expr) -> Result<bool> {
let expanded = self.expand_alias_variable_expr(expr)?;
self.is_nonliteral_pointer_arithmetic_expr(&expanded)
}
fn is_nonliteral_pointer_arithmetic_expr(&mut self, expr: &Expr) -> Result<bool> {
use crate::script::ast::BinaryOp as BO;
use crate::script::ast::Expr as E;
let E::BinaryOp { left, op, right } = expr else {
return Ok(false);
};
match op {
BO::Add => {
let left_is_ptr = self.is_dynamic_indexable_pointer_base(left)?;
let right_is_ptr = self.is_dynamic_indexable_pointer_base(right)?;
let left_is_literal = Self::integer_literal_value(left).is_some();
let right_is_literal = Self::integer_literal_value(right).is_some();
if (left_is_ptr && !right_is_ptr && !right_is_literal)
|| (right_is_ptr && !left_is_ptr && !left_is_literal)
{
return Ok(true);
}
Ok(self.expands_to_nonliteral_pointer_arithmetic(left)?
|| self.expands_to_nonliteral_pointer_arithmetic(right)?)
}
BO::Subtract => {
let left_is_ptr = self.is_dynamic_indexable_pointer_base(left)?;
let right_is_literal = Self::integer_literal_value(right).is_some();
if left_is_ptr && !right_is_literal {
return Ok(true);
}
self.expands_to_nonliteral_pointer_arithmetic(left)
}
_ => Ok(false),
}
}
pub(crate) fn resolve_ptr_i64_from_expr(
&mut self,
e: &Expr,
) -> Result<inkwell::values::IntValue<'ctx>> {
self.resolve_runtime_address_from_expr(e)
.map(|address| address.value)
}
pub(crate) fn resolve_runtime_address_from_expr(
&mut self,
e: &Expr,
) -> Result<RuntimeAddress<'ctx>> {
let mut visited = std::collections::HashSet::new();
self.resolve_runtime_address_from_expr_internal(e, &mut visited, 0)
}
fn resolve_runtime_address_from_expr_internal(
&mut self,
e: &Expr,
visited: &mut std::collections::HashSet<String>,
depth: usize,
) -> Result<RuntimeAddress<'ctx>> {
use crate::script::ast::BinaryOp as BO;
use crate::script::ast::Expr as E;
use inkwell::values::BasicValueEnum::*;
const MAX_DEPTH: usize = 64;
if depth > MAX_DEPTH {
return Err(CodeGenError::TypeError(
"alias expansion depth exceeded (cycle?)".into(),
));
}
if let E::Cast { expr, target_type } = e {
let target_type_info = self.resolve_cast_target_type(target_type)?;
if Self::cast_pointer_target_type(&target_type_info).is_some() {
return self.cast_source_pointer_value(expr);
}
return self.cast_source_memory_address(expr);
}
if let E::Variable(name) = e {
if self.alias_variable_exists(name) {
if !visited.insert(name.clone()) {
return Err(CodeGenError::TypeError(format!(
"alias cycle detected for '{name}'"
)));
}
if let Some(target) = self.get_alias_variable(name) {
let r = self.resolve_runtime_address_from_expr_internal(
&target,
visited,
depth + 1,
);
visited.remove(name);
return r;
}
}
}
if let E::AddressOf(inner) = e {
let resolved_inner: &E = if let E::Variable(name) = inner.as_ref() {
if self.alias_variable_exists(name) {
if let Some(target) = self.get_alias_variable(name) {
if let Some(var) = self.query_dwarf_for_complex_expr(&target)? {
let status_ptr = if self.condition_context_active {
Some(self.get_or_create_cond_error_global())
} else {
None
};
let pc_address = self.get_compile_time_context()?.pc_address;
return self.variable_read_plan_to_runtime_address(
&var, pc_address, status_ptr,
);
} else {
return Err(CodeGenError::TypeError(
"cannot take address of unresolved expression".into(),
));
}
} else {
return Err(CodeGenError::TypeError(
"cannot take address of unresolved expression".into(),
));
}
} else {
inner.as_ref()
}
} else {
inner.as_ref()
};
if let E::ArrayAccess(array_expr, index_expr) = resolved_inner {
if let Some(element_lvalue) =
self.compile_dynamic_array_element_address(array_expr, index_expr)?
{
return Ok(element_lvalue.address);
}
}
if let Some(lvalue) = self.dynamic_lvalue_address_and_type(resolved_inner)? {
return Ok(lvalue.address);
}
if let Some(var) = self.query_dwarf_for_complex_expr(resolved_inner)? {
let status_ptr = if self.condition_context_active {
Some(self.get_or_create_cond_error_global())
} else {
None
};
let pc_address = self.get_compile_time_context()?.pc_address;
return self.variable_read_plan_to_runtime_address(&var, pc_address, status_ptr);
} else {
return Err(CodeGenError::TypeError(
"cannot take address of unresolved expression".into(),
));
}
}
if let Some(address) = self.dynamic_pointer_arithmetic_address(e)? {
return Ok(address);
}
if let Some((ptr_side, index)) = self.pointer_arithmetic_parts_expanding_aliases(e)? {
if matches!(&ptr_side, E::AddressOf(_)) {
let base =
self.resolve_runtime_address_from_expr_internal(&ptr_side, visited, depth + 1)?;
let off = self.context.i64_type().const_int(index as u64, false);
let value = self
.builder
.build_int_add(base.value, off, "ptr_add")
.map_err(|err| CodeGenError::Builder(err.to_string()))?;
return Ok(base.with_value(value));
} else if let Some((element_info, base_address)) = self.cast_index_base(&ptr_side)? {
let index_value = self.context.i64_type().const_int(index as u64, true);
return self
.dynamic_lvalue_from_indexable_base(
element_info,
base_address,
index_value,
"cast_ptr_add",
)
.map(|lvalue| lvalue.address);
} else if let Some(var) = self.query_dwarf_for_complex_expr(&ptr_side)? {
if var.dwarf_type.is_some() {
let pointed_plan = var
.plan_pointer_element_index(index)
.map_err(|err| CodeGenError::DwarfError(err.to_string()))?;
let status_ptr = if self.condition_context_active {
Some(self.get_or_create_cond_error_global())
} else {
None
};
let pc_address = self.get_compile_time_context()?.pc_address;
return self.variable_read_plan_to_runtime_address(
&pointed_plan,
pc_address,
status_ptr,
);
}
}
}
if let E::BinaryOp { left, op, right } = e {
if matches!(op, BO::Add) {
if let Some(k) = Self::integer_literal_value(right) {
if let Ok(base) =
self.resolve_runtime_address_from_expr_internal(left, visited, depth + 1)
{
let off = self.context.i64_type().const_int(k as u64, false);
let value = self
.builder
.build_int_add(base.value, off, "ptr_add")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
return Ok(base.with_value(value));
}
}
if let Some(k) = Self::integer_literal_value(left) {
if let Ok(base) =
self.resolve_runtime_address_from_expr_internal(right, visited, depth + 1)
{
let off = self.context.i64_type().const_int(k as u64, false);
let value = self
.builder
.build_int_add(base.value, off, "ptr_add")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
return Ok(base.with_value(value));
}
}
} else if matches!(op, BO::Subtract) {
if let Some(k) = Self::integer_literal_value(right) {
if let Ok(base) =
self.resolve_runtime_address_from_expr_internal(left, visited, depth + 1)
{
let off = self
.context
.i64_type()
.const_int(k.wrapping_neg() as u64, false);
let value = self
.builder
.build_int_add(base.value, off, "ptr_sub")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
return Ok(base.with_value(value));
}
}
}
}
if let Ok(Some(var)) = self.query_dwarf_for_complex_expr(e) {
if let Some(dty) = var.dwarf_type.as_ref() {
let dty = ghostscope_dwarf::strip_type_aliases(dty);
match dty {
DwarfType::PointerType { .. } => {
let pc_address = self.get_compile_time_context()?.pc_address;
let val_any =
self.variable_read_plan_to_llvm_value(&var, pc_address, None)?;
match val_any {
IntValue(iv) => Ok(RuntimeAddress::available(iv, self.context)),
PointerValue(pv) => self
.builder
.build_ptr_to_int(pv, self.context.i64_type(), "ptr_as_i64")
.map(|value| RuntimeAddress::available(value, self.context))
.map_err(|e| CodeGenError::Builder(e.to_string())),
_ => Err(CodeGenError::TypeError(
"DWARF value is not pointer/integer".into(),
)),
}
}
DwarfType::ArrayType { .. } => {
let status_ptr = if self.condition_context_active {
Some(self.get_or_create_cond_error_global())
} else {
None
};
let pc_address = self.get_compile_time_context()?.pc_address;
self.variable_read_plan_to_runtime_address(&var, pc_address, status_ptr)
}
_ => Err(CodeGenError::TypeError(
"DWARF value is not pointer/array".into(),
)),
}
} else {
let status_ptr = if self.condition_context_active {
Some(self.get_or_create_cond_error_global())
} else {
None
};
let pc_address = self.get_compile_time_context()?.pc_address;
self.variable_read_plan_to_runtime_address(&var, pc_address, status_ptr)
}
} else {
Err(CodeGenError::TypeError(
"expression is not a pointer/address".into(),
))
}
}
fn dynamic_pointer_arithmetic_address(
&mut self,
expr: &Expr,
) -> Result<Option<RuntimeAddress<'ctx>>> {
use crate::script::ast::BinaryOp as BO;
use crate::script::ast::Expr as E;
let E::BinaryOp { left, op, right } = expr else {
return Ok(None);
};
match op {
BO::Add => {
if let Some(address) = self.dynamic_raw_address_candidate(left, right, false)? {
return Ok(Some(address));
}
if let Some(address) = self.dynamic_raw_address_candidate(right, left, false)? {
return Ok(Some(address));
}
if let Some(address) = self.dynamic_index_address_candidate(left, right)? {
return Ok(Some(address));
}
self.dynamic_index_address_candidate(right, left)
}
BO::Subtract => {
if let Some(address) = self.dynamic_raw_address_candidate(left, right, true)? {
return Ok(Some(address));
}
let negative_right = E::BinaryOp {
left: Box::new(E::Int(0)),
op: BO::Subtract,
right: right.clone(),
};
self.dynamic_index_address_candidate(left, &negative_right)
}
_ => Ok(None),
}
}
fn dynamic_raw_address_candidate(
&mut self,
base_expr: &Expr,
offset_expr: &Expr,
subtract: bool,
) -> Result<Option<RuntimeAddress<'ctx>>> {
if Self::integer_literal_value(offset_expr).is_some() {
return Ok(None);
}
let expanded_base = self.expand_alias_variable_expr(base_expr)?;
if !matches!(expanded_base, Expr::AddressOf(_)) {
return Ok(None);
}
let base_address = self.resolve_runtime_address_from_expr(&expanded_base)?;
let offset = match self.compile_expr(offset_expr)? {
BasicValueEnum::IntValue(value) => {
self.normalize_int_to_i64(value, "dynamic_raw_offset_i64")?
}
_ => {
return Err(CodeGenError::TypeError(
"raw address offset expression must compile to an integer".to_string(),
))
}
};
let offset = if subtract {
self.builder
.build_int_neg(offset, "dynamic_raw_offset_neg")
.map_err(|err| CodeGenError::Builder(err.to_string()))?
} else {
offset
};
let address = self
.builder
.build_int_add(base_address.value, offset, "dynamic_raw_address")
.map_err(|err| CodeGenError::Builder(err.to_string()))?;
Ok(Some(base_address.with_value(address)))
}
fn dynamic_index_address_candidate(
&mut self,
base_expr: &Expr,
index_expr: &Expr,
) -> Result<Option<RuntimeAddress<'ctx>>> {
match self.compile_dynamic_array_element_address(base_expr, index_expr) {
Ok(Some(element_lvalue)) => Ok(Some(element_lvalue.address)),
Ok(None) => Ok(None),
Err(CodeGenError::VariableNotFound(_))
| Err(CodeGenError::VariableNotInScope(_))
| Err(CodeGenError::TypeError(_)) => Ok(None),
Err(err) => Err(err),
}
}
fn compile_memcmp_builtin(
&mut self,
a_expr: &Expr,
b_expr: &Expr,
len_expr: &Expr,
) -> Result<BasicValueEnum<'ctx>> {
let len_val = self.compile_expr(len_expr)?;
let len_iv = match len_val {
BasicValueEnum::IntValue(iv) => iv,
_ => {
return Err(CodeGenError::TypeError(
"memcmp length must be an integer expression".into(),
))
}
};
let i32_ty = self.context.i32_type();
let len_i32 = if len_iv.get_type().get_bit_width() > 32 {
self.builder
.build_int_truncate(len_iv, i32_ty, "memcmp_len_trunc")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
} else if len_iv.get_type().get_bit_width() < 32 {
self.builder
.build_int_z_extend(len_iv, i32_ty, "memcmp_len_zext")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
} else {
len_iv
};
let zero_i32 = i32_ty.const_zero();
let is_neg = self
.builder
.build_int_compare(
inkwell::IntPredicate::SLT,
len_i32,
zero_i32,
"memcmp_len_neg",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let len_nn = self
.builder
.build_select(is_neg, zero_i32, len_i32, "memcmp_len_nn")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_int_value();
let cap = self.compile_options.compare_cap;
let cap_const = i32_ty.const_int(cap as u64, false);
let gt = self
.builder
.build_int_compare(
inkwell::IntPredicate::UGT,
len_nn,
cap_const,
"memcmp_len_gt",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let sel_len = self
.builder
.build_select(gt, cap_const, len_nn, "memcmp_len_sel")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_int_value();
let len_is_zero = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
sel_len,
i32_ty.const_zero(),
"memcmp_len_is_zero",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let func = self.current_function("compile memcmp length branch")?;
let zero_b = self.context.append_basic_block(func, "memcmp_len_zero");
let nz_b = self.context.append_basic_block(func, "memcmp_len_nz");
let cont_b = self.context.append_basic_block(func, "memcmp_len_cont");
self.builder
.build_conditional_branch(len_is_zero, zero_b, nz_b)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder.position_at_end(zero_b);
let bool_true = self.context.bool_type().const_int(1, false);
self.builder
.build_unconditional_branch(cont_b)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let zero_block = self.current_insert_block("finish memcmp zero-length block")?;
self.builder.position_at_end(nz_b);
let (arr_a_ty, buf_a) = self.get_or_create_i8_buffer(cap, "_gs_bi_memcmp_a");
let (arr_b_ty, buf_b) = self.get_or_create_i8_buffer(cap, "_gs_bi_memcmp_b");
let ptr_ty = self.context.ptr_type(AddressSpace::default());
let parse_hex_bytes = |e: &Expr| -> Option<Vec<u8>> {
if let Expr::BuiltinCall { name, args } = e {
if name == "hex" && args.len() == 1 {
if let Expr::String(s) = &args[0] {
if s.is_empty() {
return Some(Vec::new());
}
let mut out = Vec::with_capacity(s.len() / 2);
let mut i = 0usize;
while i + 1 < s.len() {
let v = u8::from_str_radix(&s[i..i + 2], 16).ok()?;
out.push(v);
i += 2;
}
return Some(out);
}
}
}
None
};
if parse_hex_bytes(a_expr).is_none() {
self.ensure_dwarf_pointer_arg(a_expr, "memcmp arg0")?;
}
let ok_a = if let Some(bytes) = parse_hex_bytes(a_expr) {
let i32_ty = self.context.i32_type();
let idx0 = i32_ty.const_zero();
for i in 0..(cap as usize) {
let idx_i = i32_ty.const_int(i as u64, false);
let pa = unsafe {
self.builder
.build_gep(arr_a_ty, buf_a, &[idx0, idx_i], &format!("hex_a_i{i}"))
.map_err(|e| CodeGenError::Builder(e.to_string()))?
};
let byte = if i < bytes.len() { bytes[i] } else { 0 } as u64;
let bv = self.context.i8_type().const_int(byte, false);
self.builder
.build_store(pa, bv)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
}
self.context.bool_type().const_int(1, false)
} else {
let ptr_a = self.resolve_runtime_address_from_expr(a_expr)?;
let offsets_found_a = ptr_a.offsets_found;
let dst_a = self
.builder
.build_bit_cast(buf_a, ptr_ty, "memcmp_dst_a")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let base_src_a = self
.builder
.build_int_to_ptr(ptr_a.value, ptr_ty, "memcmp_src_a")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let null_ptr = ptr_ty.const_null();
let src_a = self
.builder
.build_select::<BasicValueEnum<'ctx>, _>(
offsets_found_a,
base_src_a.into(),
null_ptr.into(),
"memcmp_src_a_or_null",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_pointer_value();
let zero_i32 = self.context.i32_type().const_zero();
let effective_len_a = self
.builder
.build_select::<BasicValueEnum<'ctx>, _>(
offsets_found_a,
sel_len.into(),
zero_i32.into(),
"memcmp_len_a_or_zero",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_int_value();
let ret_a = self
.create_bpf_helper_call(
BPF_FUNC_probe_read_user as u64,
&[dst_a, effective_len_a.into(), src_a.into()],
self.context.i64_type().into(),
"probe_read_user_memcmp_a",
)?
.into_int_value();
let i64_ty = self.context.i64_type();
let eq_a = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
ret_a,
i64_ty.const_zero(),
"memcmp_ok_a",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder
.build_and(eq_a, offsets_found_a, "memcmp_ok_a")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
};
if parse_hex_bytes(b_expr).is_none() {
self.ensure_dwarf_pointer_arg(b_expr, "memcmp arg1")?;
}
let ok_b = if let Some(bytes) = parse_hex_bytes(b_expr) {
let i32_ty = self.context.i32_type();
let idx0 = i32_ty.const_zero();
for i in 0..(cap as usize) {
let idx_i = i32_ty.const_int(i as u64, false);
let pb = unsafe {
self.builder
.build_gep(arr_b_ty, buf_b, &[idx0, idx_i], &format!("hex_b_i{i}"))
.map_err(|e| CodeGenError::Builder(e.to_string()))?
};
let byte = if i < bytes.len() { bytes[i] } else { 0 } as u64;
let bv = self.context.i8_type().const_int(byte, false);
self.builder
.build_store(pb, bv)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
}
self.context.bool_type().const_int(1, false)
} else {
let ptr_b = self.resolve_runtime_address_from_expr(b_expr)?;
let offsets_found_b = ptr_b.offsets_found;
let dst_b = self
.builder
.build_bit_cast(buf_b, ptr_ty, "memcmp_dst_b")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let base_src_b = self
.builder
.build_int_to_ptr(ptr_b.value, ptr_ty, "memcmp_src_b")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let null_ptr = ptr_ty.const_null();
let src_b = self
.builder
.build_select::<BasicValueEnum<'ctx>, _>(
offsets_found_b,
base_src_b.into(),
null_ptr.into(),
"memcmp_src_b_or_null",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_pointer_value();
let zero_i32 = self.context.i32_type().const_zero();
let effective_len_b = self
.builder
.build_select::<BasicValueEnum<'ctx>, _>(
offsets_found_b,
sel_len.into(),
zero_i32.into(),
"memcmp_len_b_or_zero",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_int_value();
let ret_b = self
.create_bpf_helper_call(
BPF_FUNC_probe_read_user as u64,
&[dst_b, effective_len_b.into(), src_b.into()],
self.context.i64_type().into(),
"probe_read_user_memcmp_b",
)?
.into_int_value();
let i64_ty = self.context.i64_type();
let eq_b = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
ret_b,
i64_ty.const_zero(),
"memcmp_ok_b",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder
.build_and(eq_b, offsets_found_b, "memcmp_ok_b")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
};
let status_ok = self
.builder
.build_and(ok_a, ok_b, "memcmp_status_ok")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
if self.condition_context_active {
let not_a = self
.builder
.build_not(ok_a, "memcmp_fail_a")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let not_b = self
.builder
.build_not(ok_b, "memcmp_fail_b")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let any_fail = self
.builder
.build_or(not_a, not_b, "memcmp_any_fail")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let func = self.current_function("compile memcmp condition error branch")?;
let set_b = self.context.append_basic_block(func, "memcmp_set_err");
let cont_b = self.context.append_basic_block(func, "memcmp_cont");
self.builder
.build_conditional_branch(any_fail, set_b, cont_b)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder.position_at_end(set_b);
let _ = self.set_condition_error_if_unset(2u8);
let not_a_val = self
.builder
.build_not(ok_a, "memcmp_fail_a_val")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let not_b_val = self
.builder
.build_not(ok_b, "memcmp_fail_b_val")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let cur_fn = self.current_function("compile memcmp failure address branch")?;
let set_a_bb = self.context.append_basic_block(cur_fn, "set_addr_a");
let check_b_bb = self.context.append_basic_block(cur_fn, "check_fail_b");
let set_b_bb = self.context.append_basic_block(cur_fn, "set_addr_b");
let after_set_bb = self.context.append_basic_block(cur_fn, "after_set_addr");
self.builder
.build_conditional_branch(not_a_val, set_a_bb, check_b_bb)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder.position_at_end(set_a_bb);
if let Some(pa) = match parse_hex_bytes(a_expr) {
Some(_) => None,
None => Some(self.resolve_ptr_i64_from_expr(a_expr)?),
} {
let _ = self.set_condition_error_addr_if_unset(pa);
}
self.builder
.build_unconditional_branch(after_set_bb)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder.position_at_end(check_b_bb);
self.builder
.build_conditional_branch(not_b_val, set_b_bb, after_set_bb)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder.position_at_end(set_b_bb);
if let Some(pb) = match parse_hex_bytes(b_expr) {
Some(_) => None,
None => Some(self.resolve_ptr_i64_from_expr(b_expr)?),
} {
let _ = self.set_condition_error_addr_if_unset(pb);
}
self.builder
.build_unconditional_branch(after_set_bb)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder.position_at_end(after_set_bb);
let i8t = self.context.i8_type();
let b_a = self
.builder
.build_int_z_extend(
self.builder
.build_not(ok_a, "fa")
.map_err(|e| CodeGenError::Builder(e.to_string()))?,
i8t,
"fa8",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let b_b1 = self
.builder
.build_int_z_extend(
self.builder
.build_not(ok_b, "fb")
.map_err(|e| CodeGenError::Builder(e.to_string()))?,
i8t,
"fb8",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let sh1 = self
.builder
.build_left_shift(b_b1, i8t.const_int(1, false), "b_b_shift")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let b_c = self
.builder
.build_int_z_extend(gt, i8t, "clamped8")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let sh2 = self
.builder
.build_left_shift(b_c, i8t.const_int(2, false), "b_c_shift")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let b_z = self
.builder
.build_int_z_extend(len_is_zero, i8t, "len0_8")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let sh3 = self
.builder
.build_left_shift(b_z, i8t.const_int(3, false), "b_z_shift")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let f01 = self
.builder
.build_or(b_a, sh1, "f01")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let f012 = self
.builder
.build_or(f01, sh2, "f012")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let flags = self
.builder
.build_or(f012, sh3, "flags")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let _ = self.or_condition_error_flags(flags);
self.builder
.build_unconditional_branch(cont_b)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder.position_at_end(cont_b);
}
let i32_ty = self.context.i32_type();
let idx0 = i32_ty.const_zero();
let mut acc = self.context.i8_type().const_zero();
for i in 0..cap as usize {
let idx_i = i32_ty.const_int(i as u64, false);
let active = self
.builder
.build_int_compare(
inkwell::IntPredicate::ULT,
idx_i,
sel_len,
&format!("memcmp_i{i}_active"),
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let pa = unsafe {
self.builder
.build_gep(arr_a_ty, buf_a, &[idx0, idx_i], &format!("memcmp_a_i{i}"))
.map_err(|e| CodeGenError::Builder(e.to_string()))?
};
let va = self
.builder
.build_load(self.context.i8_type(), pa, &format!("ld_a_{i}"))
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let va = match va {
BasicValueEnum::IntValue(iv) => iv,
_ => return Err(CodeGenError::LLVMError("memcmp load a != i8".into())),
};
let pb = unsafe {
self.builder
.build_gep(arr_b_ty, buf_b, &[idx0, idx_i], &format!("memcmp_b_i{i}"))
.map_err(|e| CodeGenError::Builder(e.to_string()))?
};
let vb = self
.builder
.build_load(self.context.i8_type(), pb, &format!("ld_b_{i}"))
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let vb = match vb {
BasicValueEnum::IntValue(iv) => iv,
_ => return Err(CodeGenError::LLVMError("memcmp load b != i8".into())),
};
let diff = self
.builder
.build_xor(va, vb, &format!("memcmp_diff_{i}"))
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let zero8 = self.context.i8_type().const_zero();
let masked = self
.builder
.build_select(active, diff, zero8, &format!("memcmp_masked_{i}"))
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_int_value();
acc = self
.builder
.build_or(acc, masked, &format!("memcmp_acc_{i}"))
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
}
let eq_bytes = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
acc,
self.context.i8_type().const_zero(),
"memcmp_acc_zero",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let nz_result = self
.builder
.build_and(status_ok, eq_bytes, "memcmp_and")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder
.build_unconditional_branch(cont_b)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let nz_block = self.current_insert_block("finish memcmp non-zero block")?;
self.builder.position_at_end(cont_b);
let phi = self
.builder
.build_phi(self.context.bool_type(), "memcmp_phi")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
phi.add_incoming(&[(&bool_true, zero_block), (&nz_result, nz_block)]);
Ok(phi.as_basic_value())
}
fn compile_bounded_compare_len_i32(
&mut self,
len_expr: &Expr,
max_len: u32,
name_prefix: &str,
) -> Result<(IntValue<'ctx>, IntValue<'ctx>)> {
let len_val = self.compile_expr(len_expr)?;
let len_iv = match len_val {
BasicValueEnum::IntValue(iv) => iv,
_ => {
return Err(CodeGenError::TypeError(format!(
"{name_prefix} length must be an integer expression"
)))
}
};
let i32_ty = self.context.i32_type();
let len_i32 = if len_iv.get_type().get_bit_width() > 32 {
self.builder
.build_int_truncate(len_iv, i32_ty, &format!("{name_prefix}_len_trunc"))
.map_err(|e| CodeGenError::Builder(e.to_string()))?
} else if len_iv.get_type().get_bit_width() < 32 {
self.builder
.build_int_z_extend(len_iv, i32_ty, &format!("{name_prefix}_len_zext"))
.map_err(|e| CodeGenError::Builder(e.to_string()))?
} else {
len_iv
};
let zero_i32 = i32_ty.const_zero();
let is_neg = self
.builder
.build_int_compare(
inkwell::IntPredicate::SLT,
len_i32,
zero_i32,
&format!("{name_prefix}_len_neg"),
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let len_nn = self
.builder
.build_select(is_neg, zero_i32, len_i32, &format!("{name_prefix}_len_nn"))
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_int_value();
let max_const = i32_ty.const_int(max_len as u64, false);
let gt = self
.builder
.build_int_compare(
inkwell::IntPredicate::UGT,
len_nn,
max_const,
&format!("{name_prefix}_len_gt"),
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let bounded_len = self
.builder
.build_select(gt, max_const, len_nn, &format!("{name_prefix}_len_sel"))
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_int_value();
let is_zero = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
bounded_len,
zero_i32,
&format!("{name_prefix}_len_is_zero"),
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok((bounded_len, is_zero))
}
fn compile_strncmp_builtin(
&mut self,
dwarf_expr: &Expr,
lit: &str,
n_expr: &Expr,
) -> Result<BasicValueEnum<'ctx>> {
let immediate_bytes_opt = match dwarf_expr {
Expr::Variable(name) => {
if self
.get_variable_type(name)
.is_some_and(|t| matches!(t, crate::script::VarType::String))
{
self.get_string_variable_bytes(name).cloned()
} else {
None
}
}
Expr::String(s) => {
let mut b = s.as_bytes().to_vec();
b.push(0);
Some(b)
}
_ => None,
};
if let Some(bytes) = immediate_bytes_opt {
if let Expr::Int(n) = n_expr {
let n_usize = std::cmp::min(
(*n).max(0) as usize,
self.compile_options.compare_cap as usize,
);
let content_len = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
let cmp_len = std::cmp::min(n_usize, std::cmp::min(content_len, lit.len()));
let equal = bytes.get(0..cmp_len).unwrap_or(&[])
== lit.as_bytes().get(0..cmp_len).unwrap_or(&[]);
let bool_val = self
.context
.bool_type()
.const_int(if equal { 1 } else { 0 }, false);
return Ok(bool_val.into());
}
let cap = self.compile_options.compare_cap as usize;
let content_len = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
let cmp_bound = std::cmp::min(cap, std::cmp::min(content_len, lit.len())) as u32;
if cmp_bound == 0 {
return Ok(self.context.bool_type().const_int(1, false).into());
}
let (bounded_len, _len_is_zero) =
self.compile_bounded_compare_len_i32(n_expr, cmp_bound, "strncmp")?;
let i32_ty = self.context.i32_type();
let i8_ty = self.context.i8_type();
let mut acc = i8_ty.const_zero();
for (i, (byte, lit_byte)) in bytes
.iter()
.copied()
.zip(lit.as_bytes().iter().copied())
.take(cmp_bound as usize)
.enumerate()
{
let active = self
.builder
.build_int_compare(
inkwell::IntPredicate::UGT,
bounded_len,
i32_ty.const_int(i as u64, false),
"strncmp_imm_active",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let diff = i8_ty.const_int((byte ^ lit_byte) as u64, false);
let active_diff = self
.builder
.build_select(active, diff, i8_ty.const_zero(), "strncmp_imm_diff")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_int_value();
acc = self
.builder
.build_or(acc, active_diff, "strncmp_imm_acc")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
}
let equal = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
acc,
i8_ty.const_zero(),
"strncmp_imm_eq",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
return Ok(equal.into());
}
let ptr_i64 = match self.query_dwarf_for_complex_expr(dwarf_expr)? {
Some(var) => {
if let Some(ty) = var.dwarf_type.as_ref() {
let ty = ghostscope_dwarf::strip_type_aliases(ty);
match ty {
DwarfType::PointerType { .. } => {
let pc_address = self.get_compile_time_context()?.pc_address;
let val_any =
self.variable_read_plan_to_llvm_value(&var, pc_address, None)?;
match val_any {
BasicValueEnum::IntValue(iv) => {
RuntimeAddress::available(iv, self.context)
}
BasicValueEnum::PointerValue(pv) => self
.builder
.build_ptr_to_int(pv, self.context.i64_type(), "ptr_as_i64")
.map(|value| RuntimeAddress::available(value, self.context))
.map_err(|e| CodeGenError::Builder(e.to_string()))?,
_ => {
return Err(CodeGenError::TypeError(
"strncmp requires pointer/integer value for pointer; got unsupported DWARF value".into(),
))
}
}
}
DwarfType::ArrayType { .. } => {
let status_ptr = if self.condition_context_active {
Some(self.get_or_create_cond_error_global())
} else {
None
};
let pc_address = self.get_compile_time_context()?.pc_address;
self.variable_read_plan_to_runtime_address(
&var, pc_address, status_ptr,
)?
}
_ => {
return Err(CodeGenError::TypeError(
"strncmp requires the non-string side to be an address expression (pointer/array)".into(),
));
}
}
} else {
return Err(CodeGenError::TypeError(
"strncmp non-string side lacks DWARF type info".into(),
));
}
}
None => {
self.resolve_runtime_address_from_expr(dwarf_expr).map_err(|_| {
CodeGenError::TypeError(
"strncmp requires at least one string argument, and the other side must be an address expression (DWARF pointer/array or alias)".to_string(),
)
})?
}
};
let cap = self.compile_options.compare_cap;
let cmp_bound = std::cmp::min(lit.len() as u32, cap);
if cmp_bound == 0 {
return Ok(self.context.bool_type().const_int(1, false).into());
}
let (bounded_len, len_is_zero) =
self.compile_bounded_compare_len_i32(n_expr, cmp_bound, "strncmp")?;
let func = self.current_function("compile strncmp length branch")?;
let zero_b = self.context.append_basic_block(func, "strncmp_len_zero");
let nz_b = self.context.append_basic_block(func, "strncmp_len_nz");
let final_b = self.context.append_basic_block(func, "strncmp_len_cont");
self.builder
.build_conditional_branch(len_is_zero, zero_b, nz_b)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder.position_at_end(zero_b);
let bool_true = self.context.bool_type().const_int(1, false);
self.builder
.build_unconditional_branch(final_b)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let zero_block = self.current_insert_block("finish strncmp zero-length block")?;
self.builder.position_at_end(nz_b);
let (arr_ty, buf_global) = self.get_or_create_i8_buffer(cmp_bound, "_gs_bi_strncmp");
let ptr_ty = self.context.ptr_type(AddressSpace::default());
let dst_ptr = self
.builder
.build_bit_cast(buf_global, ptr_ty, "strncmp_dst_ptr")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let base_src_ptr = self
.builder
.build_int_to_ptr(ptr_i64.value, ptr_ty, "strncmp_src_ptr")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let src_ptr = self
.builder
.build_select::<BasicValueEnum<'ctx>, _>(
ptr_i64.offsets_found,
base_src_ptr.into(),
ptr_ty.const_null().into(),
"strncmp_src_or_null",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_pointer_value();
let effective_len = self
.builder
.build_select::<BasicValueEnum<'ctx>, _>(
ptr_i64.offsets_found,
bounded_len.into(),
self.context.i32_type().const_zero().into(),
"strncmp_len_or_zero",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_int_value();
let ret = self
.create_bpf_helper_call(
BPF_FUNC_probe_read_user as u64,
&[dst_ptr, effective_len.into(), src_ptr.into()],
self.context.i64_type().into(),
"probe_read_user_strncmp",
)?
.into_int_value();
let read_ok = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
ret,
self.context.i64_type().const_zero(),
"rd_ok",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let status_ok = self
.builder
.build_and(read_ok, ptr_i64.offsets_found, "strncmp_ok_with_offsets")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
if self.condition_context_active {
let func = self.current_function("compile strncmp condition error branch")?;
let set_b = self.context.append_basic_block(func, "strncmp_set_err");
let cont_b = self.context.append_basic_block(func, "strncmp_cont");
let not_ok = self
.builder
.build_not(status_ok, "rd_fail")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder
.build_conditional_branch(not_ok, set_b, cont_b)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder.position_at_end(set_b);
let _ = self.set_condition_error_if_unset(2u8);
let _ = self.set_condition_error_addr_if_unset(ptr_i64.value);
let one = self.context.i8_type().const_int(1, false);
let _ = self.or_condition_error_flags(one);
self.builder
.build_unconditional_branch(cont_b)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder.position_at_end(cont_b);
}
let i32_ty = self.context.i32_type();
let idx0 = i32_ty.const_zero();
let mut acc = self.context.i8_type().const_zero();
for (i, b) in lit.as_bytes().iter().take(cmp_bound as usize).enumerate() {
let idx_i = i32_ty.const_int(i as u64, false);
let ptr_i = unsafe {
self.builder
.build_gep(arr_ty, buf_global, &[idx0, idx_i], "ch_ptr")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
};
let ch = self
.builder
.build_load(self.context.i8_type(), ptr_i, "ch")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let ch = match ch {
BasicValueEnum::IntValue(iv) => iv,
_ => return Err(CodeGenError::LLVMError("load did not return i8".into())),
};
let expect = self.context.i8_type().const_int(*b as u64, false);
let diff = self
.builder
.build_xor(ch, expect, "diff")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let active = self
.builder
.build_int_compare(
inkwell::IntPredicate::UGT,
bounded_len,
idx_i,
"strncmp_byte_active",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let diff = self
.builder
.build_select(
active,
diff,
self.context.i8_type().const_zero(),
"strncmp_active_diff",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_int_value();
acc = self
.builder
.build_or(acc, diff, "acc_or")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
}
let eq_bytes = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
acc,
self.context.i8_type().const_zero(),
"acc_zero",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let result = self
.builder
.build_and(status_ok, eq_bytes, "strncmp_and")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder
.build_unconditional_branch(final_b)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let nz_block = self.current_insert_block("finish strncmp non-zero block")?;
self.builder.position_at_end(final_b);
let result_phi = self
.builder
.build_phi(self.context.bool_type(), "strncmp_result")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
result_phi.add_incoming(&[(&bool_true, zero_block), (&result, nz_block)]);
Ok(result_phi.as_basic_value())
}
pub fn compile_expr(&mut self, expr: &Expr) -> Result<BasicValueEnum<'ctx>> {
match expr {
Expr::Int(value) => {
let int_value = self.context.i64_type().const_int(*value as u64, true);
debug!(
"compile_expr: Int literal {} compiled to IntValue with bit width {}",
value,
int_value.get_type().get_bit_width()
);
Ok(int_value.into())
}
Expr::Float(_value) => Err(CodeGenError::TypeError(
"Floating point expressions are not supported".to_string(),
)),
Expr::String(value) => {
let string_value = self.context.const_string(value.as_bytes(), true);
let global = self
.module
.add_global(string_value.get_type(), None, "str_const");
global.set_initializer(&string_value);
let ptr_type = self.context.ptr_type(AddressSpace::default());
let cast_ptr = self
.builder
.build_bit_cast(global.as_pointer_value(), ptr_type, "str_ptr")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok(cast_ptr)
}
Expr::Bool(value) => {
let b = self
.context
.bool_type()
.const_int(if *value { 1 } else { 0 }, false);
Ok(b.into())
}
Expr::UnaryNot(inner) => {
let v = self.compile_expr(inner)?;
let iv = match v {
BasicValueEnum::IntValue(iv) => iv,
_ => {
return Err(CodeGenError::TypeError(
"Logical NOT requires integer/boolean operand".to_string(),
))
}
};
let zero = iv.get_type().const_zero();
let res = self
.builder
.build_int_compare(inkwell::IntPredicate::EQ, iv, zero, "not_eq0")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok(res.into())
}
Expr::UnaryBitNot(inner) => {
let unsigned_width = self
.dwarf_integer_comparison_expr(inner)
.map(CIntegerComparisonType::promoted)
.and_then(|integer_type| {
integer_type
.is_unsigned
.then_some((integer_type.size * 8) as u32)
});
let v = self.compile_expr(inner)?;
let iv = match v {
BasicValueEnum::IntValue(iv) => iv,
_ => {
return Err(CodeGenError::TypeError(
"Bitwise NOT requires integer/boolean operand".to_string(),
))
}
};
if let Some(bit_width) = unsigned_width {
let iv =
self.normalize_int_for_unsigned_compare(iv, bit_width, "bitnot_unsigned")?;
let result = self
.builder
.build_xor(iv, iv.get_type().const_all_ones(), "bitnot")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
return self
.zero_extend_int_to_i64_if_needed(result, "bitnot_zext_i64")
.map(|value| value.into());
}
let iv = if iv.get_type().get_bit_width() == 1 {
self.builder
.build_int_z_extend(iv, self.context.i64_type(), "bitnot_bool_i64")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
} else {
iv
};
let all_ones = iv.get_type().const_all_ones();
let result = self
.builder
.build_xor(iv, all_ones, "bitnot")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok(result.into())
}
Expr::Variable(var_name) => {
debug!("compile_expr: Compiling variable expression: {}", var_name);
if self.alias_variable_exists(var_name) {
debug!(
"compile_expr: '{}' is an alias variable; resolving to runtime address",
var_name
);
let aliased = self
.get_alias_variable(var_name)
.expect("alias existence just checked");
let addr_i64 = self.resolve_ptr_i64_from_expr(&aliased)?;
let ptr_ty = self.context.ptr_type(AddressSpace::default());
let as_ptr = self
.builder
.build_int_to_ptr(addr_i64, ptr_ty, "alias_as_ptr")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
return Ok(as_ptr.into());
}
if self.variable_exists(var_name) {
debug!("compile_expr: Found script variable: {}", var_name);
let loaded_value = self.load_variable(var_name)?;
debug!(
"compile_expr: Loaded variable '{}' with type: {:?}",
var_name,
loaded_value.get_type()
);
match &loaded_value {
BasicValueEnum::IntValue(iv) => debug!(
"compile_expr: Variable '{}' is IntValue with bit width {}",
var_name,
iv.get_type().get_bit_width()
),
BasicValueEnum::FloatValue(_) => {
debug!("compile_expr: Variable '{}' is FloatValue", var_name)
}
BasicValueEnum::PointerValue(_) => {
debug!("compile_expr: Variable '{}' is PointerValue", var_name)
}
_ => debug!("compile_expr: Variable '{}' is other type", var_name),
}
return Ok(loaded_value);
}
debug!(
"Variable '{}' not found in script variables, checking DWARF",
var_name
);
match self.query_dwarf_for_variable(var_name) {
Ok(Some(_)) => self.compile_dwarf_expression(expr),
Ok(None) => Err(CodeGenError::VariableNotInScope(var_name.clone())),
Err(e) => Err(CodeGenError::DwarfError(e.to_string())),
}
}
Expr::SpecialVar(name) => {
let sanitized = name.trim_start_matches('$');
self.handle_special_variable(sanitized)
}
Expr::BuiltinCall { name, args } => match self.plan_builtin_call(name, args)? {
BuiltinCallPlan::Memcmp => {
self.compile_memcmp_builtin(&args[0], &args[1], &args[2])
}
BuiltinCallPlan::Strncmp => {
fn extract_script_string(
this: &mut EbpfContext<'_, '_>,
e: &Expr,
) -> Option<String> {
match e {
Expr::String(s) => Some(s.clone()),
Expr::Variable(name) => this
.get_variable_type(name)
.is_some_and(|t| matches!(t, crate::script::VarType::String))
.then(|| {
this.get_string_variable_bytes(name).map(|b| {
let cut = b.iter().position(|&x| x == 0).unwrap_or(b.len());
String::from_utf8_lossy(&b[..cut]).to_string()
})
})
.flatten(),
_ => None,
}
}
let left_str = extract_script_string(self, &args[0]);
let right_str = extract_script_string(self, &args[1]);
match (left_str, right_str) {
(Some(ls), Some(rs)) => {
let left_expr = Expr::String(ls);
self.compile_strncmp_builtin(&left_expr, &rs, &args[2])
}
(Some(ls), None) => self.compile_strncmp_builtin(&args[1], &ls, &args[2]),
(None, Some(rs)) => self.compile_strncmp_builtin(&args[0], &rs, &args[2]),
(None, None) => Err(CodeGenError::TypeError(
"strncmp requires at least one string argument (string literal or script string variable) as the first or second parameter".into(),
)),
}
}
BuiltinCallPlan::StartsWith => {
fn extract_script_string(
this: &mut EbpfContext<'_, '_>,
e: &Expr,
) -> Option<String> {
match e {
Expr::String(s) => Some(s.clone()),
Expr::Variable(name) => this
.get_variable_type(name)
.is_some_and(|t| matches!(t, crate::script::VarType::String))
.then(|| {
this.get_string_variable_bytes(name).map(|b| {
let cut = b.iter().position(|&x| x == 0).unwrap_or(b.len());
String::from_utf8_lossy(&b[..cut]).to_string()
})
})
.flatten(),
_ => None,
}
}
let s0 = extract_script_string(self, &args[0]);
let s1 = extract_script_string(self, &args[1]);
match (s0, s1) {
(Some(a), Some(b)) => {
let ok = a.as_bytes().starts_with(b.as_bytes());
let bv = self.context.bool_type().const_int(ok as u64, false);
Ok(bv.into())
}
(Some(a), None) => {
let n_expr = Expr::Int(a.len() as i64);
self.compile_strncmp_builtin(&args[1], &a, &n_expr)
}
(None, Some(b)) => {
let n_expr = Expr::Int(b.len() as i64);
self.compile_strncmp_builtin(&args[0], &b, &n_expr)
}
(None, None) => Err(CodeGenError::TypeError(
"starts_with requires at least one string argument (string literal or script string variable) as the first or second parameter".into(),
)),
}
}
},
Expr::BinaryOp { left, op, right } => {
let binary_plan = self.plan_binary_expr(left, op, right)?;
if let BinaryEmitKind::StringComparison(string_plan) = &binary_plan.emit_kind {
let other = if string_plan.literal_on_left {
right.as_ref()
} else {
left.as_ref()
};
return self.compile_string_comparison(
other,
&string_plan.literal,
string_plan.equal,
);
}
if matches!(&binary_plan.emit_kind, BinaryEmitKind::LogicalOr) {
let lhs_val = self.compile_expr(left)?;
let lhs_int = match lhs_val {
BasicValueEnum::IntValue(iv) => iv,
BasicValueEnum::PointerValue(pv) => self
.builder
.build_ptr_to_int(pv, self.context.i64_type(), "lor_lhs_ptr_as_i64")
.map_err(|e| CodeGenError::Builder(e.to_string()))?,
_ => {
return Err(CodeGenError::TypeError(
"Logical OR requires integer or pointer operands".to_string(),
))
}
};
let lhs_zero = lhs_int.get_type().const_zero();
let lhs_bool = self
.builder
.build_int_compare(
inkwell::IntPredicate::NE,
lhs_int,
lhs_zero,
"lor_lhs_nz",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let curr_block = self.builder.get_insert_block().ok_or_else(|| {
CodeGenError::LLVMError("No current basic block".to_string())
})?;
let func = curr_block
.get_parent()
.ok_or_else(|| CodeGenError::LLVMError("No parent function".to_string()))?;
let rhs_block = self.context.append_basic_block(func, "lor_rhs");
let merge_block = self.context.append_basic_block(func, "lor_merge");
self.builder
.build_conditional_branch(lhs_bool, merge_block, rhs_block)
.map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
self.builder.position_at_end(rhs_block);
let rhs_val = self.compile_expr(right)?;
let rhs_int = match rhs_val {
BasicValueEnum::IntValue(iv) => iv,
BasicValueEnum::PointerValue(pv) => self
.builder
.build_ptr_to_int(pv, self.context.i64_type(), "lor_rhs_ptr_as_i64")
.map_err(|e| CodeGenError::Builder(e.to_string()))?,
_ => {
return Err(CodeGenError::TypeError(
"Logical OR requires integer or pointer operands".to_string(),
))
}
};
let rhs_zero = rhs_int.get_type().const_zero();
let rhs_bool = self
.builder
.build_int_compare(
inkwell::IntPredicate::NE,
rhs_int,
rhs_zero,
"lor_rhs_nz",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let rhs_end_block = self.builder.get_insert_block().ok_or_else(|| {
CodeGenError::LLVMError("No current basic block after RHS".to_string())
})?;
self.builder
.build_unconditional_branch(merge_block)
.map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
self.builder.position_at_end(merge_block);
let i1 = self.context.bool_type();
let phi = self
.builder
.build_phi(i1, "lor_phi")
.map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
let one = i1.const_int(1, false);
phi.add_incoming(&[(&one, curr_block), (&rhs_bool, rhs_end_block)]);
return Ok(phi.as_basic_value());
} else if matches!(&binary_plan.emit_kind, BinaryEmitKind::LogicalAnd) {
let lhs_val = self.compile_expr(left)?;
let lhs_int = match lhs_val {
BasicValueEnum::IntValue(iv) => iv,
BasicValueEnum::PointerValue(pv) => self
.builder
.build_ptr_to_int(pv, self.context.i64_type(), "land_lhs_ptr_as_i64")
.map_err(|e| CodeGenError::Builder(e.to_string()))?,
_ => {
return Err(CodeGenError::TypeError(
"Logical AND requires integer or pointer operands".to_string(),
))
}
};
let lhs_zero = lhs_int.get_type().const_zero();
let lhs_bool = self
.builder
.build_int_compare(
inkwell::IntPredicate::NE,
lhs_int,
lhs_zero,
"land_lhs_nz",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let curr_block = self.builder.get_insert_block().ok_or_else(|| {
CodeGenError::LLVMError("No current basic block".to_string())
})?;
let func = curr_block
.get_parent()
.ok_or_else(|| CodeGenError::LLVMError("No parent function".to_string()))?;
let rhs_block = self.context.append_basic_block(func, "land_rhs");
let merge_block = self.context.append_basic_block(func, "land_merge");
self.builder
.build_conditional_branch(lhs_bool, rhs_block, merge_block)
.map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
self.builder.position_at_end(rhs_block);
let rhs_val = self.compile_expr(right)?;
let rhs_int = match rhs_val {
BasicValueEnum::IntValue(iv) => iv,
BasicValueEnum::PointerValue(pv) => self
.builder
.build_ptr_to_int(pv, self.context.i64_type(), "land_rhs_ptr_as_i64")
.map_err(|e| CodeGenError::Builder(e.to_string()))?,
_ => {
return Err(CodeGenError::TypeError(
"Logical AND requires integer or pointer operands".to_string(),
))
}
};
let rhs_zero = rhs_int.get_type().const_zero();
let rhs_bool = self
.builder
.build_int_compare(
inkwell::IntPredicate::NE,
rhs_int,
rhs_zero,
"land_rhs_nz",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let rhs_end_block = self.builder.get_insert_block().ok_or_else(|| {
CodeGenError::LLVMError("No current basic block after RHS".to_string())
})?;
self.builder
.build_unconditional_branch(merge_block)
.map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
self.builder.position_at_end(merge_block);
let i1 = self.context.bool_type();
let phi = self
.builder
.build_phi(i1, "land_phi")
.map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
let zero = i1.const_zero();
phi.add_incoming(&[(&rhs_bool, rhs_end_block), (&zero, curr_block)]);
return Ok(phi.as_basic_value());
}
let left_val = self.compile_expr(left)?;
let right_val = self.compile_expr(right)?;
self.compile_binary_op_with_ordering(
left_val,
binary_plan.op,
right_val,
binary_plan.integer_semantics,
)
}
Expr::MemberAccess(_, _) => {
self.compile_dwarf_expression(expr)
}
Expr::PointerDeref(_) => {
self.compile_dwarf_expression(expr)
}
Expr::AddressOf(inner) => {
let target_inner: &Expr = if let Expr::Variable(var_name) = inner.as_ref() {
if self.alias_variable_exists(var_name) {
let aliased = self
.get_alias_variable(var_name)
.expect("alias existence just checked");
let var =
self.query_dwarf_for_complex_expr(&aliased)?
.ok_or_else(|| {
super::context::CodeGenError::TypeError(
"cannot take address of unresolved expression".to_string(),
)
})?;
let pc_address = self.get_compile_time_context()?.pc_address;
match self.variable_read_plan_to_runtime_address(&var, pc_address, None) {
Ok(address) => {
let ptr_ty = self.context.ptr_type(AddressSpace::default());
let as_ptr = self
.builder
.build_int_to_ptr(address.value, ptr_ty, "addr_as_ptr")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
return Ok(as_ptr.into());
}
Err(_) => {
return Err(super::context::CodeGenError::TypeError(
"cannot take address of rvalue".to_string(),
));
}
}
} else {
inner.as_ref()
}
} else {
inner.as_ref()
};
if let Some(lvalue) = self.dynamic_lvalue_address_and_type(target_inner)? {
let ptr_ty = self.context.ptr_type(AddressSpace::default());
let as_ptr = self
.builder
.build_int_to_ptr(lvalue.address.value, ptr_ty, "addr_as_ptr")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
return Ok(as_ptr.into());
}
let var = self
.query_dwarf_for_complex_expr(target_inner)?
.ok_or_else(|| {
super::context::CodeGenError::TypeError(
"cannot take address of unresolved expression".to_string(),
)
})?;
let pc_address = self.get_compile_time_context()?.pc_address;
match self.variable_read_plan_to_runtime_address(&var, pc_address, None) {
Ok(address) => {
let ptr_ty = self.context.ptr_type(AddressSpace::default());
let as_ptr = self
.builder
.build_int_to_ptr(address.value, ptr_ty, "addr_as_ptr")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok(as_ptr.into())
}
Err(_) => Err(super::context::CodeGenError::TypeError(
"cannot take address of rvalue".to_string(),
)),
}
}
Expr::ArrayAccess(_, _) => {
self.compile_dwarf_expression(expr)
}
Expr::Cast {
expr: inner,
target_type,
} => self.compile_cast_expr_value(inner, target_type),
Expr::ChainAccess(_) => {
self.compile_dwarf_expression(expr)
}
}
}
pub fn handle_special_variable(&mut self, name: &str) -> Result<BasicValueEnum<'ctx>> {
match self.plan_special_variable(name)? {
SpecialVarPlan::Pid => {
let (pid, _tid) = self.get_special_pid_tid_values()?;
Ok(pid.into())
}
SpecialVarPlan::Tid => {
let (_pid, tid) = self.get_special_pid_tid_values()?;
Ok(tid.into())
}
SpecialVarPlan::HostPid => {
let (host_pid, _host_tid) = self.get_host_pid_tid_values()?;
let host_pid = self
.builder
.build_int_z_extend(host_pid, self.context.i64_type(), "selected_host_pid")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok(host_pid.into())
}
SpecialVarPlan::InputPid => {
let input_pid = self.compile_options.input_pid.ok_or_else(|| {
CodeGenError::NotImplemented(
"Special variable '$input_pid' is only available in -p mode".to_string(),
)
})?;
Ok(self
.context
.i64_type()
.const_int(input_pid as u64, false)
.into())
}
SpecialVarPlan::Timestamp => {
let ts = self.get_current_timestamp()?;
Ok(ts.into())
}
SpecialVarPlan::Pc => self.load_special_register_value(16),
SpecialVarPlan::Sp => self.load_special_register_value(7),
}
}
fn load_special_register_value(&mut self, dwarf_reg: u16) -> Result<BasicValueEnum<'ctx>> {
let pt_regs = self.get_pt_regs_parameter()?;
self.load_register_value(dwarf_reg, pt_regs)
}
pub fn compile_binary_op(
&mut self,
left: BasicValueEnum<'ctx>,
op: BinaryOp,
right: BasicValueEnum<'ctx>,
) -> Result<BasicValueEnum<'ctx>> {
self.compile_binary_op_with_ordering(left, op, right, BinaryIntegerSemantics::default())
}
pub(crate) fn build_signed_int_div_via_udiv(
&mut self,
left: IntValue<'ctx>,
right: IntValue<'ctx>,
name: &str,
) -> Result<IntValue<'ctx>> {
let int_type = left.get_type();
let zero = int_type.const_zero();
let left_is_neg = self
.builder
.build_int_compare(inkwell::IntPredicate::SLT, left, zero, "sdiv_lhs_neg")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let right_is_neg = self
.builder
.build_int_compare(inkwell::IntPredicate::SLT, right, zero, "sdiv_rhs_neg")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let neg_left = self
.builder
.build_int_sub(zero, left, "sdiv_lhs_negated")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let neg_right = self
.builder
.build_int_sub(zero, right, "sdiv_rhs_negated")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let abs_left = self
.builder
.build_select::<BasicValueEnum<'ctx>, _>(
left_is_neg,
neg_left.into(),
left.into(),
"sdiv_lhs_abs",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_int_value();
let abs_right = self
.builder
.build_select::<BasicValueEnum<'ctx>, _>(
right_is_neg,
neg_right.into(),
right.into(),
"sdiv_rhs_abs",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_int_value();
let abs_quotient = self
.builder
.build_int_unsigned_div(abs_left, abs_right, "sdiv_abs_udiv")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let negative_result = self
.builder
.build_xor(left_is_neg, right_is_neg, "sdiv_result_neg")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let neg_quotient = self
.builder
.build_int_sub(zero, abs_quotient, "sdiv_negated")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder
.build_select::<BasicValueEnum<'ctx>, _>(
negative_result,
neg_quotient.into(),
abs_quotient.into(),
name,
)
.map_err(|e| CodeGenError::Builder(e.to_string()))
.map(|value| value.into_int_value())
}
pub(crate) fn build_signed_int_rem_via_urem(
&mut self,
left: IntValue<'ctx>,
right: IntValue<'ctx>,
name: &str,
) -> Result<IntValue<'ctx>> {
let int_type = left.get_type();
let zero = int_type.const_zero();
let left_is_neg = self
.builder
.build_int_compare(inkwell::IntPredicate::SLT, left, zero, "srem_lhs_neg")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let right_is_neg = self
.builder
.build_int_compare(inkwell::IntPredicate::SLT, right, zero, "srem_rhs_neg")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let neg_left = self
.builder
.build_int_sub(zero, left, "srem_lhs_negated")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let neg_right = self
.builder
.build_int_sub(zero, right, "srem_rhs_negated")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let abs_left = self
.builder
.build_select::<BasicValueEnum<'ctx>, _>(
left_is_neg,
neg_left.into(),
left.into(),
"srem_lhs_abs",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_int_value();
let abs_right = self
.builder
.build_select::<BasicValueEnum<'ctx>, _>(
right_is_neg,
neg_right.into(),
right.into(),
"srem_rhs_abs",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?
.into_int_value();
let abs_remainder = self
.builder
.build_int_unsigned_rem(abs_left, abs_right, "srem_abs_urem")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let neg_remainder = self
.builder
.build_int_sub(zero, abs_remainder, "srem_negated")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder
.build_select::<BasicValueEnum<'ctx>, _>(
left_is_neg,
neg_remainder.into(),
abs_remainder.into(),
name,
)
.map_err(|e| CodeGenError::Builder(e.to_string()))
.map(|value| value.into_int_value())
}
fn normalize_int_for_unsigned_compare(
&mut self,
value: IntValue<'ctx>,
bit_width: u32,
name: &str,
) -> Result<IntValue<'ctx>> {
let current_width = value.get_type().get_bit_width();
if current_width == bit_width {
return Ok(value);
}
let target_type = self.context.custom_width_int_type(bit_width);
if current_width > bit_width {
self.builder
.build_int_truncate(value, target_type, name)
.map_err(|e| CodeGenError::Builder(e.to_string()))
} else {
self.builder
.build_int_z_extend(value, target_type, name)
.map_err(|e| CodeGenError::Builder(e.to_string()))
}
}
fn align_int_widths_for_binary_op(
&mut self,
left: IntValue<'ctx>,
right: IntValue<'ctx>,
) -> Result<(IntValue<'ctx>, IntValue<'ctx>)> {
let left_width = left.get_type().get_bit_width();
let right_width = right.get_type().get_bit_width();
if left_width == right_width {
return Ok((left, right));
}
let target_width = left_width.max(right_width);
let target_type = self.context.custom_width_int_type(target_width);
let left = if left_width < target_width {
self.builder
.build_int_z_extend(left, target_type, "lhs_width_align")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
} else {
left
};
let right = if right_width < target_width {
self.builder
.build_int_z_extend(right, target_type, "rhs_width_align")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
} else {
right
};
Ok((left, right))
}
fn mask_shift_amount(&mut self, amount: IntValue<'ctx>, name: &str) -> Result<IntValue<'ctx>> {
let bit_width = amount.get_type().get_bit_width();
let mask = amount
.get_type()
.const_int(u64::from(bit_width.saturating_sub(1)), false);
self.builder
.build_and(amount, mask, name)
.map_err(|e| CodeGenError::Builder(e.to_string()))
}
fn normalize_ints_for_unsigned_width(
&mut self,
left: IntValue<'ctx>,
right: IntValue<'ctx>,
bit_width: u32,
name: &str,
) -> Result<(IntValue<'ctx>, IntValue<'ctx>)> {
let left = self.normalize_int_for_unsigned_compare(
left,
bit_width,
&format!("{name}_lhs_unsigned"),
)?;
let right = self.normalize_int_for_unsigned_compare(
right,
bit_width,
&format!("{name}_rhs_unsigned"),
)?;
Ok((left, right))
}
fn zero_extend_int_to_i64_if_needed(
&mut self,
value: IntValue<'ctx>,
name: &str,
) -> Result<IntValue<'ctx>> {
if value.get_type().get_bit_width() >= 64 {
return Ok(value);
}
self.builder
.build_int_z_extend(value, self.context.i64_type(), name)
.map_err(|e| CodeGenError::Builder(e.to_string()))
}
fn compile_binary_op_with_ordering(
&mut self,
left: BasicValueEnum<'ctx>,
op: BinaryOp,
right: BasicValueEnum<'ctx>,
integer_semantics: BinaryIntegerSemantics,
) -> Result<BasicValueEnum<'ctx>> {
use inkwell::values::BasicValueEnum::*;
debug!("compile_binary_op: op={:?}", op);
debug!("compile_binary_op: left type = {:?}", left.get_type());
debug!("compile_binary_op: right type = {:?}", right.get_type());
match &left {
IntValue(iv) => debug!(
"compile_binary_op: left is IntValue with bit width {}",
iv.get_type().get_bit_width()
),
FloatValue(_) => debug!("compile_binary_op: left is FloatValue"),
PointerValue(_) => debug!("compile_binary_op: left is PointerValue"),
_ => debug!("compile_binary_op: left is other type"),
}
match &right {
IntValue(iv) => debug!(
"compile_binary_op: right is IntValue with bit width {}",
iv.get_type().get_bit_width()
),
FloatValue(_) => debug!("compile_binary_op: right is FloatValue"),
PointerValue(_) => debug!("compile_binary_op: right is PointerValue"),
_ => debug!("compile_binary_op: right is other type"),
}
match (left, right) {
(IntValue(left_int), IntValue(right_int)) => {
let (left_int, right_int) =
self.align_int_widths_for_binary_op(left_int, right_int)?;
let unsigned_cmp_values = if let Some(bit_width) =
integer_semantics.unsigned_ordering_width
{
Some((
self.normalize_int_for_unsigned_compare(
left_int,
bit_width,
"lhs_unsigned_cmp",
)?,
self.normalize_int_for_unsigned_compare(
right_int,
bit_width,
"rhs_unsigned_cmp",
)?,
))
} else {
None
};
let result = match op {
BinaryOp::Add => self
.builder
.build_int_add(left_int, right_int, "add")
.map_err(|e| CodeGenError::Builder(e.to_string()))?,
BinaryOp::Subtract => self
.builder
.build_int_sub(left_int, right_int, "sub")
.map_err(|e| CodeGenError::Builder(e.to_string()))?,
BinaryOp::Multiply => self
.builder
.build_int_mul(left_int, right_int, "mul")
.map_err(|e| CodeGenError::Builder(e.to_string()))?,
BinaryOp::Divide => {
if let Some(bit_width) = integer_semantics.unsigned_division_width {
let (left_int, right_int) = self.normalize_ints_for_unsigned_width(
left_int,
right_int,
bit_width,
"div",
)?;
let result = self
.builder
.build_int_unsigned_div(left_int, right_int, "div")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.zero_extend_int_to_i64_if_needed(result, "div_zext_i64")?
} else {
self.build_signed_int_div_via_udiv(left_int, right_int, "div")?
}
}
BinaryOp::Modulo => {
if let Some(bit_width) = integer_semantics.unsigned_division_width {
let (left_int, right_int) = self.normalize_ints_for_unsigned_width(
left_int,
right_int,
bit_width,
"mod",
)?;
let result = self
.builder
.build_int_unsigned_rem(left_int, right_int, "mod")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.zero_extend_int_to_i64_if_needed(result, "mod_zext_i64")?
} else {
self.build_signed_int_rem_via_urem(left_int, right_int, "mod")?
}
}
BinaryOp::BitAnd => {
let (left_int, right_int) =
if let Some(bit_width) = integer_semantics.unsigned_bitwise_width {
self.normalize_ints_for_unsigned_width(
left_int,
right_int,
bit_width,
"bitand",
)?
} else {
(left_int, right_int)
};
let result = self
.builder
.build_and(left_int, right_int, "bitand")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
if integer_semantics.unsigned_bitwise_width.is_some() {
self.zero_extend_int_to_i64_if_needed(result, "bitand_zext_i64")?
} else {
result
}
}
BinaryOp::BitXor => {
let (left_int, right_int) =
if let Some(bit_width) = integer_semantics.unsigned_bitwise_width {
self.normalize_ints_for_unsigned_width(
left_int,
right_int,
bit_width,
"bitxor",
)?
} else {
(left_int, right_int)
};
let result = self
.builder
.build_xor(left_int, right_int, "bitxor")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
if integer_semantics.unsigned_bitwise_width.is_some() {
self.zero_extend_int_to_i64_if_needed(result, "bitxor_zext_i64")?
} else {
result
}
}
BinaryOp::BitOr => {
let (left_int, right_int) =
if let Some(bit_width) = integer_semantics.unsigned_bitwise_width {
self.normalize_ints_for_unsigned_width(
left_int,
right_int,
bit_width,
"bitor",
)?
} else {
(left_int, right_int)
};
let result = self
.builder
.build_or(left_int, right_int, "bitor")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
if integer_semantics.unsigned_bitwise_width.is_some() {
self.zero_extend_int_to_i64_if_needed(result, "bitor_zext_i64")?
} else {
result
}
}
BinaryOp::ShiftLeft => {
let right_int = self.mask_shift_amount(right_int, "shl_rhs_mask")?;
self.builder
.build_left_shift(left_int, right_int, "shl")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
}
BinaryOp::ShiftRight => {
let right_int = self.mask_shift_amount(right_int, "shr_rhs_mask")?;
self.builder
.build_right_shift(
left_int,
right_int,
integer_semantics.unsigned_right_shift_width.is_none(),
"shr",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?
}
BinaryOp::Equal => {
let result = self
.builder
.build_int_compare(inkwell::IntPredicate::EQ, left_int, right_int, "eq")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
return Ok(result.into());
}
BinaryOp::NotEqual => {
let result = self
.builder
.build_int_compare(inkwell::IntPredicate::NE, left_int, right_int, "ne")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
return Ok(result.into());
}
BinaryOp::LessThan => {
let predicate = if integer_semantics.unsigned_ordering_width.is_some() {
inkwell::IntPredicate::ULT
} else {
inkwell::IntPredicate::SLT
};
let (left_cmp, right_cmp) =
unsigned_cmp_values.unwrap_or((left_int, right_int));
let result = self
.builder
.build_int_compare(predicate, left_cmp, right_cmp, "lt")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
return Ok(result.into());
}
BinaryOp::LessEqual => {
let predicate = if integer_semantics.unsigned_ordering_width.is_some() {
inkwell::IntPredicate::ULE
} else {
inkwell::IntPredicate::SLE
};
let (left_cmp, right_cmp) =
unsigned_cmp_values.unwrap_or((left_int, right_int));
let result = self
.builder
.build_int_compare(predicate, left_cmp, right_cmp, "le")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
return Ok(result.into());
}
BinaryOp::GreaterThan => {
let predicate = if integer_semantics.unsigned_ordering_width.is_some() {
inkwell::IntPredicate::UGT
} else {
inkwell::IntPredicate::SGT
};
let (left_cmp, right_cmp) =
unsigned_cmp_values.unwrap_or((left_int, right_int));
let result = self
.builder
.build_int_compare(predicate, left_cmp, right_cmp, "gt")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
return Ok(result.into());
}
BinaryOp::GreaterEqual => {
let predicate = if integer_semantics.unsigned_ordering_width.is_some() {
inkwell::IntPredicate::UGE
} else {
inkwell::IntPredicate::SGE
};
let (left_cmp, right_cmp) =
unsigned_cmp_values.unwrap_or((left_int, right_int));
let result = self
.builder
.build_int_compare(predicate, left_cmp, right_cmp, "ge")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
return Ok(result.into());
}
BinaryOp::LogicalAnd => {
let lz = left_int.get_type().const_zero();
let rz = right_int.get_type().const_zero();
let lbool = self
.builder
.build_int_compare(inkwell::IntPredicate::NE, left_int, lz, "lhs_nz")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let rbool = self
.builder
.build_int_compare(inkwell::IntPredicate::NE, right_int, rz, "rhs_nz")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let result = self
.builder
.build_and(lbool, rbool, "and_bool")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
return Ok(result.into());
}
BinaryOp::LogicalOr => {
let lz = left_int.get_type().const_zero();
let rz = right_int.get_type().const_zero();
let lbool = self
.builder
.build_int_compare(inkwell::IntPredicate::NE, left_int, lz, "lhs_nz")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let rbool = self
.builder
.build_int_compare(inkwell::IntPredicate::NE, right_int, rz, "rhs_nz")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let result = self
.builder
.build_or(lbool, rbool, "or_bool")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
return Ok(result.into());
}
};
Ok(result.into())
}
(PointerValue(lp), IntValue(ri)) | (IntValue(ri), PointerValue(lp)) => {
match op {
BinaryOp::Equal | BinaryOp::NotEqual => {
let lpi64 = self
.builder
.build_ptr_to_int(lp, self.context.i64_type(), "ptr_as_i64")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let rbw = ri.get_type().get_bit_width();
let ri64 = if rbw < 64 {
self.builder
.build_int_z_extend(ri, self.context.i64_type(), "rhs_zext_i64")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
} else if rbw > 64 {
self.builder
.build_int_truncate(ri, self.context.i64_type(), "rhs_trunc_i64")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
} else {
ri
};
let pred = if matches!(op, BinaryOp::Equal) {
inkwell::IntPredicate::EQ
} else {
inkwell::IntPredicate::NE
};
let cmp = self
.builder
.build_int_compare(pred, lpi64, ri64, "ptr_cmp")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok(cmp.into())
}
_ => Err(CodeGenError::TypeError(
"Unsupported operation between aggregate address/pointer and integer: only '==' and '!=' are allowed. If you meant to offset an address, use '&expr +/- <integer literal>' in an alias/address context, or access a scalar field.".to_string(),
)),
}
}
(PointerValue(lp), PointerValue(rp)) => match op {
BinaryOp::Equal | BinaryOp::NotEqual => {
let lpi64 = self
.builder
.build_ptr_to_int(lp, self.context.i64_type(), "l_ptr_as_i64")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let rpi64 = self
.builder
.build_ptr_to_int(rp, self.context.i64_type(), "r_ptr_as_i64")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let pred = if matches!(op, BinaryOp::Equal) {
inkwell::IntPredicate::EQ
} else {
inkwell::IntPredicate::NE
};
let cmp = self
.builder
.build_int_compare(pred, lpi64, rpi64, "ptr_ptr_cmp")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok(cmp.into())
}
_ => Err(CodeGenError::TypeError(
"Pointer ordered comparison ('<', '<=', '>', '>=') is not supported. Use '==' or '!=' to compare addresses. If you need to adjust an address, use '&expr +/- <integer literal>' in an alias/address context; to compare values, select a scalar field (e.g., 'obj.field')."
.to_string(),
)),
},
(FloatValue(left_float), FloatValue(right_float)) => match op {
BinaryOp::Add => {
let result = self
.builder
.build_float_add(left_float, right_float, "add")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok(result.into())
}
BinaryOp::Subtract => {
let result = self
.builder
.build_float_sub(left_float, right_float, "sub")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok(result.into())
}
BinaryOp::Multiply => {
let result = self
.builder
.build_float_mul(left_float, right_float, "mul")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok(result.into())
}
BinaryOp::Divide => {
let result = self
.builder
.build_float_div(left_float, right_float, "div")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok(result.into())
}
BinaryOp::Equal => {
let result = self
.builder
.build_float_compare(
inkwell::FloatPredicate::OEQ,
left_float,
right_float,
"eq",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok(result.into())
}
BinaryOp::NotEqual => {
let result = self
.builder
.build_float_compare(
inkwell::FloatPredicate::ONE,
left_float,
right_float,
"ne",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok(result.into())
}
BinaryOp::LessThan => {
let result = self
.builder
.build_float_compare(
inkwell::FloatPredicate::OLT,
left_float,
right_float,
"lt",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok(result.into())
}
BinaryOp::LessEqual => {
let result = self
.builder
.build_float_compare(
inkwell::FloatPredicate::OLE,
left_float,
right_float,
"le",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok(result.into())
}
BinaryOp::GreaterThan => {
let result = self
.builder
.build_float_compare(
inkwell::FloatPredicate::OGT,
left_float,
right_float,
"gt",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok(result.into())
}
BinaryOp::GreaterEqual => {
let result = self
.builder
.build_float_compare(
inkwell::FloatPredicate::OGE,
left_float,
right_float,
"ge",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
Ok(result.into())
}
_ => Err(CodeGenError::NotImplemented(format!(
"Float binary operation {op:?} not implemented"
))),
},
_ => Err(CodeGenError::TypeError(format!(
"Type mismatch in binary operation {op:?}"
))),
}
}
pub fn compile_member_access(
&mut self,
obj_expr: &Expr,
field: &str,
) -> Result<BasicValueEnum<'ctx>> {
let member_access_expr = Expr::MemberAccess(Box::new(obj_expr.clone()), field.to_string());
self.compile_dwarf_expression(&member_access_expr)
}
pub fn compile_pointer_deref(&mut self, expr: &Expr) -> Result<BasicValueEnum<'ctx>> {
let pointer_deref_expr = Expr::PointerDeref(Box::new(expr.clone()));
self.compile_dwarf_expression(&pointer_deref_expr)
}
pub fn compile_array_access(
&mut self,
array_expr: &Expr,
index_expr: &Expr,
) -> Result<BasicValueEnum<'ctx>> {
if let Some((value, _element_type)) =
self.compile_dynamic_array_access_value(array_expr, index_expr)?
{
return Ok(value);
}
let array_access_expr =
Expr::ArrayAccess(Box::new(array_expr.clone()), Box::new(index_expr.clone()));
self.compile_dwarf_expression(&array_access_expr)
}
pub fn compile_chain_access(&mut self, chain: &[String]) -> Result<BasicValueEnum<'ctx>> {
let chain_access_expr = Expr::ChainAccess(chain.to_vec());
self.compile_dwarf_expression(&chain_access_expr)
}
pub fn compile_dwarf_expression(
&mut self,
expr: &crate::script::Expr,
) -> Result<BasicValueEnum<'ctx>> {
debug!(
"compile_dwarf_expression: Compiling complex expression: {:?}",
expr
);
if let crate::script::Expr::Cast {
expr: inner,
target_type,
} = expr
{
return self.compile_cast_expr_value(inner, target_type);
}
if let crate::script::Expr::ArrayAccess(array_expr, index_expr) = expr {
if let Some((value, _element_type)) =
self.compile_dynamic_array_access_value(array_expr, index_expr)?
{
return Ok(value);
}
}
if let crate::script::Expr::MemberAccess(obj_expr, field) = expr {
if let Some((value, _member_type)) =
self.compile_dynamic_member_access_value(obj_expr, field)?
{
return Ok(value);
}
}
if matches!(expr, crate::script::Expr::PointerDeref(_)) {
if let Some(lvalue) = self.dynamic_lvalue_address_and_type(expr)? {
return self
.read_dynamic_address_value(lvalue.address, &lvalue.type_info.dwarf_type);
}
}
let compile_context = self.get_compile_time_context()?.clone();
let variable_plan = match self.query_dwarf_for_complex_expr(expr)? {
Some(var) => var,
None => {
let expr_str = Self::expr_to_debug_string(expr);
return Err(CodeGenError::VariableNotFound(expr_str));
}
};
let materialized =
self.variable_read_plan_to_materialization(variable_plan, compile_context.pc_address)?;
let dwarf_type = materialized.dwarf_type.as_ref().ok_or_else(|| {
CodeGenError::DwarfError("Expression has no DWARF type information".to_string())
})?;
debug!(
"compile_dwarf_expression: Found DWARF info for expression '{}' with type: {:?}",
materialized.name, dwarf_type
);
self.variable_materialization_to_llvm_value(&materialized, compile_context.pc_address, None)
}
pub(super) fn compile_dynamic_array_access_value(
&mut self,
array_expr: &Expr,
index_expr: &Expr,
) -> Result<Option<(BasicValueEnum<'ctx>, DwarfType)>> {
let Some(element_lvalue) =
self.compile_dynamic_array_element_address(array_expr, index_expr)?
else {
return Ok(None);
};
let value = self.read_dynamic_address_value(
element_lvalue.address,
&element_lvalue.type_info.dwarf_type,
)?;
Ok(Some((value, element_lvalue.type_info.dwarf_type)))
}
pub(super) fn compile_dynamic_member_access_value(
&mut self,
obj_expr: &Expr,
field: &str,
) -> Result<Option<(BasicValueEnum<'ctx>, DwarfType)>> {
let Some(object_lvalue) = self.dynamic_lvalue_address_and_type(obj_expr)? else {
return Ok(None);
};
let Some(element_lvalue) = self.dynamic_member_base_address_and_type(object_lvalue)? else {
return Ok(None);
};
let (member_offset, member_type) =
self.dynamic_member_offset_and_type(&element_lvalue.type_info, field)?;
let member_offset = self.context.i64_type().const_int(member_offset, false);
let member_address = self
.builder
.build_int_add(
element_lvalue.address.value,
member_offset,
"dynamic_member_address",
)
.map_err(|err| CodeGenError::Builder(err.to_string()))?;
let value = self.read_dynamic_address_value(
element_lvalue.address.with_value(member_address),
&member_type,
)?;
Ok(Some((value, member_type)))
}
pub(super) fn dynamic_lvalue_address_and_type(
&mut self,
expr: &Expr,
) -> Result<Option<DynamicLvalue<'ctx>>> {
if let Expr::Variable(name) = expr {
if self.alias_variable_exists(name) {
let expanded = self.expand_alias_variable_expr(expr)?;
return self.dynamic_lvalue_address_and_type(&expanded);
}
}
if let Expr::Cast {
expr: inner,
target_type,
} = expr
{
return self
.cast_lvalue_address_and_type(inner, target_type)
.map(Some);
}
if let Expr::PointerDeref(inner) = expr {
let expanded_inner = self.expand_alias_variable_expr(inner)?;
if matches!(expanded_inner, Expr::Cast { .. }) {
return self.dynamic_lvalue_address_and_type(&expanded_inner);
}
if let Expr::BinaryOp { .. } = expanded_inner {
if let Some(lvalue) = self.dynamic_lvalue_address_and_type(&expanded_inner)? {
return Ok(Some(lvalue));
}
}
}
if let Expr::ArrayAccess(array_expr, index_expr) = expr {
return self.compile_dynamic_array_element_address(array_expr, index_expr);
}
if let Some(lvalue) = self.dynamic_lvalue_from_const_pointer_arithmetic(expr)? {
return Ok(Some(lvalue));
}
if self.expands_to_nonliteral_pointer_arithmetic(expr)? {
let Some(element_info) = self.indexable_element_type_and_stride(expr)? else {
return Ok(None);
};
let element_address = self.resolve_runtime_address_from_expr(expr)?;
return Ok(Some(DynamicLvalue {
address: element_address,
type_info: DynamicTypeInfo {
dwarf_type: element_info.element_type,
module_path: element_info.module_path,
},
}));
}
if let Expr::MemberAccess(obj_expr, field) = expr {
let Some(object_lvalue) = self.dynamic_lvalue_address_and_type(obj_expr)? else {
return Ok(None);
};
let Some(base_lvalue) = self.dynamic_member_base_address_and_type(object_lvalue)?
else {
return Ok(None);
};
let (member_offset, member_type) =
self.dynamic_member_offset_and_type(&base_lvalue.type_info, field)?;
let member_offset = self.context.i64_type().const_int(member_offset, false);
let member_address = self
.builder
.build_int_add(
base_lvalue.address.value,
member_offset,
"dynamic_member_lvalue_address",
)
.map_err(|err| CodeGenError::Builder(err.to_string()))?;
return Ok(Some(DynamicLvalue {
address: base_lvalue.address.with_value(member_address),
type_info: DynamicTypeInfo {
dwarf_type: member_type,
module_path: base_lvalue.type_info.module_path,
},
}));
}
Ok(None)
}
fn dynamic_member_base_address_and_type(
&mut self,
object: DynamicLvalue<'ctx>,
) -> Result<Option<DynamicLvalue<'ctx>>> {
let module_path = object.type_info.module_path.clone();
let object_type = self.complete_dynamic_member_element_type(
object.type_info.dwarf_type,
module_path.as_deref(),
);
match ghostscope_dwarf::strip_type_aliases(&object_type) {
DwarfType::StructType { .. } | DwarfType::UnionType { .. } => Ok(Some(DynamicLvalue {
address: object.address,
type_info: DynamicTypeInfo {
dwarf_type: object_type,
module_path,
},
})),
DwarfType::PointerType { target_type, .. } => {
let pointer_value =
self.read_dynamic_address_value(object.address, &object_type)?;
let pointer_value = match pointer_value {
BasicValueEnum::IntValue(value) => {
self.normalize_int_to_i64(value, "dynamic_member_pointer_i64")?
}
BasicValueEnum::PointerValue(value) => self
.builder
.build_ptr_to_int(
value,
self.context.i64_type(),
"dynamic_member_pointer_ptr",
)
.map_err(|err| CodeGenError::Builder(err.to_string()))?,
_ => {
return Err(CodeGenError::TypeError(
"dynamic member pointer base did not compile to an address".to_string(),
))
}
};
let target_type = self.complete_dynamic_member_element_type(
target_type.as_ref().clone(),
module_path.as_deref(),
);
Ok(Some(DynamicLvalue {
address: RuntimeAddress::available(pointer_value, self.context),
type_info: DynamicTypeInfo {
dwarf_type: target_type,
module_path,
},
}))
}
_ => Ok(None),
}
}
fn dynamic_member_offset_and_type(
&self,
aggregate: &DynamicTypeInfo,
field: &str,
) -> Result<(u64, DwarfType)> {
let aggregate_type = self.complete_dynamic_member_element_type(
aggregate.dwarf_type.clone(),
aggregate.module_path.as_deref(),
);
match ghostscope_dwarf::member_layout(&aggregate_type, field) {
Ok(layout) => Ok((layout.offset, layout.member_type)),
Err(err @ TypeLayoutError::UnknownMember { .. }) => {
Err(CodeGenError::DwarfError(err.to_string()))
}
Err(err @ TypeLayoutError::InvalidMemberBase { .. }) => {
Err(CodeGenError::TypeError(err.to_string()))
}
}
}
fn dynamic_array_base_from_plan(
&mut self,
array_plan: &VariableReadPlan,
pc_address: u64,
status_ptr: Option<PointerValue<'ctx>>,
static_index: i64,
) -> Result<(IndexableElementInfo, RuntimeAddress<'ctx>, i64)> {
let module_path = array_plan.module_path.clone();
let array_type = array_plan.dwarf_type.as_ref().ok_or_else(|| {
CodeGenError::DwarfError("Array expression has no DWARF type information".to_string())
})?;
let element_info =
Self::indexable_info_from_type(array_type, module_path).ok_or_else(|| {
CodeGenError::TypeError(format!(
"dynamic array index requires array or pointer type, got '{}'",
array_type.type_name()
))
})?;
match ghostscope_dwarf::strip_type_aliases(array_type) {
DwarfType::ArrayType { .. } => {
let base_address =
self.variable_read_plan_to_runtime_address(array_plan, pc_address, status_ptr)?;
Ok((element_info, base_address, static_index))
}
DwarfType::PointerType { .. } => {
let pointer_value =
self.variable_read_plan_to_llvm_value(array_plan, pc_address, status_ptr)?;
let base_address = self.compiled_pointer_value_to_runtime_address(
pointer_value,
"dynamic_array_base_i64",
"dynamic_array_base_ptr",
"array base pointer did not compile to an address",
)?;
Ok((element_info, base_address, static_index))
}
_ => unreachable!("indexable_info_from_type accepts only array or pointer types"),
}
}
fn compile_dynamic_array_element_address(
&mut self,
array_expr: &Expr,
index_expr: &Expr,
) -> Result<Option<DynamicLvalue<'ctx>>> {
let literal_index = Self::integer_literal_value(index_expr);
let expanded_array_expr = self.expand_alias_variable_expr(array_expr)?;
let has_dynamic_base =
self.expands_to_nonliteral_pointer_arithmetic(&expanded_array_expr)?;
let cast_base = self.cast_index_base(&expanded_array_expr)?;
if literal_index.is_some() && !has_dynamic_base && cast_base.is_none() {
return Ok(None);
}
let compile_context = self.get_compile_time_context()?.clone();
let status_ptr = if self.condition_context_active {
Some(self.get_or_create_cond_error_global())
} else {
None
};
let (element_info, base_address, static_index) = if let Some((element_info, base_address)) =
cast_base
{
(element_info, base_address, 0)
} else {
match self.query_dwarf_for_complex_expr(array_expr)? {
Some(array_plan) => self.dynamic_array_base_from_plan(
&array_plan,
compile_context.pc_address,
status_ptr,
0,
)?,
None => {
if let Some((base_expr, static_index)) =
self.pointer_arithmetic_parts_expanding_aliases(&expanded_array_expr)?
{
let array_plan = self
.query_dwarf_for_complex_expr(&base_expr)?
.ok_or_else(|| {
CodeGenError::VariableNotFound(Self::expr_to_debug_string(
&base_expr,
))
})?;
self.dynamic_array_base_from_plan(
&array_plan,
compile_context.pc_address,
status_ptr,
static_index,
)?
} else if has_dynamic_base {
let element_info = self
.indexable_element_type_and_stride(&expanded_array_expr)?
.ok_or_else(|| {
CodeGenError::VariableNotFound(Self::expr_to_debug_string(
array_expr,
))
})?;
let base_address =
self.resolve_runtime_address_from_expr(&expanded_array_expr)?;
(element_info, base_address, 0)
} else if let Some(array_lvalue) =
self.dynamic_lvalue_address_and_type(&expanded_array_expr)?
{
let module_path = array_lvalue.type_info.module_path.clone();
let element_info = Self::indexable_info_from_type(
&array_lvalue.type_info.dwarf_type,
module_path,
)
.ok_or_else(|| {
CodeGenError::TypeError(format!(
"dynamic array index requires array or pointer type, got '{}'",
array_lvalue.type_info.dwarf_type.type_name()
))
})?;
match ghostscope_dwarf::strip_type_aliases(
&array_lvalue.type_info.dwarf_type,
) {
DwarfType::ArrayType { .. } => (element_info, array_lvalue.address, 0),
DwarfType::PointerType { .. } => {
let pointer_value = self.read_dynamic_address_value(
array_lvalue.address,
&array_lvalue.type_info.dwarf_type,
)?;
let base_address = self.compiled_pointer_value_to_runtime_address(
pointer_value,
"dynamic_array_member_ptr_i64",
"dynamic_array_member_ptr",
"array member pointer did not compile to an address",
)?;
(element_info, base_address, 0)
}
_ => unreachable!(
"indexable_info_from_type accepts only array or pointer types"
),
}
} else {
return Err(CodeGenError::VariableNotFound(Self::expr_to_debug_string(
array_expr,
)));
}
}
}
};
let index_value = if let Some(index) = literal_index {
self.context.i64_type().const_int(index as u64, true)
} else {
match self.compile_expr(index_expr)? {
BasicValueEnum::IntValue(value) => {
self.normalize_int_to_i64(value, "dynamic_array_index_i64")?
}
_ => {
return Err(CodeGenError::TypeError(
"array index expression must compile to an integer".to_string(),
))
}
}
};
let index_value = if static_index == 0 {
index_value
} else {
let static_index_value = self.context.i64_type().const_int(static_index as u64, true);
self.builder
.build_int_add(
index_value,
static_index_value,
"dynamic_array_static_index",
)
.map_err(|err| CodeGenError::Builder(err.to_string()))?
};
let stride_value = self
.context
.i64_type()
.const_int(element_info.stride, false);
let byte_offset = self
.builder
.build_int_mul(index_value, stride_value, "dynamic_array_byte_offset")
.map_err(|err| CodeGenError::Builder(err.to_string()))?;
let element_address = self
.builder
.build_int_add(
base_address.value,
byte_offset,
"dynamic_array_element_address",
)
.map_err(|err| CodeGenError::Builder(err.to_string()))?;
Ok(Some(DynamicLvalue {
address: base_address.with_value(element_address),
type_info: DynamicTypeInfo {
dwarf_type: element_info.element_type,
module_path: element_info.module_path,
},
}))
}
fn indexable_element_type_and_stride(
&mut self,
expr: &Expr,
) -> Result<Option<IndexableElementInfo>> {
use crate::script::ast::BinaryOp as BO;
use crate::script::ast::Expr as E;
let expanded = self.expand_alias_variable_expr(expr)?;
if let Some((element_info, _base_address)) = self.cast_index_base(&expanded)? {
return Ok(Some(element_info));
}
if let Some(plan) = self.query_dwarf_for_complex_expr(&expanded)? {
if let Some(dwarf_type) = plan.dwarf_type.as_ref() {
if let Some(info) =
Self::indexable_info_from_type(dwarf_type, plan.module_path.clone())
{
return Ok(Some(info));
}
}
}
if let Some((base_expr, _static_index)) =
self.pointer_arithmetic_parts_expanding_aliases(&expanded)?
{
if let Some(plan) = self.query_dwarf_for_complex_expr(&base_expr)? {
if let Some(dwarf_type) = plan.dwarf_type.as_ref() {
if let Some(info) =
Self::indexable_info_from_type(dwarf_type, plan.module_path.clone())
{
return Ok(Some(info));
}
}
}
}
match expanded {
E::BinaryOp {
ref left,
op: BO::Add,
ref right,
} => {
if let Some(info) = self.indexable_element_type_and_stride(left)? {
return Ok(Some(info));
}
self.indexable_element_type_and_stride(right)
}
E::BinaryOp {
ref left,
op: BO::Subtract,
..
} => self.indexable_element_type_and_stride(left),
_ => Ok(None),
}
}
fn complete_dynamic_member_element_type(
&self,
element_type: DwarfType,
module_path: Option<&Path>,
) -> DwarfType {
let Some(analyzer) = self.process_analyzer else {
return element_type;
};
let fallback_module_path = self
.current_compile_time_context
.as_ref()
.map(|ctx| PathBuf::from(&ctx.module_path));
let lookup_module_path = module_path.or(fallback_module_path.as_deref());
if let Some(module_path) = lookup_module_path {
analyzer.complete_shallow_unknown_aggregate_type_in_module(module_path, element_type)
} else {
analyzer.complete_shallow_unknown_aggregate_type(element_type)
}
}
fn read_dynamic_address_value(
&mut self,
address: RuntimeAddress<'ctx>,
dwarf_type: &DwarfType,
) -> Result<BasicValueEnum<'ctx>> {
if ghostscope_dwarf::is_c_aggregate_type(dwarf_type) {
let ptr_ty = self.context.ptr_type(AddressSpace::default());
let as_ptr = self
.builder
.build_int_to_ptr(address.value, ptr_ty, "dynamic_aggregate_ptr")
.map_err(|err| CodeGenError::Builder(err.to_string()))?;
return Ok(as_ptr.into());
}
let access_size = self.dwarf_type_to_memory_access_size(dwarf_type);
let value = if self.condition_context_active {
self.generate_memory_read_with_status(address, access_size)?
} else {
self.generate_memory_read(address, access_size, None)?
};
self.sign_extend_memory_read_if_needed(value, dwarf_type, access_size)
}
fn expand_alias_variable_expr(&self, expr: &Expr) -> Result<Expr> {
let mut expanded = expr.clone();
let mut visited = std::collections::HashSet::new();
loop {
let Expr::Variable(name) = &expanded else {
return Ok(expanded);
};
if !self.alias_variable_exists(name) {
return Ok(expanded);
}
if !visited.insert(name.clone()) {
return Err(CodeGenError::TypeError(format!(
"alias cycle detected for '{name}'"
)));
}
let Some(target) = self.get_alias_variable(name) else {
return Ok(expanded);
};
expanded = target;
}
}
fn normalize_int_to_i64(&self, value: IntValue<'ctx>, name: &str) -> Result<IntValue<'ctx>> {
let width = value.get_type().get_bit_width();
if width == 64 {
return Ok(value);
}
if width < 64 {
return self
.builder
.build_int_s_extend(value, self.context.i64_type(), name)
.map_err(|err| CodeGenError::Builder(err.to_string()));
}
self.builder
.build_int_truncate(value, self.context.i64_type(), name)
.map_err(|err| CodeGenError::Builder(err.to_string()))
}
pub(crate) fn dwarf_expression_unavailable_error(
name: &str,
availability: &Availability,
pc_address: u64,
) -> CodeGenError {
let reason = Self::format_availability_reason(availability);
CodeGenError::VariableUnavailable(format!(
"'{name}' is {reason}; cannot use it as a value expression at PC 0x{pc_address:x}"
))
}
pub(crate) fn dwarf_lvalue_address_unavailable_error(
name: &str,
availability: &Availability,
pc_address: u64,
) -> CodeGenError {
let reason = Self::format_availability_reason(availability);
CodeGenError::VariableUnavailable(format!(
"'{name}' is {reason}; cannot take its address at PC 0x{pc_address:x}"
))
}
fn format_availability_reason(availability: &Availability) -> String {
match availability {
Availability::OptimizedOut => "optimized out at the selected probe PC".to_string(),
Availability::NotInScope => "not in scope at the selected probe PC".to_string(),
Availability::Unsupported(reason) => {
format!(
"unsupported DWARF semantic shape: {}",
Self::format_unsupported_reason(reason)
)
}
Availability::Requires(requirement) => {
format!(
"requires unavailable runtime support: {}",
Self::format_runtime_requirement(requirement)
)
}
Availability::Ambiguous(reason) => {
format!(
"ambiguous DWARF semantic result: {}",
Self::format_ambiguity_reason(reason)
)
}
Availability::Available | Availability::PartiallyAvailable => "available".to_string(),
}
}
fn format_unsupported_reason(reason: &UnsupportedReason) -> String {
match reason {
UnsupportedReason::DwarfOp { op } => format!("unsupported DWARF op {op}"),
UnsupportedReason::ExpressionShape { detail } => {
format!("unsupported DWARF expression shape: {detail}")
}
UnsupportedReason::TypeLayout { detail } => {
format!("unsupported type layout: {detail}")
}
UnsupportedReason::AddressClass { detail } => {
format!("unsupported address class: {detail}")
}
UnsupportedReason::RegisterMapping { dwarf_reg } => {
format!("unsupported DWARF register mapping for register {dwarf_reg}")
}
}
}
fn format_runtime_requirement(requirement: &RuntimeRequirement) -> &'static str {
match requirement {
RuntimeRequirement::CallerFrame => "caller-frame recovery",
RuntimeRequirement::SleepableUprobe => "sleepable uprobe support",
RuntimeRequirement::UserMemoryRead => "user-memory read support",
RuntimeRequirement::DwarfCfiRecovery => "DWARF CFI recovery",
}
}
fn format_ambiguity_reason(reason: &AmbiguityReason) -> String {
match reason {
AmbiguityReason::InlineContext { detail } => {
format!("ambiguous inline context: {detail}")
}
AmbiguityReason::VariableDeclaration { detail } => {
format!("ambiguous variable declaration: {detail}")
}
AmbiguityReason::TypeResolution { detail } => {
format!("ambiguous type resolution: {detail}")
}
}
}
fn expr_to_debug_string(expr: &crate::script::Expr) -> String {
use crate::script::Expr;
match expr {
Expr::Variable(name) => name.clone(),
Expr::MemberAccess(obj, field) => {
format!("{}.{}", Self::expr_to_debug_string(obj), field)
}
Expr::ArrayAccess(arr, _) => format!("{}[index]", Self::expr_to_debug_string(arr)),
Expr::Cast { expr, target_type } => format!(
"cast({}, \"{}\")",
Self::expr_to_debug_string(expr),
target_type
),
Expr::ChainAccess(chain) => chain.join("."),
Expr::PointerDeref(expr) => format!("*{}", Self::expr_to_debug_string(expr)),
_ => "expr".to_string(),
}
}
}
impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> {
fn compile_string_comparison(
&mut self,
dwarf_expr: &Expr,
lit: &str,
is_equal: bool,
) -> Result<BasicValueEnum<'ctx>> {
use ghostscope_dwarf::TypeInfo as TI;
let var = self
.query_dwarf_for_complex_expr(dwarf_expr)?
.ok_or_else(|| {
CodeGenError::TypeError(
"string comparison requires DWARF variable/expression".into(),
)
})?;
let dwarf_type_opt = var.dwarf_type.as_ref();
enum ParsedKind {
PtrChar,
ArrChar(Option<u32>),
Other,
}
fn parse_type_name(name: &str) -> ParsedKind {
let lower = name.to_lowercase();
let has_char = lower.contains("char");
let is_ptr = lower.contains('*');
if has_char && is_ptr {
return ParsedKind::PtrChar;
}
if has_char && lower.contains('[') {
let mut n: Option<u32> = None;
if let Some(start) = lower.find('[') {
if let Some(end) = lower[start + 1..].find(']') {
let inside = &lower[start + 1..start + 1 + end];
let digits: String =
inside.chars().filter(|c| c.is_ascii_digit()).collect();
if !digits.is_empty() {
if let Ok(v) = digits.parse::<u32>() {
n = Some(v);
}
}
}
}
return ParsedKind::ArrChar(n);
}
ParsedKind::Other
}
let lit_bytes = lit.as_bytes();
let lit_len = lit_bytes.len() as u32;
let one = self.context.bool_type().const_int(1, false);
let zero = self.context.bool_type().const_zero();
let result = match dwarf_type_opt.map(ghostscope_dwarf::strip_type_aliases) {
Some(TI::PointerType { target_type, .. }) => {
let base = ghostscope_dwarf::strip_type_aliases(target_type.as_ref());
let is_char_like = matches!(base, TI::BaseType { name, size, .. } if name.contains("char") && *size == 1);
if !is_char_like {
return Err(CodeGenError::TypeError(
"automatic string comparison only supports char*".into(),
));
}
let pc_address = self.get_compile_time_context()?.pc_address;
let val_any = self.variable_read_plan_to_llvm_value(&var, pc_address, None)?;
let ptr_i64 = match val_any {
BasicValueEnum::IntValue(iv) => iv,
BasicValueEnum::PointerValue(pv) => self
.builder
.build_ptr_to_int(pv, self.context.i64_type(), "ptr_as_i64")
.map_err(|e| CodeGenError::Builder(e.to_string()))?,
_ => {
return Err(CodeGenError::TypeError(
"pointer value must be integer or pointer".into(),
))
}
};
let need = lit_len + 1;
let (buf_global, ret_len, arr_ty) = self.read_user_cstr_into_buffer(
RuntimeAddress::available(ptr_i64, self.context),
need,
"_gs_strbuf",
)?;
let i64_ty = self.context.i64_type();
let expect_len = i64_ty.const_int(need as u64, false);
let len_ok = self
.builder
.build_int_compare(inkwell::IntPredicate::EQ, ret_len, expect_len, "str_len_ok")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let i32_ty = self.context.i32_type();
let idx0 = i32_ty.const_zero();
let idx_l = i32_ty.const_int(lit_len as u64, false);
let char_ptr = unsafe {
self.builder
.build_gep(arr_ty, buf_global, &[idx0, idx_l], "nul_ptr")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
};
let c = self
.builder
.build_load(self.context.i8_type(), char_ptr, "c_l")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let c = match c {
BasicValueEnum::IntValue(iv) => iv,
_ => return Err(CodeGenError::LLVMError("load did not return i8".into())),
};
let nul_ok = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
c,
self.context.i8_type().const_zero(),
"nul_ok",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let mut acc = self.context.i8_type().const_zero();
for (i, b) in lit_bytes.iter().enumerate() {
let idx_i = i32_ty.const_int(i as u64, false);
let ptr_i = unsafe {
self.builder
.build_gep(arr_ty, buf_global, &[idx0, idx_i], "ch_ptr")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
};
let ch = self
.builder
.build_load(self.context.i8_type(), ptr_i, "ch")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let ch = match ch {
BasicValueEnum::IntValue(iv) => iv,
_ => return Err(CodeGenError::LLVMError("load did not return i8".into())),
};
let expect = self.context.i8_type().const_int(*b as u64, false);
let diff = self
.builder
.build_xor(ch, expect, "diff")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
acc = self
.builder
.build_or(acc, diff, "acc_or")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
}
let eq_bytes = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
acc,
self.context.i8_type().const_zero(),
"acc_zero",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let ok1 = self
.builder
.build_and(len_ok, nul_ok, "ok_len_nul")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder
.build_and(ok1, eq_bytes, "str_eq")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
}
Some(TI::ArrayType {
element_type,
element_count,
total_size,
}) => {
let elem = ghostscope_dwarf::strip_type_aliases(element_type.as_ref());
let is_char_like = matches!(elem, TI::BaseType { name, size, .. } if name.contains("char") && *size == 1);
if !is_char_like {
return Err(CodeGenError::TypeError(
"automatic string comparison only supports char[N]".into(),
));
}
let n_opt = element_count.or_else(|| total_size.map(|ts| ts));
let n = if let Some(nv) = n_opt { nv as u32 } else { 0 };
if n == 0 {
return Err(CodeGenError::TypeError(
"array size unknown for char[N] comparison".into(),
));
}
if lit_len + 1 > n {
return Ok((if is_equal { zero } else { one }).into());
}
let status_ptr = if self.condition_context_active {
Some(self.get_or_create_cond_error_global())
} else {
None
};
let pc_address = self.get_compile_time_context()?.pc_address;
let addr =
self.variable_read_plan_to_runtime_address(&var, pc_address, status_ptr)?;
let (buf_global, status, arr_ty) =
self.read_user_bytes_into_buffer(addr, lit_len + 1, "_gs_arrbuf")?;
let status_ok = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
status,
self.context.i64_type().const_zero(),
"rd_ok",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let i32_ty = self.context.i32_type();
let idx0 = i32_ty.const_zero();
let idx_l = i32_ty.const_int(lit_len as u64, false);
let char_ptr = unsafe {
self.builder
.build_gep(arr_ty, buf_global, &[idx0, idx_l], "nul_ptr")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
};
let c = self
.builder
.build_load(self.context.i8_type(), char_ptr, "c_l")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let c = match c {
BasicValueEnum::IntValue(iv) => iv,
_ => return Err(CodeGenError::LLVMError("load did not return i8".into())),
};
let nul_ok = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
c,
self.context.i8_type().const_zero(),
"nul_ok",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let mut acc = self.context.i8_type().const_zero();
for (i, b) in lit_bytes.iter().enumerate() {
let idx_i = i32_ty.const_int(i as u64, false);
let ptr_i = unsafe {
self.builder
.build_gep(arr_ty, buf_global, &[idx0, idx_i], "ch_ptr")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
};
let ch = self
.builder
.build_load(self.context.i8_type(), ptr_i, "ch")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let ch = match ch {
BasicValueEnum::IntValue(iv) => iv,
_ => return Err(CodeGenError::LLVMError("load did not return i8".into())),
};
let expect = self.context.i8_type().const_int(*b as u64, false);
let diff = self
.builder
.build_xor(ch, expect, "diff")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
acc = self
.builder
.build_or(acc, diff, "acc_or")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
}
let eq_bytes = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
acc,
self.context.i8_type().const_zero(),
"acc_zero",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let ok1 = self
.builder
.build_and(status_ok, nul_ok, "ok_len_nul")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder
.build_and(ok1, eq_bytes, "arr_eq")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
}
None => {
let status_ptr = if self.condition_context_active {
Some(self.get_or_create_cond_error_global())
} else {
None
};
let pc_address = self.get_compile_time_context()?.pc_address;
let addr =
self.variable_read_plan_to_runtime_address(&var, pc_address, status_ptr)?;
match parse_type_name(&var.type_name) {
ParsedKind::PtrChar => {
let ptr_any = self.generate_memory_read(
addr,
ghostscope_dwarf::MemoryAccessSize::U64,
None,
)?;
let ptr_i64 = match ptr_any {
BasicValueEnum::IntValue(iv) => iv,
_ => {
return Err(CodeGenError::LLVMError(
"pointer load did not return integer".to_string(),
))
}
};
let need = lit_len + 1;
let (buf_global, ret_len, arr_ty) = self.read_user_cstr_into_buffer(
RuntimeAddress::available(ptr_i64, self.context),
need,
"_gs_strbuf",
)?;
let i64_ty = self.context.i64_type();
let expect_len = i64_ty.const_int(need as u64, false);
let len_ok = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
ret_len,
expect_len,
"str_len_ok",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let i32_ty = self.context.i32_type();
let idx0 = i32_ty.const_zero();
let idx_l = i32_ty.const_int(lit_len as u64, false);
let char_ptr = unsafe {
self.builder
.build_gep(arr_ty, buf_global, &[idx0, idx_l], "nul_ptr")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
};
let c = self
.builder
.build_load(self.context.i8_type(), char_ptr, "c_l")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let c = match c {
BasicValueEnum::IntValue(iv) => iv,
_ => {
return Err(CodeGenError::LLVMError(
"load did not return i8".into(),
))
}
};
let nul_ok = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
c,
self.context.i8_type().const_zero(),
"nul_ok",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let mut acc = self.context.i8_type().const_zero();
for (i, b) in lit_bytes.iter().enumerate() {
let idx_i = i32_ty.const_int(i as u64, false);
let ptr_i = unsafe {
self.builder
.build_gep(arr_ty, buf_global, &[idx0, idx_i], "ch_ptr")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
};
let ch = self
.builder
.build_load(self.context.i8_type(), ptr_i, "ch")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let ch = match ch {
BasicValueEnum::IntValue(iv) => iv,
_ => {
return Err(CodeGenError::LLVMError(
"load did not return i8".into(),
))
}
};
let expect = self.context.i8_type().const_int(*b as u64, false);
let diff = self
.builder
.build_xor(ch, expect, "diff")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
acc = self
.builder
.build_or(acc, diff, "acc_or")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
}
let eq_bytes = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
acc,
self.context.i8_type().const_zero(),
"acc_zero",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let ok1 = self
.builder
.build_and(len_ok, nul_ok, "ok_len_nul")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder
.build_and(ok1, eq_bytes, "str_eq")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
}
ParsedKind::ArrChar(n_opt) => {
if let Some(n) = n_opt {
if lit_len + 1 > n {
return Ok((if is_equal { zero } else { one }).into());
}
}
let (buf_global, status, arr_ty) =
self.read_user_bytes_into_buffer(addr, lit_len + 1, "_gs_arrbuf")?;
let status_ok = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
status,
self.context.i64_type().const_zero(),
"rd_ok",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let i32_ty = self.context.i32_type();
let idx0 = i32_ty.const_zero();
let idx_l = i32_ty.const_int(lit_len as u64, false);
let char_ptr = unsafe {
self.builder
.build_gep(arr_ty, buf_global, &[idx0, idx_l], "nul_ptr")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
};
let c = self
.builder
.build_load(self.context.i8_type(), char_ptr, "c_l")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let c = match c {
BasicValueEnum::IntValue(iv) => iv,
_ => {
return Err(CodeGenError::LLVMError(
"load did not return i8".into(),
))
}
};
let nul_ok = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
c,
self.context.i8_type().const_zero(),
"nul_ok",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let mut acc = self.context.i8_type().const_zero();
for (i, b) in lit_bytes.iter().enumerate() {
let idx_i = i32_ty.const_int(i as u64, false);
let ptr_i = unsafe {
self.builder
.build_gep(arr_ty, buf_global, &[idx0, idx_i], "ch_ptr")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
};
let ch = self
.builder
.build_load(self.context.i8_type(), ptr_i, "ch")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let ch = match ch {
BasicValueEnum::IntValue(iv) => iv,
_ => {
return Err(CodeGenError::LLVMError(
"load did not return i8".into(),
))
}
};
let expect = self.context.i8_type().const_int(*b as u64, false);
let diff = self
.builder
.build_xor(ch, expect, "diff")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
acc = self
.builder
.build_or(acc, diff, "acc_or")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
}
let eq_bytes = self
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
acc,
self.context.i8_type().const_zero(),
"acc_zero",
)
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
let ok1 = self
.builder
.build_and(status_ok, nul_ok, "ok_len_nul")
.map_err(|e| CodeGenError::Builder(e.to_string()))?;
self.builder
.build_and(ok1, eq_bytes, "arr_eq")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
}
ParsedKind::Other => {
return Err(CodeGenError::TypeError(format!(
"string comparison unsupported for type name '{}' without DWARF type",
var.type_name
)));
}
}
}
Some(_) => {
return Err(CodeGenError::TypeError(
"string comparison only supports char* or char[N]".into(),
));
}
};
let final_bool = if is_equal {
result
} else {
self.builder
.build_not(result, "not_eq")
.map_err(|e| CodeGenError::Builder(e.to_string()))?
};
Ok(final_bool.into())
}
}