For academic’s sake I’ll present my findings. This is the correct and original function which gets re-ordered.
fn nearbyintGeneric(comptime T: type, rint_func: fn (T) callconv(.c) T, x: T) T {
const e = std.c.fetestexcept(std.c.FE_INEXACT);
const result = @trunc(rint_func(x));
if (e == 0) {
_ = std.c.feclearexcept(std.c.FE_INEXACT);
}
return result;
}
The re-ordering in -OReleaseSmall puts the rint call after the if block. I made the attempt with asm volatile ("") but the instructions were still re-ordered.
~/repo/zig$ git diff lib/c/math.zig
diff --git a/lib/c/math.zig b/lib/c/math.zig
index efbe31c1c6..7d304303fa 100644
--- a/lib/c/math.zig
+++ b/lib/c/math.zig
@@ -399,6 +399,7 @@ fn rintl(x: c_longdouble) callconv(.c) c_longdouble {
fn nearbyintGeneric(comptime T: type, rint_func: fn (T) callconv(.c) T, x: T) T {
const e = std.c.fetestexcept(std.c.FE_INEXACT);
const result = @trunc(rint_func(x));
+ asm volatile ("");
if (e == 0) {
_ = std.c.feclearexcept(std.c.FE_INEXACT);
}
I also tried my original approach which was to re-use the result value in the feclearexcept function but the function is still re-ordered
~/repo/zig$ git diff lib/c/math.zig
diff --git a/lib/c/math.zig b/lib/c/math.zig
index efbe31c1c6..508fa8a519 100644
--- a/lib/c/math.zig
+++ b/lib/c/math.zig
@@ -399,8 +399,10 @@ fn rintl(x: c_longdouble) callconv(.c) c_longdouble {
fn nearbyintGeneric(comptime T: type, rint_func: fn (T) callconv(.c) T, x: T) T {
const e = std.c.fetestexcept(std.c.FE_INEXACT);
const result = @trunc(rint_func(x));
+ const cast: f32 = @floatCast(result);
+ const masked: c_int = @bitCast(cast);
if (e == 0) {
- _ = std.c.feclearexcept(std.c.FE_INEXACT);
+ _ = std.c.feclearexcept(std.c.FE_INEXACT | (masked & std.c.FE_INEXACT));
}
return result;
}
With some print statements, this is the output:
~/repo/zig$ git diff lib/c/math.zig
diff --git a/lib/c/math.zig b/lib/c/math.zig
index efbe31c1c6..b928a50f4e 100644
--- a/lib/c/math.zig
+++ b/lib/c/math.zig
@@ -399,9 +399,14 @@ fn rintl(x: c_longdouble) callconv(.c) c_longdouble {
const result = @trunc(rint_func(x));
const cast: f32 = @floatCast(result);
const masked: c_int = @bitCast(cast);
+ std.debug.print("Should be '{}', is {}\n", .{ std.c.FE_INEXACT, std.c.fetestexcept(std.c.FE_INEXACT) });
if (e == 0) {
+ std.debug.print("Inside If: Should be '{}', is {}\n", .{ std.c.FE_INEXACT, std.c.fetestexcept(std.c.FE_INEXACT) });
_ = std.c.feclearexcept(std.c.FE_INEXACT | (masked & std.c.FE_INEXACT));
}
+ std.debug.print("AFTER IF: Should be '{}', is {}\n", .{ 0, std.c.fetestexcept(std.c.FE_INEXACT) });
return result;
}
-------
~/repo/zig$ qemu-riscv64 ./ntest-release-small
Should be '1', is 0
Inside If: Should be '1', is 0
AFTER IF: Should be '0', is 1
and of course in debug mode without the instructions re-ordering the program’s behaviour is correct
~/repo/zig$ qemu-riscv64 ./ntest-debug
Should be '1', is 1
Inside If: Should be '1', is 1
AFTER IF: Should be '0', is 0
I can also confirm that using std.mem.doNotOptimizeAway does prevent the instructions from being re-ordered. I can’t change the signature of the feclearexcept function, as this needs to be exactly equal to the libc function but keeping it in the if block prevents the re-ordering.
~/repo/zig$ git diff lib/c/math.zig
diff --git a/lib/c/math.zig b/lib/c/math.zig
index efbe31c1c6..9078c326ae 100644
--- a/lib/c/math.zig
+++ b/lib/c/math.zig
@@ -400,6 +400,7 @@ fn nearbyintGeneric(comptime T: type, rint_func: fn (T) callconv(.c) T, x: T) T
const e = std.c.fetestexcept(std.c.FE_INEXACT);
const result = @trunc(rint_func(x));
if (e == 0) {
+ _ = std.mem.doNotOptimizeAway(result);
_ = std.c.feclearexcept(std.c.FE_INEXACT);
}
return result;
------
~/repo/zig$ qemu-riscv64 ./ntest-release-small
Should be '1', is 1
Inside If: Should be '1', is 1
AFTER IF: Should be '1', is 0