Skip to content

Commit c04e71b

Browse files
fix(math): C23 setpayload/setpayloadsig reject non-integer payloads + zero *res
Continuing the zero-test-coverage scan into the NaN-payload functions: setpayload/setpayloadsig (+f32) used `payload as u64`, which silently TRUNCATED a non-integer payload (1.5 -> 1) and SUCCEEDED, and on failure left *res unchanged. glibc requires a non-negative integer payload in range and, on failure, sets *res to +0: setpayload(1.5) -> ret 1, *res = +0 (fl: ret 0, payload 1) setpayload(NaN) -> ret 1, *res = +0 (fl: ret 0, payload 0) setpayload(-1) -> ret 1, *res = +0 (fl: ret 1, *res untouched) setpayloadsig(0) -> ret 1, *res = +0 (sNaN needs payload >= 1) Rewrote all four impls to validate `>= lo && < 2^(p) && == trunc()` and zero *res on failure. getpayload/getpayloadf were already correct (verified bit-exact, incl. signaling NaN and the -1 non-NaN result). New gate conformance_diff_payload: 64 golden tuples from a gcc -fno-builtin oracle (payload fns may be IFUNCs), value bits + return code. 116 math_abi unit tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 817c7fd commit c04e71b

2 files changed

Lines changed: 158 additions & 28 deletions

File tree

