diff --git a/docs/cutover/abi-mapping.tsv b/docs/cutover/abi-mapping.tsv index 7e2f5dd..dc1e670 100644 --- a/docs/cutover/abi-mapping.tsv +++ b/docs/cutover/abi-mapping.tsv @@ -56,6 +56,7 @@ map2check_non_det_loff_t __VERIFIER_nondet_loff_t 2.5 idem primeira linha da fam map2check_non_det_sector_t __VERIFIER_nondet_sector_t 2.5 idem primeira linha da família; sem equivalente núcleo; export novo map2check_non_det_uint __VERIFIER_nondet_uint - idem primeira linha da família map2check_non_det_double __VERIFIER_nondet_double - idem primeira linha da família +map2check_non_det_float __VERIFIER_nondet_float - idem primeira linha da família (FLOAT da 9.0, issue #74) map2check_nondet_int map2check_save_nondet_log_int - tipos de NonDetFunctions.hpp:77-92 (NON_DET_FUNCTIONS_HELPER); C: (i32 line,i32 scope,i32 value,ptr fn) (double: valor double; pointer: ptrtoint; char/short/long/etc.: cast para i32) → núcleo (ffi.rs:305): (line:i32,scope:i32,type_var:i32,value:T*,function_name) → non_det_log_t*; o passe grava o valor num alloca e passa o ponteiro + type_var map2check_nondet_unsigned map2check_save_nondet_log_unsigned 2.5 idem primeira linha da família; o passe grava o valor num alloca e passa o ponteiro + type_var; sem equivalente núcleo; export novo map2check_nondet_char map2check_save_nondet_log_char - idem primeira linha da família; o passe grava o valor num alloca e passa o ponteiro + type_var @@ -72,3 +73,4 @@ map2check_nondet_loff_t map2check_save_nondet_log_loff_t 2.5 idem primeira linha map2check_nondet_sector_t map2check_save_nondet_log_sector_t 2.5 idem primeira linha da família; o passe grava o valor num alloca e passa o ponteiro + type_var; sem equivalente núcleo; export novo map2check_nondet_uint map2check_save_nondet_log_uint - idem primeira linha da família; o passe grava o valor num alloca e passa o ponteiro + type_var map2check_nondet_double map2check_save_nondet_log_double - idem primeira linha da família; o passe grava o valor num alloca e passa o ponteiro + type_var +map2check_nondet_float map2check_save_nondet_log_float - idem primeira linha da família; FLOAT 16 da 9.0 (issue #74), valor float no alloca diff --git a/rust/src/output/legacy_files.rs b/rust/src/output/legacy_files.rs index afe3a45..7c6d872 100644 --- a/rust/src/output/legacy_files.rs +++ b/rust/src/output/legacy_files.rs @@ -77,6 +77,52 @@ pub fn c_double(value: f64) -> String { } } +/// `%.9g` of glibc for a `float`, as Map2Check 9.0's `NonDetLog.c` prints a +/// `FLOAT` row: nine significant digits round-trip any `f32`, where `%lf`'s +/// six fixed decimals print 1e-30 as 0, a different input. The test-suite +/// writer copies the value column verbatim into ``, so the text must +/// be one a validator's `scanf` reads back as the same float. +pub fn c_float(value: f32) -> String { + /// Drops the trailing zeros of a fraction, and its point if nothing is + /// left, as `%g` does. + fn strip(digits: &str) -> &str { + if digits.contains('.') { + digits.trim_end_matches('0').trim_end_matches('.') + } else { + digits + } + } + let sign = if value.is_sign_negative() { "-" } else { "" }; + if value.is_nan() { + return format!("{sign}nan"); + } + if value.is_infinite() { + return format!("{sign}inf"); + } + let magnitude = f64::from(value.abs()); + if magnitude == 0.0 { + return format!("{sign}0"); + } + // %g picks the style from the exponent X of the value rounded to nine + // significant digits (C11 7.21.6.1): exponential when X < -4 or X >= 9. + let scientific = format!("{magnitude:.8e}"); + // `{:e}` always writes `e`. + let Some((mantissa, exponent)) = scientific.split_once('e') else { + return format!("{sign}{scientific}"); + }; + let Ok(exponent) = exponent.parse::() else { + return format!("{sign}{scientific}"); + }; + let body = if !(-4..9).contains(&exponent) { + let mark = if exponent < 0 { '-' } else { '+' }; + format!("{}e{mark}{:02}", strip(mantissa), exponent.abs()) + } else { + let fixed = format!("{magnitude:.*}", (8 - exponent) as usize); + strip(&fixed).to_owned() + }; + format!("{sign}{body}") +} + /// Text of the verdict, as `write_property` prints it /// (`PropertyGenerator.c:50-106`), and the extra per-property file the C /// runtime writes next to `map2check_property`. `line` is printed with @@ -225,8 +271,8 @@ pub fn write_trackbb_log(dir: &Path, rows: &[BasicBlockEntry]) -> io::Result<()> /// through `CreatePtrToInt(v, i32)` (`:224-238`, `:291-292`), the low 32 /// bits of the address. `nondet_log_to_file` then prints `UNSIGNED` with /// `%u`, `DOUBLE` with `%lf` and every other type with `%d` -/// (`NonDetLog.c:45-54`). `float` has no develop counterpart and is logged as -/// a double. +/// (`NonDetLog.c:45-54`). `float` has no develop counterpart; it is logged +/// as Map2Check 9.0 logs it, `FLOAT` 16 with `%.9g` ([`c_float`]). pub fn nondet_value_and_type(value: &NonDetValue) -> (String, i32) { /// `trunc` to `i32`, as `CreateIntegerCast` narrows a wide integer. fn low32(v: u64) -> String { @@ -248,7 +294,9 @@ pub fn nondet_value_and_type(value: &NonDetValue) -> (String, i32) { NonDetValue::SectorT(v) => (low32(v), 12), NonDetValue::UInt(v) => ((v as i32).to_string(), 13), NonDetValue::Short(v) => ((v as u16).to_string(), 14), - NonDetValue::Float(v) => (c_double(f64::from(v)), 15), + // Map2Check 9.0 gave `float` its own type, `FLOAT` 16, printed with + // %.9g (`NonDetLog.c`, issue #74). + NonDetValue::Float(v) => (c_float(v), 16), NonDetValue::Double(v) => (c_double(v), 15), } } @@ -910,6 +958,38 @@ mod tests { assert_eq!(c_double(-f64::NAN), "-nan"); } + /// `printf("%.9g", f)` of glibc 2.35 for each bit pattern. + #[test] + fn c_float_matches_glibc_g9() { + let golden: &[(u32, &str)] = &[ + (0x0000_0000, "0"), + (0x8000_0000, "-0"), + (0x0da2_4260, "1e-30"), + (0xbfb3_1773, "-1.39915311"), + (0x3dcc_cccd, "0.100000001"), + (0x4ceb_79a3, "123456792"), + (0x4e6e_6b28, "1e+09"), + (0x38d1_b717, "9.99999975e-05"), + (0x3727_c5ac, "9.99999975e-06"), + (0x4b80_0000, "16777216"), + (0x7f7f_ffff, "3.40282347e+38"), + (0x0080_0000, "1.17549435e-38"), + (0x0000_0001, "1.40129846e-45"), + (0x4cbe_bc20, "100000000"), + (0x3f00_0000, "0.5"), + (0xc020_0000, "-2.5"), + (0x3eaa_aaab, "0.333333343"), + (0x7fc0_0000, "nan"), + (0xffc0_0000, "-nan"), + (0x7f80_0000, "inf"), + (0xff80_0000, "-inf"), + (0x42f6_e979, "123.456001"), + ]; + for &(bits, text) in golden { + assert_eq!(c_float(f32::from_bits(bits)), text, "bits {bits:#010x}"); + } + } + #[test] fn nondet_rows_use_develop_types_and_value_formats() { use crate::nondet::NonDetValue as V; @@ -928,7 +1008,7 @@ mod tests { ); assert_eq!( nondet_value_and_type(&V::Float(0.5)), - ("0.500000".into(), 15) + ("0.5".into(), 16) ); assert_eq!( nondet_value_and_type(&V::Double(2.5)),