From 93cb44c80582dd02b63b02e7bb7e54d7ad8a4ebc Mon Sep 17 00:00:00 2001 From: mlugg Date: Mon, 26 Aug 2024 21:50:14 +0100 Subject: [PATCH] translate-c: support GCC/Clang pointer subtraction extension Pointer subtraction on `void *` or function pointers is UB by the C spec, but is permitted by GCC and Clang as an extension. So, avoid crashing translate-c in such cases, and follow the extension behavior -- there's nothing else that could really be intended. --- src/translate_c.zig | 28 +++++++++++-------- .../translate_c/void_pointer_subtraction.c | 16 +++++++++++ 2 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 test/cases/translate_c/void_pointer_subtraction.c diff --git a/src/translate_c.zig b/src/translate_c.zig index 14a0def274..835121e0cc 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -1596,6 +1596,11 @@ fn transBinaryOperator( // @divExact(@bitCast(, @intFromPtr(lhs) -% @intFromPtr(rhs)), @sizeOf()) const ptrdiff_type = try transQualTypeIntWidthOf(c, qt, true); + const bitcast = try Tag.as.create(c.arena, .{ + .lhs = ptrdiff_type, + .rhs = try Tag.bit_cast.create(c.arena, infixOpNode), + }); + // C standard requires that pointer subtraction operands are of the same type, // otherwise it is undefined behavior. So we can assume the left and right // sides are the same QualType and arbitrarily choose left. @@ -1603,18 +1608,19 @@ fn transBinaryOperator( const lhs_qt = getExprQualType(c, lhs_expr); const lhs_qt_translated = try transQualType(c, scope, lhs_qt, lhs_expr.getBeginLoc()); const c_pointer = getContainer(c, lhs_qt_translated).?; - const elem_type = c_pointer.castTag(.c_pointer).?.data.elem_type; - const sizeof = try Tag.sizeof.create(c.arena, elem_type); - const bitcast = try Tag.as.create(c.arena, .{ - .lhs = ptrdiff_type, - .rhs = try Tag.bit_cast.create(c.arena, infixOpNode), - }); - - return Tag.div_exact.create(c.arena, .{ - .lhs = bitcast, - .rhs = sizeof, - }); + if (c_pointer.castTag(.c_pointer)) |c_pointer_payload| { + const sizeof = try Tag.sizeof.create(c.arena, c_pointer_payload.data.elem_type); + return Tag.div_exact.create(c.arena, .{ + .lhs = bitcast, + .rhs = sizeof, + }); + } else { + // This is an opaque/incomplete type. This subtraction exhibits Undefined Behavior by the C99 spec. + // However, allowing subtraction on `void *` and function pointers is a commonly used extension. + // So, just return the value in byte units, mirroring the behavior of this language extension as implemented by GCC and Clang. + return bitcast; + } } return infixOpNode; } diff --git a/test/cases/translate_c/void_pointer_subtraction.c b/test/cases/translate_c/void_pointer_subtraction.c new file mode 100644 index 0000000000..69ce5a9d4d --- /dev/null +++ b/test/cases/translate_c/void_pointer_subtraction.c @@ -0,0 +1,16 @@ +#include +ptrdiff_t sub_ptr(void *a, void *b) { + return a - b; +} + +// translate-c +// c_frontend=clang +// target=x86_64-linux +// +// pub export fn sub_ptr(arg_a: ?*anyopaque, arg_b: ?*anyopaque) ptrdiff_t { +// var a = arg_a; +// _ = &a; +// var b = arg_b; +// _ = &b; +// return @as(c_long, @bitCast(@intFromPtr(a) -% @intFromPtr(b))); +// }