translate-c: enable pointer arithmetic with signed integer operand

Given a pointer operand `ptr` and a signed integer operand `idx`

`ptr + idx` and `idx + ptr` -> ptr + @bitCast(usize, @intCast(isize, idx))
`ptr - idx` -> ptr - @bitCast(usize, @intCast(isize, idx))

Thanks @LemonBoy for pointing out that we can take advantage of wraparound
to dramatically simplify the code.
This commit is contained in:
Evan Haas 2021-03-03 19:53:48 -08:00 committed by Veikka Tuominen
parent 02737d535a
commit 291edafa1b
2 changed files with 70 additions and 0 deletions

View File

@ -1127,6 +1127,44 @@ fn transOffsetOfExpr(
return fail(c, error.UnsupportedTranslation, expr.getBeginLoc(), "TODO: implement complex OffsetOfExpr translation", .{});
}
/// Translate an arithmetic expression with a pointer operand and a signed-integer operand.
/// Zig requires a usize argument for pointer arithmetic, so we intCast to isize and then
/// bitcast to usize; pointer wraparound make the math work.
/// Zig pointer addition is not commutative (unlike C); the pointer operand needs to be on the left.
/// The + operator in C is not a sequence point so it should be safe to switch the order if necessary.
fn transCreatePointerArithmeticSignedOp(
c: *Context,
scope: *Scope,
stmt: *const clang.BinaryOperator,
result_used: ResultUsed,
) TransError!Node {
const is_add = stmt.getOpcode() == .Add;
const lhs = stmt.getLHS();
const rhs = stmt.getRHS();
const swap_operands = is_add and cIsSignedInteger(getExprQualType(c, lhs));
const swizzled_lhs = if (swap_operands) rhs else lhs;
const swizzled_rhs = if (swap_operands) lhs else rhs;
const lhs_node = try transExpr(c, scope, swizzled_lhs, .used);
const rhs_node = try transExpr(c, scope, swizzled_rhs, .used);
const intcast_node = try Tag.int_cast.create(c.arena, .{
.lhs = try Tag.identifier.create(c.arena, "isize"),
.rhs = rhs_node,
});
const bitcast_node = try Tag.bit_cast.create(c.arena, .{
.lhs = try Tag.identifier.create(c.arena, "usize"),
.rhs = intcast_node,
});
const arith_args = .{ .lhs = lhs_node, .rhs = bitcast_node };
const arith_node = try if (is_add) Tag.add.create(c.arena, arith_args) else Tag.sub.create(c.arena, arith_args);
return maybeSuppressResult(c, scope, result_used, arith_node);
}
fn transBinaryOperator(
c: *Context,
scope: *Scope,
@ -1184,6 +1222,12 @@ fn transBinaryOperator(
.LOr => {
return transCreateNodeBoolInfixOp(c, scope, stmt, .@"or", result_used);
},
.Add, .Sub => {
// `ptr + idx` and `idx + ptr` -> ptr + @bitCast(usize, @intCast(isize, idx))
// `ptr - idx` -> ptr - @bitCast(usize, @intCast(isize, idx))
if (qualTypeIsPtr(qt) and (cIsSignedInteger(getExprQualType(c, stmt.getLHS())) or
cIsSignedInteger(getExprQualType(c, stmt.getRHS())))) return transCreatePointerArithmeticSignedOp(c, scope, stmt, result_used);
},
else => {},
}
var op_id: Tag = undefined;

View File

@ -1131,4 +1131,30 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
\\ return 0;
\\}
, "");
cases.add("pointer arithmetic with signed operand",
\\#include <stdlib.h>
\\int main() {
\\ int array[10];
\\ int *x = &array[5];
\\ int *y;
\\ int idx = 0;
\\ y = x + ++idx;
\\ if (y != x + 1 || y != &array[6]) abort();
\\ y = idx + x;
\\ if (y != x + 1 || y != &array[6]) abort();
\\ y = x - idx;
\\ if (y != x - 1 || y != &array[4]) abort();
\\
\\ idx = 0;
\\ y = --idx + x;
\\ if (y != x - 1 || y != &array[4]) abort();
\\ y = idx + x;
\\ if (y != x - 1 || y != &array[4]) abort();
\\ y = x - idx;
\\ if (y != x + 1 || y != &array[6]) abort();
\\
\\ return 0;
\\}
, "");
}