Skip to content

Commit dd699f3

Browse files
committed
Don't use host C runtime for printing compile-time reals, use LLVM
Primary motivation are apparent printf issues for the hex format and quadruple C `long double` in Android's Bionic runtime for x86_64. This required an extra Termux patch until now: https://github.com/termux/termux-packages/blob/3c0e8cd65e6abc2709462c0fe421de5a9f877362/packages/ldc/ldc-x64-sprintf.patch
1 parent 734d849 commit dd699f3

File tree

2 files changed

+203
-1
lines changed

2 files changed

+203
-1
lines changed

dmd/root/ctfloat.d

+106-1
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ extern (C++) struct CTFloat
207207
// implemented in gen/ctfloat.cpp
208208
@system
209209
static real_t parse(const(char)* literal, bool* isOutOfRange = null);
210+
@system
211+
static int sprint(char* str, char fmt, real_t x);
210212
}
211213
else
212214
{
@@ -230,7 +232,6 @@ extern (C++) struct CTFloat
230232
*isOutOfRange = (errno == ERANGE);
231233
return r;
232234
}
233-
}
234235

235236
@system
236237
static int sprint(char* str, char fmt, real_t x)
@@ -258,6 +259,7 @@ extern (C++) struct CTFloat
258259
}
259260
}
260261
}
262+
}
261263

262264
// Constant real values 0, 1, -1 and 0.5.
263265
__gshared real_t zero;
@@ -288,3 +290,106 @@ extern (C++) struct CTFloat
288290
}
289291
}
290292
}
293+
294+
version (IN_LLVM)
295+
{
296+
version (Android) { /* double/quadruple real_t */ } else
297+
{
298+
version (X86) version = real_t_X87;
299+
version (X86_64) version = real_t_X87;
300+
}
301+
302+
// Test parsing and printing of real_t values.
303+
unittest
304+
{
305+
CTFloat.initialize();
306+
307+
static void printAndCheck(char format, real_t x, string expected) nothrow
308+
{
309+
char[32] buffer = void;
310+
const length = CTFloat.sprint(buffer.ptr, format, x);
311+
assert(length < buffer.length);
312+
printf("'%s', expected '%.*s'\n", buffer.ptr, cast(int) expected.length, expected.ptr);
313+
assert(buffer[0 .. length] == expected);
314+
assert(buffer[length] == 0);
315+
}
316+
317+
static struct T
318+
{
319+
nothrow:
320+
321+
real_t x;
322+
string expected_g, expected_a, expected_A;
323+
324+
this(real_t x, string g, string a, string A)
325+
{
326+
this.x = x;
327+
expected_g = g;
328+
expected_a = a;
329+
expected_A = A;
330+
}
331+
332+
this(string x, string g, string a, string A)
333+
{
334+
this.x = CTFloat.parse(x.ptr);
335+
expected_g = g;
336+
expected_a = a;
337+
expected_A = A;
338+
}
339+
340+
void test() const
341+
{
342+
printAndCheck('g', x, expected_g);
343+
printAndCheck('a', x, expected_a);
344+
printAndCheck('A', x, expected_A);
345+
}
346+
}
347+
348+
immutable T[] generic_ts = [
349+
T( CTFloat.nan, "nan", "nan", "NAN"),
350+
T(-CTFloat.nan, "-nan", "-nan", "-NAN"),
351+
T( CTFloat.infinity, "inf", "inf", "INF"),
352+
T(-CTFloat.infinity, "-inf", "-inf", "-INF"),
353+
T( "0.0", "0.0", "0x0p+0", "0X0P+0"),
354+
T("-0.0", "-0.0", "-0x0p+0", "-0X0P+0"),
355+
T( "0x1p-1", "0.5", "0x1p-1", "0X1P-1"),
356+
T( "0x3p-3", "0.375", "0x1.8p-2", "0X1.8P-2"),
357+
T( "0x1p+0", "1.0", "0x1p+0", "0X1P+0"),
358+
T( "0x3p-1", "1.5", "0x1.8p+0", "0X1.8P+0"),
359+
T("-0x3p-2", "-0.75", "-0x1.8p-1", "-0X1.8P-1"),
360+
T( "100.0", "100.0", "0x1.9p+6", "0X1.9P+6"),
361+
];
362+
363+
foreach (t; generic_ts)
364+
{
365+
version (AArch64)
366+
{
367+
// FPU may not preserve NaN sign (depending on 'default NaN mode' control bit)
368+
if (t.expected_g == "-nan")
369+
continue;
370+
}
371+
t.test();
372+
}
373+
374+
version (real_t_X87)
375+
{
376+
immutable T[] x87_ts = [
377+
T( "1e+300", "1e+300", "0x1.7e43c8800759ba5ap+996", "0X1.7E43C8800759BA5AP+996"),
378+
T( "1e-300", "9.99999e-301", "0x1.56e1fc2f8f358d94p-997", "0X1.56E1FC2F8F358D94P-997"),
379+
T( "1.2345678901234567890123456789", "1.23457", "0x1.3c0ca428c59fb71ap+0", "0X1.3C0CA428C59FB71AP+0"),
380+
T("-12.345678901234567890123456789", "-12.3457", "-0x1.8b0fcd32f707a4e2p+3", "-0X1.8B0FCD32F707A4E2P+3"),
381+
T( "123456.78901234567890123456789", "123457.0", "0x1.e240c9fcb68cd4c4p+16", "0X1.E240C9FCB68CD4C4P+16"),
382+
T("-123456.78901234567890123456789", "-123457.0", "-0x1.e240c9fcb68cd4c4p+16", "-0X1.E240C9FCB68CD4C4P+16"),
383+
T( "1234567.8901234567890123456789", "1.23457e+06", "0x1.2d687e3df21804fap+20", "0X1.2D687E3DF21804FAP+20"),
384+
T("-1234567.8901234567890123456789", "-1.23457e+06", "-0x1.2d687e3df21804fap+20", "-0X1.2D687E3DF21804FAP+20"),
385+
T( "0.0001234567890123456789012345", "0.000123457", "0x1.02e85be180b7447cp-13", "0X1.02E85BE180B7447CP-13"),
386+
T("-0.0001234567890123456789012345", "-0.000123457", "-0x1.02e85be180b7447cp-13", "-0X1.02E85BE180B7447CP-13"),
387+
T( "0.0000123456789012345678901234", "1.23457e-05", "0x1.9e409302678ba0c8p-17", "0X1.9E409302678BA0C8P-17"),
388+
T("-0.0000123456789012345678901234", "-1.23457e-05", "-0x1.9e409302678ba0c8p-17", "-0X1.9E409302678BA0C8P-17"),
389+
];
390+
391+
foreach (t; x87_ts)
392+
t.test();
393+
}
394+
}
395+
}

