Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -255,9 +255,41 @@ __attribute__((noinline)) static void scalar_probe(int iter)
&format_len);
}

volatile int promotion_negative = -1;
volatile struct {
unsigned int u24:24;
unsigned int u25:25;
unsigned int u31:31;
unsigned int u32:32;
} promotion_fields = {1, 1, 1, 1};
volatile _Bool promotion_expected24, promotion_expected25;
volatile _Bool promotion_expected31, promotion_expected32;
volatile int64_t promotion_expected_bitnot[4];
volatile int64_t promotion_expected_division[4];
volatile int64_t promotion_expected_remainder[4];

int main(void)
{
int iter = 0;
/* The native C compiler supplies an independent promotion oracle. */
promotion_expected24 = promotion_negative < promotion_fields.u24;
promotion_expected25 = promotion_negative < promotion_fields.u25;
promotion_expected31 = promotion_negative < promotion_fields.u31;
promotion_expected32 = promotion_negative < promotion_fields.u32;

/* Widen only the results so the operands retain native integer promotions. */
promotion_expected_bitnot[0] = ~promotion_fields.u24;
promotion_expected_bitnot[1] = ~promotion_fields.u25;
promotion_expected_bitnot[2] = ~promotion_fields.u31;
promotion_expected_bitnot[3] = ~promotion_fields.u32;
promotion_expected_division[0] = promotion_fields.u24 / promotion_negative;
promotion_expected_division[1] = promotion_fields.u25 / promotion_negative;
promotion_expected_division[2] = promotion_fields.u31 / promotion_negative;
promotion_expected_division[3] = promotion_fields.u32 / promotion_negative;
promotion_expected_remainder[0] = promotion_fields.u24 % promotion_negative;
promotion_expected_remainder[1] = promotion_fields.u25 % promotion_negative;
promotion_expected_remainder[2] = promotion_fields.u31 % promotion_negative;
promotion_expected_remainder[3] = promotion_fields.u32 % promotion_negative;

while (iter < 20000) {
scalar_probe(iter++);
Expand Down
54 changes: 54 additions & 0 deletions e2e-tests/tests/scalar_types_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,60 @@ use common::{init, OptimizationLevel, FIXTURES};
use std::path::Path;
use std::time::Duration;

#[tokio::test]
async fn test_c_bitfield_integer_promotions_match_the_native_compiler() -> anyhow::Result<()> {
init();
let binary = FIXTURES.get_test_binary("scalar_types_program")?;
let target = spawn_scalar_types_binary(&binary).await?;
let script = r#"
trace scalar_anchor {
print "PROMOTED:{}:{}:{}:{}", promotion_negative < promotion_fields.u24, promotion_negative < promotion_fields.u25, promotion_negative < promotion_fields.u31, promotion_negative < promotion_fields.u32;
print "NATIVE:{}:{}:{}:{}", promotion_expected24, promotion_expected25, promotion_expected31, promotion_expected32;
}
"#;
let (code, stdout, stderr) = run_ghostscope_with_script_for_target(script, 3, &target).await?;
target.terminate().await?;
assert_eq!(code, 0, "stderr={stderr} stdout={stdout}");
assert!(stdout.contains("NATIVE:true:true:true:false"), "{stdout}");
assert!(stdout.contains("PROMOTED:true:true:true:false"), "{stdout}");
Ok(())
}

#[tokio::test]
async fn test_c_bitfield_integer_operators_match_the_native_compiler() -> anyhow::Result<()> {
init();
let binary = FIXTURES.get_test_binary("scalar_types_program")?;
let target = spawn_scalar_types_binary(&binary).await?;
let script = r#"
trace scalar_anchor {
print "PROMOTED_BITNOT:{}:{}:{}:{}", ~promotion_fields.u24, ~promotion_fields.u25, ~promotion_fields.u31, ~promotion_fields.u32;
print "NATIVE_BITNOT:{}:{}:{}:{}", promotion_expected_bitnot[0], promotion_expected_bitnot[1], promotion_expected_bitnot[2], promotion_expected_bitnot[3];
print "PROMOTED_DIVISION:{}:{}:{}:{}", promotion_fields.u24 / promotion_negative, promotion_fields.u25 / promotion_negative, promotion_fields.u31 / promotion_negative, promotion_fields.u32 / promotion_negative;
print "NATIVE_DIVISION:{}:{}:{}:{}", promotion_expected_division[0], promotion_expected_division[1], promotion_expected_division[2], promotion_expected_division[3];
print "PROMOTED_REMAINDER:{}:{}:{}:{}", promotion_fields.u24 % promotion_negative, promotion_fields.u25 % promotion_negative, promotion_fields.u31 % promotion_negative, promotion_fields.u32 % promotion_negative;
print "NATIVE_REMAINDER:{}:{}:{}:{}", promotion_expected_remainder[0], promotion_expected_remainder[1], promotion_expected_remainder[2], promotion_expected_remainder[3];
}
"#;
let (code, stdout, stderr) = run_ghostscope_with_script_for_target(script, 3, &target).await?;
target.terminate().await?;
assert_eq!(code, 0, "stderr={stderr} stdout={stdout}");
for expected in [
"BITNOT:-2:-2:-2:4294967294",
"DIVISION:-1:-1:-1:0",
"REMAINDER:0:0:0:1",
] {
assert!(
stdout.contains(&format!("NATIVE_{expected}")),
"Expected native result {expected}. STDOUT: {stdout}"
);
assert!(
stdout.contains(&format!("PROMOTED_{expected}")),
"Expected traced result {expected}. STDOUT: {stdout}"
);
}
Ok(())
}

async fn run_ghostscope_with_script_for_target(
script_content: &str,
timeout_secs: u64,
Expand Down
48 changes: 47 additions & 1 deletion ghostscope-dwarf/src/semantics/c_integer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use crate::TypeInfo;
pub struct CIntegerComparisonType {
pub size: u64,
pub is_unsigned: bool,
/// Effective value width for bitfields; storage bytes alone lose promotion rules.
pub bitfield_width: Option<u8>,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
Expand All @@ -19,10 +21,14 @@ pub struct CIntegerComparisonPlan {

impl CIntegerComparisonType {
pub fn promoted(self) -> Self {
if self.size < 4 {
let fits_int = self.bitfield_width.map_or(self.size < 4, |width| {
width < 32 || (width == 32 && !self.is_unsigned)
});
if fits_int {
Self {
size: 4,
is_unsigned: false,
bitfield_width: None,
}
} else {
self
Expand All @@ -33,6 +39,7 @@ impl CIntegerComparisonType {
Self {
size: 8,
is_unsigned: false,
bitfield_width: None,
}
}
}
Expand All @@ -48,6 +55,7 @@ pub fn c_integer_comparison_type(ty: &TypeInfo) -> Option<CIntegerComparisonType
(is_unsigned || is_signed).then_some(CIntegerComparisonType {
size: *size,
is_unsigned,
bitfield_width: None,
})
}
TypeInfo::EnumType {
Expand All @@ -67,6 +75,7 @@ pub fn c_integer_comparison_type(ty: &TypeInfo) -> Option<CIntegerComparisonType
..
} => c_integer_comparison_type(underlying_type).map(|mut ty| {
ty.size = (*bit_size as u64).max(1).div_ceil(8);
ty.bitfield_width = Some(*bit_size);
ty
}),
TypeInfo::TypedefType {
Expand Down Expand Up @@ -145,6 +154,36 @@ mod tests {
int_type("int", 4, crate::constants::DW_ATE_signed.0 as u16)
}

#[test]
fn bitfield_promotions_use_value_width_at_the_int_boundary() {
for unsigned in [false, true] {
for width in [1, 24, 25, 31, 32] {
let bitfield = TypeInfo::BitfieldType {
underlying_type: Box::new(int_type(
if unsigned { "unsigned int" } else { "int" },
4,
if unsigned {
crate::constants::DW_ATE_unsigned.0 as u16
} else {
crate::constants::DW_ATE_signed.0 as u16
},
)),
bit_offset: 0,
bit_size: width,
};
let ty = c_integer_comparison_type(&bitfield).unwrap();
assert_eq!(ty.is_unsigned, unsigned, "storage signedness is unchanged");
let plan = usual_c_arithmetic_comparison_plan(
c_integer_comparison_type(&signed_int()).unwrap(),
ty,
);
assert_eq!(plan.size, 4);
assert_eq!(plan.is_unsigned, unsigned && width == 32);
assert_eq!(ty.promoted().promoted(), ty.promoted());
}
}
}

#[test]
fn integer_comparison_type_handles_enums_and_bitfields() {
let enum_type = TypeInfo::EnumType {
Expand All @@ -162,6 +201,7 @@ mod tests {
Some(CIntegerComparisonType {
size: 4,
is_unsigned: true,
bitfield_width: None,
})
);

Expand All @@ -175,6 +215,7 @@ mod tests {
Some(CIntegerComparisonType {
size: 2,
is_unsigned: false,
bitfield_width: Some(9),
})
);
}
Expand All @@ -187,6 +228,7 @@ mod tests {
Some(CIntegerComparisonType {
size: 1,
is_unsigned: false,
bitfield_width: None,
})
);
assert!(!is_c_signed_integer_type(&bool_type));
Expand All @@ -198,10 +240,12 @@ mod tests {
let u8_type = CIntegerComparisonType {
size: 1,
is_unsigned: true,
bitfield_width: None,
};
let i8_type = CIntegerComparisonType {
size: 1,
is_unsigned: false,
bitfield_width: None,
};

assert_eq!(
Expand All @@ -218,10 +262,12 @@ mod tests {
let u64_type = CIntegerComparisonType {
size: 8,
is_unsigned: true,
bitfield_width: None,
};
let i32_type = CIntegerComparisonType {
size: 4,
is_unsigned: false,
bitfield_width: None,
};

assert_eq!(
Expand Down
Loading