Skip to content

libc: implement common abs for various integer sizes #23893

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/c.zig
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ else
std.debug.no_panic;

comptime {
_ = @import("c/inttypes.zig");
_ = @import("c/stdlib.zig");

if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {
// Files specific to musl and wasi-libc.
_ = @import("c/string.zig");
Expand Down
20 changes: 20 additions & 0 deletions lib/c/inttypes.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const std = @import("std");
const common = @import("common.zig");
const builtin = @import("builtin");
const intmax_t = std.c.intmax_t;

comptime {
if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {
// Functions specific to musl and wasi-libc.
@export(&imaxabs, .{ .name = "imaxabs", .linkage = common.linkage, .visibility = common.visibility });
}
}

fn imaxabs(a: intmax_t) callconv(.c) intmax_t {
return @intCast(@abs(a));
}

test imaxabs {
const val: intmax_t = -10;
try std.testing.expectEqual(10, imaxabs(val));
}
39 changes: 39 additions & 0 deletions lib/c/stdlib.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const std = @import("std");
const common = @import("common.zig");
const builtin = @import("builtin");

comptime {
if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {
// Functions specific to musl and wasi-libc.
@export(&abs, .{ .name = "abs", .linkage = common.linkage, .visibility = common.visibility });
@export(&labs, .{ .name = "labs", .linkage = common.linkage, .visibility = common.visibility });
@export(&llabs, .{ .name = "llabs", .linkage = common.linkage, .visibility = common.visibility });
}
}

fn abs(a: c_int) callconv(.c) c_int {
return @intCast(@abs(a));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@intCast is not correct here.

Copy link
Member

@mlugg mlugg May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't it correct? The C standard specifies that abs(minInt(c_int)) will exhibit UB assuming a two's complement representation. C99 7.20.6.1(2):

If the result cannot be represented, the behavior is undefined. [265]
[...]
265) The absolute value of the most negative number cannot be represented in two’s complement.

Copy link
Member

@andrewrk andrewrk May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case, this extremely non-obvious requirement needs to be cited in a comment in the implementation, and someone should have pointed out the motivation for adding @intCast rather than the previous implementation, which does not invoke UB, was intentional to better comply with the standard.

Given the previous review was

As long as it's functionally compatible with the musl implementation, you're free to implement it in a more "Ziggy" way.

It's pretty clear that this UB was an accident, that just so happens to comply with the C standard.

This also brings up another use case for implementing libc in zig: the ability to enable ubsan for the code. Other libcs such as musl will not catch it since they effectively @bitCast rather than @intCast.

Copy link
Member

@mlugg mlugg May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what way is it clear this was an accident? I don't understand what you're getting at. I thought it was well-known that this is UB in C, especially given that there's... kind of no sane definition.

[...] the previous implementation, which does not invoke UB [...]

Huh? This implementation has just as much UB as the implementation the review comment was on, and just as much UB as the musl implementation this PR replaces; in both cases, the negation expression -x is what exhibits the undefined/illegal behavior in the "min int" case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-x in C does not invoke UB, it is a bitcast.

That's my mistake for not noticing the -x in Zig for the UB, but my main point stands:

In that case, this extremely non-obvious requirement needs to be cited in a comment in the implementation, and someone should have pointed out the motivation for adding @intCast rather than the previous implementation, which does not invoke UB, was intentional to better comply with the standard.

and

It's pretty clear that this UB was an accident, that just so happens to comply with the C standard.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, looks like I am wrong again:

andy@bark ~/tmp> cat test.c
#include <stdio.h>
#include <limits.h>

int main() {
    int x = -2147483648;
    printf("x = %d\n", -x);
    return 0;
}
andy@bark ~/tmp> zig-dev run test.c -lc
thread 27213 panic: negation of -2147483648 cannot be represented in type 'int'
/home/andy/tmp/test.c:6:24: 0x10134de in main (test.c)
    printf("x = %d\n", -x);
                       ^
???:?:?: 0x7f92c89ce27d in ??? (libc.so.6)
Unwind information for `libc.so.6:0x7f92c89ce27d` was not available, trace may be incomplete

fish: Job 1, 'zig-dev run test.c -lc' terminated by signal SIGABRT (Abort)

I apologize, I misremembered C semantics.

However, the point stands that intentional UB should be noted along with the citation in the spec that says it is UB.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

intentional UB should be noted

I could perhaps buy that, but... I imagine you'd need a lot of comments if you wanted to do that for every single instance of potential UB in ziglibc, since libc has rather a lot of it. And where are you drawing the line? Should dereferencing a pointer argument have a comment pointing out where in the spec the pointer is assumed to be valid?

I'm not saying ziglibc shouldn't have some kind of UB-commenting rule, but this is definitely a new thing you're coming up with rather than an existing convention, so I certainly don't think it was incorrect for Alex to merge this as-is. All the old implementation (previous iteration of this PR, and musl's implementation) did was hide the UB from you by disguising it behind a negation; OTOH, this implementation makes it obvious, so you know it's there, but can easily verify that it aligns with the spec. Isn't that a clear improvement?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it was incorrect for Alex to merge as-is either. I apologize for the alarm.

I agree it's a clear improvement. So clear that I didn't even realize there was UB in the C code. My reaction was uncalled for.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, I'd be okay with a policy of adding comments for a subset of UB. As Matthew said, we obviously wouldn't want to do it everywhere, but even a subjective evaluation like "does any reviewer feel that it'd be helpful here?" is probably better than nothing.

}

fn labs(a: c_long) callconv(.c) c_long {
return @intCast(@abs(a));
}

fn llabs(a: c_longlong) callconv(.c) c_longlong {
return @intCast(@abs(a));
}

test abs {
const val: c_int = -10;
try std.testing.expectEqual(10, abs(val));
}

test labs {
const val: c_long = -10;
try std.testing.expectEqual(10, labs(val));
}

test llabs {
const val: c_longlong = -10;
try std.testing.expectEqual(10, llabs(val));
}
18 changes: 0 additions & 18 deletions lib/libc/mingw/math/fabsf.c

This file was deleted.

18 changes: 0 additions & 18 deletions lib/libc/mingw/math/fabsl.c

This file was deleted.

7 changes: 0 additions & 7 deletions lib/libc/musl/src/math/aarch64/fabs.c

This file was deleted.

7 changes: 0 additions & 7 deletions lib/libc/musl/src/math/aarch64/fabsf.c

This file was deleted.

15 changes: 0 additions & 15 deletions lib/libc/musl/src/math/arm/fabs.c

This file was deleted.

15 changes: 0 additions & 15 deletions lib/libc/musl/src/math/arm/fabsf.c

This file was deleted.

9 changes: 0 additions & 9 deletions lib/libc/musl/src/math/fabs.c

This file was deleted.

9 changes: 0 additions & 9 deletions lib/libc/musl/src/math/fabsf.c

This file was deleted.

15 changes: 0 additions & 15 deletions lib/libc/musl/src/math/fabsl.c

This file was deleted.

7 changes: 0 additions & 7 deletions lib/libc/musl/src/math/i386/fabs.c

This file was deleted.

7 changes: 0 additions & 7 deletions lib/libc/musl/src/math/i386/fabsf.c

This file was deleted.

7 changes: 0 additions & 7 deletions lib/libc/musl/src/math/i386/fabsl.c

This file was deleted.

16 changes: 0 additions & 16 deletions lib/libc/musl/src/math/mips/fabs.c

This file was deleted.

16 changes: 0 additions & 16 deletions lib/libc/musl/src/math/mips/fabsf.c

This file was deleted.

15 changes: 0 additions & 15 deletions lib/libc/musl/src/math/powerpc/fabs.c

This file was deleted.

15 changes: 0 additions & 15 deletions lib/libc/musl/src/math/powerpc/fabsf.c

This file was deleted.

7 changes: 0 additions & 7 deletions lib/libc/musl/src/math/powerpc64/fabs.c

This file was deleted.

7 changes: 0 additions & 7 deletions lib/libc/musl/src/math/powerpc64/fabsf.c

This file was deleted.

15 changes: 0 additions & 15 deletions lib/libc/musl/src/math/riscv32/fabs.c

This file was deleted.

15 changes: 0 additions & 15 deletions lib/libc/musl/src/math/riscv32/fabsf.c

This file was deleted.

15 changes: 0 additions & 15 deletions lib/libc/musl/src/math/riscv64/fabs.c

This file was deleted.

15 changes: 0 additions & 15 deletions lib/libc/musl/src/math/riscv64/fabsf.c

This file was deleted.

15 changes: 0 additions & 15 deletions lib/libc/musl/src/math/s390x/fabs.c

This file was deleted.

15 changes: 0 additions & 15 deletions lib/libc/musl/src/math/s390x/fabsf.c

This file was deleted.

Loading
Loading