Commit 291edafa1b

Evan Haas <evan@lagerdata.com>
2021-03-04 04:53:48
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.
1 parent 02737d5
Changed files (2)
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;
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 <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;
+        \\}
+    , "");
 }