gen/ctfloat.cpp

+97
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,100 @@ bool CTFloat::isFloat64LiteralOutOfRange(const char *literal) {
127127
parseLiteral(APFloat::IEEEdouble AP_SEMANTICS_PARENS, literal, &isOutOfRange);
128128
return isOutOfRange;
129129
}
130+
131+
////////////////////////////////////////////////////////////////////////////////
132+
133+
int CTFloat::sprint(char *str, char fmt, real_t x) {
134+
assert(fmt == 'g' || fmt == 'a' || fmt == 'A');
135+
const bool uppercase = fmt == 'A';
136+
137+
APFloat ap(0.0);
138+
toAPFloat(x, ap);
139+
140+
// We try to keep close to C printf and handle a few divergences of the LLVM
141+
// to-string utility functions.
142+
143+
int length = 0;
144+
if (isNaN(x)) {
145+
if (copysign(one, x) != one) {
146+
str[0] = '-';
147+
++length;
148+
}
149+
memcpy(str + length, uppercase ? "NAN" : "nan", 3);
150+
length += 3;
151+
} else if (isInfinity(x)) {
152+
if (!isIdentical(x, infinity)) {
153+
str[0] = '-';
154+
++length;
155+
}
156+
memcpy(str + length, uppercase ? "INF" : "inf", 3);
157+
length += 3;
158+
} else if (fmt == 'a' || fmt == 'A') {
159+
length =
160+
ap.convertToHexString(str, 0, uppercase, APFloat::rmNearestTiesToEven);
161+
162+
// insert a '+' prefix for non-negative exponents (incl. 0) as visual aid
163+
const char p = uppercase ? 'P' : 'p';
164+
for (int i = length - 2; i >= 0; --i) {
165+
if (str[i] == p) {
166+
if (str[i + 1] != '-' && str[i + 1] != '+') {
167+
for (int j = length - 1; j > i; --j)
168+
str[j + 1] = str[j];
169+
str[i + 1] = '+';
170+
++length;
171+
}
172+
173+
break;
174+
}
175+
}
176+
} else {
177+
assert(fmt == 'g');
178+
179+
llvm::SmallString<32> buffer;
180+
ap.toString(buffer, /*FormatPrecision=*/6, /*FormatMaxPadding=*/4);
181+
182+
bool needsFPSuffix = true;
183+
ptrdiff_t commaIndex = -1;
184+
for (size_t i = 0; i < buffer.size(); ++i) {
185+
if (buffer[i] == '.') {
186+
needsFPSuffix = false;
187+
commaIndex = i;
188+
} else if (buffer[i] == 'E') {
189+
needsFPSuffix = false;
190+
191+
if (!uppercase)
192+
buffer[i] = 'e';
193+
194+
// remove excessive comma+zeros in scientific notation
195+
// (1.000000e+100 => 1e+100)
196+
if (commaIndex > 0) {
197+
bool onlyZeros = std::all_of(&buffer[commaIndex + 1], &buffer[i],
198+
[](char c) { return c == '0'; });
199+
if (onlyZeros) {
200+
buffer.erase(&buffer[commaIndex], &buffer[i]);
201+
i = commaIndex;
202+
commaIndex = -1;
203+
}
204+
}
205+
206+
// ensure at least 2 digits for the exponent after the sign
207+
// (1e+6 => 1e+06)
208+
if (buffer.size() - (i + 2) == 1)
209+
buffer.insert(&buffer[i + 2], '0');
210+
211+
break;
212+
}
213+
}
214+
215+
// no comma for non-scientific notation? then add FP suffix to distinguish
216+
// from integers (100 => 100.0)
217+
if (needsFPSuffix)
218+
buffer += ".0";
219+
220+
length = static_cast<int>(buffer.size());
221+
memcpy(str, buffer.data(), length);
222+
}
223+
224+
str[length] = 0; // null-terminate
225+
return length;
226+
}

0 commit comments

Comments
 (0)