diff --git a/src/c/stdlib.c b/src/c/stdlib.c index 39e4fb0f44..62e98108bb 100644 --- a/src/c/stdlib.c +++ b/src/c/stdlib.c @@ -3,3 +3,11 @@ double strtod(const char *nptr, char **endptr); long double strtold(const char *nptr, char **endptr) { return (long double)strtod(nptr, endptr); } + +double relibc_ldtod(const long double* val) { + return (double)(*val); +} + +void relibc_dtold(double val, long double* out) { + *out = (long double)val; +} diff --git a/src/header/setjmp/mod.rs b/src/header/setjmp/mod.rs index 15d21f3bde..e7a70cd527 100644 --- a/src/header/setjmp/mod.rs +++ b/src/header/setjmp/mod.rs @@ -39,8 +39,8 @@ unsafe extern "C" { #[cfg(target_arch = "aarch64")] #[unsafe(no_mangle)] pub unsafe extern "C" fn __relibc_unused_but_exist_to_avoid_linker_error_on_setjmp(arg: c_int) { - /// Workaround to https://www.openwall.com/lists/musl/2023/09/07/2 - /// By keeping setjmp close to sigsetjmp in link time + // Workaround to https://www.openwall.com/lists/musl/2023/09/07/2 + // By keeping setjmp close to sigsetjmp in link time let jb: *mut u64 = core::ptr::null_mut(); unsafe { setjmp(jb); diff --git a/src/header/stdio/printf.rs b/src/header/stdio/printf.rs index f97f873e31..f875c91253 100644 --- a/src/header/stdio/printf.rs +++ b/src/header/stdio/printf.rs @@ -15,13 +15,20 @@ use crate::{ platform::{ self, types::{ - c_char, c_double, c_int, c_long, c_longlong, c_short, c_uchar, c_uint, c_ulong, - c_ulonglong, c_ushort, c_void, intmax_t, ptrdiff_t, size_t, ssize_t, uintmax_t, - wchar_t, wint_t, + c_char, c_double, c_int, c_long, c_longdouble, c_longlong, c_short, c_uchar, c_uint, + c_ulong, c_ulonglong, c_ushort, c_void, intmax_t, ptrdiff_t, size_t, ssize_t, + uintmax_t, wchar_t, wint_t, }, }, }; +#[allow(unused_doc_comments)] +/// cbindgen:ignore +unsafe extern "C" { + pub unsafe fn relibc_ldtod(x: *const c_longdouble) -> c_double; + pub unsafe fn relibc_dtold(x: c_double, out: *mut c_longdouble); +} + // ____ _ _ _ _ // | __ ) ___ (_) | ___ _ __ _ __ | | __ _| |_ ___ _ // | _ \ / _ \| | |/ _ \ '__| '_ \| |/ _` | __/ _ (_) @@ -76,6 +83,10 @@ impl Number { match arg { VaArg::c_char(i) => i as usize, VaArg::c_double(i) => i as usize, + #[cfg(target_pointer_width = "32")] + VaArg::c_longdouble(_) => 0 as usize, + #[cfg(target_pointer_width = "64")] + VaArg::c_longdouble(i) => i as usize, VaArg::c_int(i) => i as usize, VaArg::c_long(i) => i as usize, VaArg::c_longlong(i) => i as usize, @@ -92,6 +103,7 @@ impl Number { pub(crate) enum VaArg { c_char(c_char), c_double(c_double), + c_longdouble(c_longdouble), c_int(c_int), c_long(c_long), c_longlong(c_longlong), @@ -147,6 +159,11 @@ impl VaArg { VaArg::ssize_t(unsafe { ap.arg::() }) } + (FmtKind::AnyNotation, IntKind::LongLong) + | (FmtKind::Decimal, IntKind::LongLong) + | (FmtKind::Scientific, IntKind::LongLong) => { + VaArg::c_longdouble(unsafe { VaArg::extract_longdouble(ap) }) + } (FmtKind::AnyNotation, _) | (FmtKind::Decimal, _) | (FmtKind::Scientific, _) => { VaArg::c_double(unsafe { ap.arg::() }) } @@ -156,6 +173,69 @@ impl VaArg { } } } + #[cfg(target_arch = "x86")] + unsafe fn extract_longdouble(ap: &mut core::ffi::VaList) -> c_longdouble { + todo_skip!(0, "long double in variadic printf is not supported"); + [0, 0, 0] + } + #[cfg(target_arch = "x86_64")] + unsafe fn extract_longdouble(ap: &mut core::ffi::VaList) -> c_longdouble { + // https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.95.pdf (long double) + + // exactly same as core::ffi::VaListImpl but all variables exposed + #[repr(C)] + struct VaListImpl { + gp_offset: i32, + fp_offset: i32, + overflow_arg_area: *mut u8, + reg_save_area: *mut u8, + } + + let ap_impl = unsafe { + // The double deconstruct is intended + let ptr_to_struct = *(ap as *mut core::ffi::VaList as *mut *mut VaListImpl); + &mut *ptr_to_struct + }; + + let ptr = ap_impl.overflow_arg_area as *const c_longdouble; + let val = unsafe { ptr.read() }; + + ap_impl.overflow_arg_area = unsafe { ap_impl.overflow_arg_area.add(16) }; + + val + } + #[cfg(target_arch = "aarch64")] + unsafe fn extract_longdouble(ap: &mut core::ffi::VaList) -> c_longdouble { + // https://c9x.me/compile/bib/abi-arm64.pdf (quad precision) + + // exactly same as core::ffi::VaListImpl but all variables exposed + #[repr(C)] + struct VaListImpl { + stack: *mut u8, + gr_top: *mut u8, + vr_top: *mut u8, + gr_offs: i32, + vr_offs: i32, + } + + let ap_impl: &mut VaListImpl = unsafe { + // The double deconstruct is intended + let ptr_to_struct = *(ap as *mut core::ffi::VaList as *mut *mut VaListImpl); + &mut *ptr_to_struct + }; + + let ptr = unsafe { ap_impl.vr_top.offset(ap_impl.vr_offs as isize) as *const c_longdouble }; + + ap_impl.vr_offs += 16; + + unsafe { ptr.read() } + } + + #[cfg(target_arch = "riscv64")] + unsafe fn extract_longdouble(ap: &mut core::ffi::VaList) -> c_longdouble { + todo_skip!(0, "long double in variadic printf is not supported"); + 0u128 + } unsafe fn transmute(&self, fmtkind: FmtKind, intkind: IntKind) -> VaArg { // At this point, there are conflicting printf arguments. An // example of this is: @@ -168,6 +248,7 @@ impl VaArg { union Untyped { c_char: c_char, c_double: c_double, + c_longdouble: c_longdouble, c_int: c_int, c_long: c_long, c_longlong: c_longlong, @@ -181,6 +262,7 @@ impl VaArg { let untyped = match *self { VaArg::c_char(i) => Untyped { c_char: i }, VaArg::c_double(i) => Untyped { c_double: i }, + VaArg::c_longdouble(i) => Untyped { c_longdouble: i }, VaArg::c_int(i) => Untyped { c_int: i }, VaArg::c_long(i) => Untyped { c_long: i }, VaArg::c_longlong(i) => Untyped { c_longlong: i }, @@ -223,6 +305,11 @@ impl VaArg { VaArg::ssize_t(unsafe { untyped.ssize_t }) } + (FmtKind::AnyNotation, IntKind::LongLong) + | (FmtKind::Decimal, IntKind::LongLong) + | (FmtKind::Scientific, IntKind::LongLong) => { + VaArg::c_longdouble(unsafe { untyped.c_longdouble }) + } (FmtKind::AnyNotation, _) | (FmtKind::Decimal, _) | (FmtKind::Scientific, _) => { VaArg::c_double(unsafe { untyped.c_double }) } @@ -596,7 +683,7 @@ impl<'a, T: c_str::Kind> Iterator for PrintfIter<'a, T> { 'o' | 'u' | 'x' | 'X' => FmtKind::Unsigned, 'b' | 'B' if T::IS_THIN_NOT_WIDE => FmtKind::Unsigned, 'e' | 'E' => FmtKind::Scientific, - 'f' | 'F' => FmtKind::Decimal, + 'f' | 'F' | 'L' => FmtKind::Decimal, 'g' | 'G' => FmtKind::AnyNotation, 's' => FmtKind::String, 'c' => FmtKind::Char, @@ -752,6 +839,7 @@ pub(crate) unsafe fn inner_printf( } { VaArg::c_char(i) => i.to_string(), VaArg::c_double(i) => panic!("this should not be possible"), + VaArg::c_longdouble(i) => panic!("this should not be possible"), VaArg::c_int(i) => i.to_string(), VaArg::c_long(i) => i.to_string(), VaArg::c_longlong(i) => i.to_string(), @@ -802,6 +890,7 @@ pub(crate) unsafe fn inner_printf( } { VaArg::c_char(i) => fmt_int::<_, T>(fmt, i as c_uchar), VaArg::c_double(i) => panic!("this should not be possible"), + VaArg::c_longdouble(i) => panic!("this should not be possible"), VaArg::c_int(i) => fmt_int::<_, T>(fmt, i as c_uint), VaArg::c_long(i) => fmt_int::<_, T>(fmt, i as c_ulong), VaArg::c_longlong(i) => fmt_int::<_, T>(fmt, i as c_ulonglong), @@ -862,6 +951,7 @@ pub(crate) unsafe fn inner_printf( varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) } { VaArg::c_double(i) => i, + VaArg::c_longdouble(i) => unsafe { relibc_ldtod(&i as *const c_longdouble) }, _ => panic!("this should not be possible"), }; if float.is_finite() { @@ -880,6 +970,7 @@ pub(crate) unsafe fn inner_printf( varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) } { VaArg::c_double(i) => i, + VaArg::c_longdouble(i) => unsafe { relibc_ldtod(&i as *const c_longdouble) }, _ => panic!("this should not be possible"), }; if float.is_finite() { @@ -895,6 +986,7 @@ pub(crate) unsafe fn inner_printf( varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) } { VaArg::c_double(i) => i, + VaArg::c_longdouble(i) => unsafe { relibc_ldtod(&i as *const c_longdouble) }, _ => panic!("this should not be possible"), }; if float.is_finite() { diff --git a/src/platform/types.rs b/src/platform/types.rs index e0b925a3b7..1a5305fd9f 100644 --- a/src/platform/types.rs +++ b/src/platform/types.rs @@ -78,6 +78,17 @@ pub type clock_t = c_long; pub type clockid_t = c_int; pub type timer_t = *mut c_void; +// A C long double is 96 bit in x86, 128 bit in other 64-bit targets +// However, both in x86 and x86_64 is actually f80 padded which rust has no underlying support, +// while aarch64 (and possibly riscv64) support full f128 type but behind a feature gate. +// Until rust supporting them, relibc will lose precision to get them working, plus: +// All read operation to this type must be converted from "relibc_ldtod". +// All write operation to this type must be converted with "relibc_dtold". +#[cfg(target_pointer_width = "64")] +pub type c_longdouble = u128; +#[cfg(target_pointer_width = "32")] +pub type c_longdouble = [u32; 3]; + pub use crate::header::bits_pthread::*; #[repr(C, align(16))] diff --git a/tests/expected/bins_dynamic/stdio/printf.stdout b/tests/expected/bins_dynamic/stdio/printf.stdout index aaa9ee635e..a13543aeb2 100644 --- a/tests/expected/bins_dynamic/stdio/printf.stdout +++ b/tests/expected/bins_dynamic/stdio/printf.stdout @@ -52,6 +52,8 @@ Float madness: 0.0001 1E-05 1.000000E-05 +001234.56 +001234.56 Non-finite float madness: %e: inf -inf nan -nan diff --git a/tests/expected/bins_static/stdio/printf.stdout b/tests/expected/bins_static/stdio/printf.stdout index aaa9ee635e..a13543aeb2 100644 --- a/tests/expected/bins_static/stdio/printf.stdout +++ b/tests/expected/bins_static/stdio/printf.stdout @@ -52,6 +52,8 @@ Float madness: 0.0001 1E-05 1.000000E-05 +001234.56 +001234.56 Non-finite float madness: %e: inf -inf nan -nan diff --git a/tests/stdio/printf.c b/tests/stdio/printf.c index 197ad254b9..f794782793 100644 --- a/tests/stdio/printf.c +++ b/tests/stdio/printf.c @@ -65,6 +65,9 @@ int main(void) { printf("%G\n", 0.00001); printf("%E\n", 0.00001); + printf("%2$0*1$.*3$lf\n", 9, 1234.56, 2); + printf("%2$0*1$.*3$Lf\n", 9, 1234.56L, 2); + double nonfinites[] = {INFINITY, -INFINITY, NAN, -NAN}; char *float_formats[] = {"%e", "%E", "%f", "%F", "%g", "%G"}; puts("\nNon-finite float madness:");