From 291edafa1b3e6f56f88c3d1c542bdb25e99e45d1 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Wed, 3 Mar 2021 19:53:48 -0800 Subject: [PATCH] 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. --- src/translate_c.zig | 44 +++++++++++++++++++++++++++++++++++++++ test/run_translated_c.zig | 26 +++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/src/translate_c.zig b/src/translate_c.zig index ed80a13058..2770ffb4cb 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -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; diff --git a/test/run_translated_c.zig b/test/run_translated_c.zig index 07e449733f..977400be82 100644 --- a/test/run_translated_c.zig +++ b/test/run_translated_c.zig @@ -1131,4 +1131,30 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void { \\ return 0; \\} , ""); + + cases.add("pointer arithmetic with signed operand", + \\#include + \\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; + \\} + , ""); }