Merge branch 'long-double' into 'master'

Implement long double in printf

Closes #273

See merge request redox-os/relibc!1065
This commit is contained in:
Jeremy Soller
2026-03-04 09:51:24 -07:00
7 changed files with 124 additions and 6 deletions
+8
View File
@@ -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;
}
+2 -2
View File
@@ -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);
+96 -4
View File
@@ -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::<ssize_t>() })
}
(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::<c_double>() })
}
@@ -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<T: c_str::Kind>(
} {
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<T: c_str::Kind>(
} {
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<T: c_str::Kind>(
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<T: c_str::Kind>(
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<T: c_str::Kind>(
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() {
+11
View File
@@ -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))]
@@ -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
@@ -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
+3
View File
@@ -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:");