Skip to content

Commit

Permalink
translate-c: support GCC/Clang pointer subtraction extension
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mlugg committed Aug 27, 2024
1 parent d3c6f71 commit 93cb44c
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 11 deletions.
28 changes: 17 additions & 11 deletions src/translate_c.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1596,25 +1596,31 @@ fn transBinaryOperator(
// @divExact(@bitCast(<platform-ptrdiff_t>, @intFromPtr(lhs) -% @intFromPtr(rhs)), @sizeOf(<lhs target type>))
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.
const lhs_expr = stmt.getLHS();
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;
}
Expand Down
16 changes: 16 additions & 0 deletions test/cases/translate_c/void_pointer_subtraction.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include <stddef.h>
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)));
// }

0 comments on commit 93cb44c

Please sign in to comment.