I want to make a piece of code compatible with both Zig 0.11.0 and 0.12.0. The following code proves to be a stumbling block:
pub fn abs(v: anytype) @TypeOf(v) {
return if (@hasDecl(std.math, "fabs")) std.math.fabs(v) else @abs(v);
}
It wouldn’t compile in 0.11.0 because @abs()
is missing (it was @fabs()
before). I wonder if there’s a way to get the right CPU instruction in there without the use of the builtin. An undocumented internal function, perhaps? If not, I’m just going to use stick a if (v < 0)
statement in there.
Could you use the builtins module to check the version of the compiler and then use the right builtin call?
This might not be the best solution for your case, but I came up with this in response to a similar question.
nyc
April 25, 2024, 6:09pm
4
the sign on floats is just the most significant bit.
const bits: u64 = @bitCast(v); // assuming a 64 bit float.
const mask = (1 << 63) - 1;
const abs_bits = bits & mask;
const abs_v: f64 = @bitCast(abs_bits);
That should basically be a single isstruction. And yo ucan do some comptime magic to get the proper bit size integer.
2 Likes
Can’t do that. This is for a source code translator. The output has to be a single file.
I think I’m just going to go with std.math.sign(v) * v
for now. That works when v
is a vector.
If you try to use @abs
with 0.11, the lexer will complain, so you can’t even try to wrap this inside a comptime
block. You need to prevent the lexer from seeing it. I think this would work:
File newAbs.zig:
pub fn abs(v: anytype) @TypeOf(v){
return @abs(v);
}
File oldAbs.zig:
pub fn abs(v: anytype) @TypeOf(v){
return switch(@typeInfo(@TypeOf(v))){
.Int, .ComptimeInt => std.math.abs(v),
.Float, .ComptimeFloat => std.math.fabs(v),
else => unreachable,
};
}
File abs.zig:
const builtin = @import("builtin");
pub const abs = if(builtin.zig_version.minor <= 11)
@import("oldAbs.zig").abs
else
@import("newAbs.zig").abs
3 Likes
Are you sure that this works in both 0.12
and 0.11
? Under 0.11.0
I get the following error:
newAbs.zig:2:12: error: invalid builtin function: '@abs'
return @abs(v);
No, not sure. I didn’t download 0.11 to test it. I assumed imports were lazy.
The imports are lazy, but grammar is always checked.
I did not expect the parser to validate the builtin functions (in AstGen.zig
).
Holy cow, I found a solution! @max(-v, v)
amazingly compiles to the same code as @abs(v)
and @fabs(v)
:
pub fn abs1(n: f32) f32 {
return @max(-n, n);
}
pub fn abs2(n: f32) f32 {
return @abs(n);
}
export const exports = [_]*const anyopaque{
@ptrCast(&abs1),
@ptrCast(&abs2),
};
.LCPI0_0:
.long 0x7fffffff
example.abs1:
vandps xmm0, xmm0, dword ptr [rip + .LCPI0_0]{1to4}
ret
exports:
.quad example.abs1
.quad example.abs1
God bless godbolt
6 Likes
nyc
April 26, 2024, 1:01pm
11
cool. I never thought about it. i wonder if there is a peephole pass in llvm for this specifically? There is no neg instruction for floats, so you would think the straight forward gen would be x1 = x0 ^ 0x80000000
then MAXSS
. I wonder how it gets from that to the bitmask.