crates/frankenlibc-abi/src/math_abi.rs

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4725,45 +4725,54 @@ fn getpayloadf_impl(x: *const f32) -> f32 {
47254725
let bits = val.to_bits();
47264726
(bits & 0x003F_FFFF) as f32
47274727
}
4728+
// C23 setpayload/setpayloadsig: the payload must be a non-negative INTEGER in
4729+
// range. A non-integer (1.5), NaN, inf or out-of-range value fails (returns 1)
4730+
// and sets *res to +0 — glibc does NOT truncate (the old `payload as u64`
4731+
// silently accepted 1.5 as 1 and never zeroed *res). setpayloadsig additionally
4732+
// requires payload >= 1 (payload 0 would yield an infinity, not an sNaN).
47284733
fn setpayload_impl(res: *mut f64, payload: f64) -> c_int {
4729-
let p = payload as u64;
4730-
if payload < 0.0 || p >= (1u64 << 51) {
4731-
return 1;
4732-
}
4733-
unsafe {
4734-
*res = f64::from_bits(0x7FF8_0000_0000_0000 | p);
4734+
if payload >= 0.0 && payload < 2_251_799_813_685_248.0 && payload == payload.trunc() {
4735+
unsafe {
4736+
*res = f64::from_bits(0x7FF8_0000_0000_0000 | payload as u64);
4737+
}
4738+
0
4739+
} else {
4740+
unsafe { *res = 0.0 };
4741+
1
47354742
}
4736-
0
47374743
}
47384744
fn setpayloadf_impl(res: *mut f32, payload: f32) -> c_int {
4739-
let p = payload as u32;
4740-
if payload < 0.0f32 || p >= (1u32 << 22) {
4741-
return 1;
4742-
}
4743-
unsafe {
4744-
*res = f32::from_bits(0x7FC0_0000 | p);
4745+
if payload >= 0.0 && payload < 4_194_304.0 && payload == payload.trunc() {
4746+
unsafe {
4747+
*res = f32::from_bits(0x7FC0_0000 | payload as u32);
4748+
}
4749+
0
4750+
} else {
4751+
unsafe { *res = 0.0 };
4752+
1
47454753
}
4746-
0
47474754
}
47484755
fn setpayloadsig_impl(res: *mut f64, payload: f64) -> c_int {
4749-
let p = payload as u64;
4750-
if payload < 0.0 || p == 0 || p >= (1u64 << 51) {
4751-
return 1;
4752-
}
4753-
unsafe {
4754-
*res = f64::from_bits(0x7FF0_0000_0000_0000 | p);
4756+
if payload >= 1.0 && payload < 2_251_799_813_685_248.0 && payload == payload.trunc() {
4757+
unsafe {
4758+
*res = f64::from_bits(0x7FF0_0000_0000_0000 | payload as u64);
4759+
}
4760+
0
4761+
} else {
4762+
unsafe { *res = 0.0 };
4763+
1
47554764
}
4756-
0
47574765
}
47584766
fn setpayloadsigf_impl(res: *mut f32, payload: f32) -> c_int {
4759-
let p = payload as u32;
4760-
if payload < 0.0f32 || p == 0 || p >= (1u32 << 22) {
4761-
return 1;
4762-
}
4763-
unsafe {
4764-
*res = f32::from_bits(0x7F80_0000 | p);
4767+
if payload >= 1.0 && payload < 4_194_304.0 && payload == payload.trunc() {
4768+
unsafe {
4769+
*res = f32::from_bits(0x7F80_0000 | payload as u32);
4770+
}
4771+
0
4772+
} else {
4773+
unsafe { *res = 0.0 };
4774+
1
47654775
}
4766-
0
47674776
}
47684777
#[cfg_attr(not(debug_assertions), unsafe(no_mangle))]
47694778
pub unsafe extern "C" fn getpayload(x: *const f64) -> f64 {
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//! Conformance gate for C23 NaN-payload fns getpayload/setpayload/setpayloadsig
2+
//! (+f32) vs host glibc. setpayload* require a non-negative INTEGER payload in
3+
//! range and set *res to +0 on failure; getpayload extracts the mantissa
4+
//! payload (or -1 for a non-NaN). Golden tuples captured from a gcc
5+
//! -fno-builtin oracle (these may be IFUNCs). fl exercised via Rust paths.
6+
#![cfg(target_os = "linux")]
7+
#![allow(unsafe_code)]
8+
use frankenlibc_abi::math_abi as fl;
9+
// setpayload/setpayloadsig f64: (payload_bits, ret, out_bits)
10+
const SP64: &[(u64, i32, u64)] = &[
11+
(0x0000000000000000, 0, 0x7ff8000000000000),
12+
(0x3ff0000000000000, 0, 0x7ff8000000000001),
13+
(0x4014000000000000, 0, 0x7ff8000000000005),
14+
(0x3ff8000000000000, 1, 0x0000000000000000),
15+
(0xbff0000000000000, 1, 0x0000000000000000),
16+
(0x431ffffffffffffc, 0, 0x7fffffffffffffff),
17+
(0x4320000000000000, 1, 0x0000000000000000),
18+
(0x4008000000000000, 0, 0x7ff8000000000003),
19+
(0x7ff8000000000000, 1, 0x0000000000000000),
20+
(0x7ff0000000000000, 1, 0x0000000000000000),
21+
(0x404559999999999a, 1, 0x0000000000000000),
22+
(0x3fe0000000000000, 1, 0x0000000000000000),
23+
];
24+
const SS64: &[(u64, i32, u64)] = &[
25+
(0x0000000000000000, 1, 0x0000000000000000),
26+
(0x3ff0000000000000, 0, 0x7ff0000000000001),
27+
(0x4014000000000000, 0, 0x7ff0000000000005),
28+
(0x3ff8000000000000, 1, 0x0000000000000000),
29+
(0xbff0000000000000, 1, 0x0000000000000000),
30+
(0x431ffffffffffffc, 0, 0x7ff7ffffffffffff),
31+
(0x4320000000000000, 1, 0x0000000000000000),
32+
(0x4008000000000000, 0, 0x7ff0000000000003),
33+
(0x7ff8000000000000, 1, 0x0000000000000000),
34+
(0x7ff0000000000000, 1, 0x0000000000000000),
35+
(0x404559999999999a, 1, 0x0000000000000000),
36+
(0x3fe0000000000000, 1, 0x0000000000000000),
37+
];
38+
const SP32: &[(u32, i32, u32)] = &[
39+
(0x00000000, 0, 0x7fc00000),
40+
(0x3f800000, 0, 0x7fc00001),
41+
(0x40a00000, 0, 0x7fc00005),
42+
(0x3fc00000, 1, 0x00000000),
43+
(0xbf800000, 1, 0x00000000),
44+
(0x4a7ffffc, 0, 0x7fffffff),
45+
(0x4a800000, 1, 0x00000000),
46+
(0x40400000, 0, 0x7fc00003),
47+
(0x7fc00000, 1, 0x00000000),
48+
(0x7f800000, 1, 0x00000000),
49+
(0x422acccd, 1, 0x00000000),
50+
(0x3f000000, 1, 0x00000000),
51+
];
52+
const SS32: &[(u32, i32, u32)] = &[
53+
(0x00000000, 1, 0x00000000),
54+
(0x3f800000, 0, 0x7f800001),
55+
(0x40a00000, 0, 0x7f800005),
56+
(0x3fc00000, 1, 0x00000000),
57+
(0xbf800000, 1, 0x00000000),
58+
(0x4a7ffffc, 0, 0x7fbfffff),
59+
(0x4a800000, 1, 0x00000000),
60+
(0x40400000, 0, 0x7f800003),
61+
(0x7fc00000, 1, 0x00000000),
62+
(0x7f800000, 1, 0x00000000),
63+
(0x422acccd, 1, 0x00000000),
64+
(0x3f000000, 1, 0x00000000),
65+
];
66+
const GP64: &[(u64, u64)] = &[
67+
(0x7ff8000000000000, 0x0000000000000000),
68+
(0x7ff8000000000005, 0x4014000000000000),
69+
(0x7ff0000000000005, 0x4014000000000000),
70+
(0xfff8000000000005, 0x4014000000000000),
71+
(0x3ff0000000000000, 0xbff0000000000000),
72+
(0x7ff0000000000000, 0xbff0000000000000),
73+
(0x0000000000000000, 0xbff0000000000000),
74+
(0x7fffffffffffffff, 0x431ffffffffffffc),
75+
];
76+
const GP32: &[(u32, u32)] = &[
77+
(0x7fc00000, 0x00000000),
78+
(0x7fc00005, 0x40a00000),
79+
(0x7f800005, 0x40a00000),
80+
(0xffc00005, 0x40a00000),
81+
(0x3f800000, 0xbf800000),
82+
(0x7f800000, 0xbf800000),
83+
(0x00000000, 0xbf800000),
84+
(0x7fffffff, 0x4a7ffffc),
85+
];
86+
87+
#[test]
88+
fn payload_matches_glibc() {
89+
let mut div: Vec<String> = Vec::new();
90+
for &(pb, ret, out) in SP64 {
91+
let mut o = f64::from_bits(0x1234567890abcdef);
92+
let r = unsafe { fl::setpayload(&mut o, f64::from_bits(pb)) };
93+
if r != ret || o.to_bits() != out { div.push(format!("setpayload(0x{:016x}): fl=ret{}/0x{:016x} glibc=ret{}/0x{:016x}", pb, r, o.to_bits(), ret, out)); }
94+
}
95+
for &(pb, ret, out) in SS64 {
96+
let mut o = f64::from_bits(0x1234567890abcdef);
97+
let r = unsafe { fl::setpayloadsig(&mut o, f64::from_bits(pb)) };
98+
if r != ret || o.to_bits() != out { div.push(format!("setpayloadsig(0x{:016x}): fl=ret{}/0x{:016x} glibc=ret{}/0x{:016x}", pb, r, o.to_bits(), ret, out)); }
99+
}
100+
for &(pb, ret, out) in SP32 {
101+
let mut o = f32::from_bits(0xdeadbeef);
102+
let r = unsafe { fl::setpayloadf(&mut o, f32::from_bits(pb)) };
103+
if r != ret || o.to_bits() != out { div.push(format!("setpayloadf(0x{:08x}): fl=ret{}/0x{:08x} glibc=ret{}/0x{:08x}", pb, r, o.to_bits(), ret, out)); }
104+
}
105+
for &(pb, ret, out) in SS32 {
106+
let mut o = f32::from_bits(0xdeadbeef);
107+
let r = unsafe { fl::setpayloadsigf(&mut o, f32::from_bits(pb)) };
108+
if r != ret || o.to_bits() != out { div.push(format!("setpayloadsigf(0x{:08x}): fl=ret{}/0x{:08x} glibc=ret{}/0x{:08x}", pb, r, o.to_bits(), ret, out)); }
109+
}
110+
for &(xb, pl) in GP64 {
111+
let x = f64::from_bits(xb);
112+
let g = unsafe { fl::getpayload(&x) }.to_bits();
113+
if g != pl { div.push(format!("getpayload(0x{:016x}): fl=0x{:016x} glibc=0x{:016x}", xb, g, pl)); }
114+
}
115+
for &(xb, pl) in GP32 {
116+
let x = f32::from_bits(xb);
117+
let g = unsafe { fl::getpayloadf(&x) }.to_bits();
118+
if g != pl { div.push(format!("getpayloadf(0x{:08x}): fl=0x{:08x} glibc=0x{:08x}", xb, g, pl)); }
119+
}
120+
assert!(div.is_empty(), "payload divergences vs glibc ({}):\n {}", div.len(), div.join("\n "));
121+
}

0 commit comments

Comments
 (0)