Skip to content

Commit 157e5d3

Browse files
committed
translate-c: handle signed array subscripts
A rather complicated workaround for handling signed array subscripts. Once `[*]T + isize` is allowed, this can be removed. Fixes #8556
1 parent 84d5cc3 commit 157e5d3

File tree

2 files changed

+133
-10
lines changed

2 files changed

+133
-10
lines changed

src/translate_c.zig

Lines changed: 93 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3032,30 +3032,113 @@ fn transMemberExpr(c: *Context, scope: *Scope, stmt: *const clang.MemberExpr, re
30323032
return maybeSuppressResult(c, scope, result_used, node);
30333033
}
30343034

3035+
/// ptr[subscr] (`subscr` is a signed integer expression, `ptr` a pointer) becomes:
3036+
/// (blk: {
3037+
/// const tmp = subscr;
3038+
/// if (tmp >= 0) break :blk ptr + @intCast(usize, tmp) else break :blk ptr - ~@bitCast(usize, @intCast(isize, tmp) +% -1);
3039+
/// }).*
3040+
/// Todo: rip this out once `[*]T + isize` becomes valid.
3041+
fn transSignedArrayAccess(
3042+
c: *Context,
3043+
scope: *Scope,
3044+
container_expr: *const clang.Expr,
3045+
subscr_expr: *const clang.Expr,
3046+
result_used: ResultUsed,
3047+
) TransError!Node {
3048+
var block_scope = try Scope.Block.init(c, scope, true);
3049+
defer block_scope.deinit();
3050+
3051+
const tmp = try block_scope.makeMangledName(c, "tmp");
3052+
3053+
const subscr_node = try transExpr(c, &block_scope.base, subscr_expr, .used);
3054+
const subscr_decl = try Tag.var_simple.create(c.arena, .{ .name = tmp, .init = subscr_node });
3055+
try block_scope.statements.append(subscr_decl);
3056+
3057+
const tmp_ref = try Tag.identifier.create(c.arena, tmp);
3058+
3059+
const container_node = try transExpr(c, &block_scope.base, container_expr, .used);
3060+
3061+
const cond_node = try Tag.greater_than_equal.create(c.arena, .{ .lhs = tmp_ref, .rhs = Tag.zero_literal.init() });
3062+
3063+
const then_value = try Tag.add.create(c.arena, .{
3064+
.lhs = container_node,
3065+
.rhs = try Tag.int_cast.create(c.arena, .{
3066+
.lhs = try Tag.identifier.create(c.arena, "usize"),
3067+
.rhs = tmp_ref,
3068+
}),
3069+
});
3070+
3071+
const then_body = try Tag.break_val.create(c.arena, .{
3072+
.label = block_scope.label,
3073+
.val = then_value,
3074+
});
3075+
3076+
const minuend = container_node;
3077+
const signed_size = try Tag.int_cast.create(c.arena, .{
3078+
.lhs = try Tag.identifier.create(c.arena, "isize"),
3079+
.rhs = tmp_ref,
3080+
});
3081+
const to_cast = try Tag.add_wrap.create(c.arena, .{
3082+
.lhs = signed_size,
3083+
.rhs = try Tag.negate.create(c.arena, Tag.one_literal.init()),
3084+
});
3085+
const bitcast_node = try Tag.bit_cast.create(c.arena, .{
3086+
.lhs = try Tag.identifier.create(c.arena, "usize"),
3087+
.rhs = to_cast,
3088+
});
3089+
const subtrahend = try Tag.bit_not.create(c.arena, bitcast_node);
3090+
const difference = try Tag.sub.create(c.arena, .{
3091+
.lhs = minuend,
3092+
.rhs = subtrahend,
3093+
});
3094+
const else_body = try Tag.break_val.create(c.arena, .{
3095+
.label = block_scope.label,
3096+
.val = difference,
3097+
});
3098+
3099+
const if_node = try Tag.@"if".create(c.arena, .{
3100+
.cond = cond_node,
3101+
.then = then_body,
3102+
.@"else" = else_body,
3103+
});
3104+
3105+
try block_scope.statements.append(if_node);
3106+
const block_node = try block_scope.complete(c);
3107+
3108+
const derefed = try Tag.deref.create(c.arena, block_node);
3109+
3110+
return maybeSuppressResult(c, &block_scope.base, result_used, derefed);
3111+
}
3112+
30353113
fn transArrayAccess(c: *Context, scope: *Scope, stmt: *const clang.ArraySubscriptExpr, result_used: ResultUsed) TransError!Node {
3036-
var base_stmt = stmt.getBase();
3114+
const base_stmt = stmt.getBase();
3115+
const base_qt = getExprQualType(c, base_stmt);
3116+
const is_vector = cIsVector(base_qt);
3117+
3118+
const subscr_expr = stmt.getIdx();
3119+
const subscr_qt = getExprQualType(c, subscr_expr);
3120+
const is_longlong = cIsLongLongInteger(subscr_qt);
3121+
const is_signed = cIsSignedInteger(subscr_qt);
30373122

30383123
// Unwrap the base statement if it's an array decayed to a bare pointer type
30393124
// so that we index the array itself
3125+
var unwrapped_base = base_stmt;
30403126
if (@ptrCast(*const clang.Stmt, base_stmt).getStmtClass() == .ImplicitCastExprClass) {
30413127
const implicit_cast = @ptrCast(*const clang.ImplicitCastExpr, base_stmt);
30423128

30433129
if (implicit_cast.getCastKind() == .ArrayToPointerDecay) {
3044-
base_stmt = implicit_cast.getSubExpr();
3130+
unwrapped_base = implicit_cast.getSubExpr();
30453131
}
30463132
}
30473133

3048-
const container_node = try transExpr(c, scope, base_stmt, .used);
3049-
3050-
// cast if the index is long long or signed
3051-
const subscr_expr = stmt.getIdx();
3052-
const qt = getExprQualType(c, subscr_expr);
3053-
const is_longlong = cIsLongLongInteger(qt);
3054-
const is_signed = cIsSignedInteger(qt);
3134+
// Special case: actual pointer (not decayed array) and signed integer subscript
3135+
// See discussion at https://github.com/ziglang/zig/pull/8589
3136+
if (is_signed and (base_stmt == unwrapped_base) and !is_vector) return transSignedArrayAccess(c, scope, base_stmt, subscr_expr, result_used);
30553137

3138+
const container_node = try transExpr(c, scope, unwrapped_base, .used);
30563139
const rhs = if (is_longlong or is_signed) blk: {
30573140
// check if long long first so that signed long long doesn't just become unsigned long long
3058-
var typeid_node = if (is_longlong) try Tag.identifier.create(c.arena, "usize") else try transQualTypeIntWidthOf(c, qt, false);
3141+
const typeid_node = if (is_longlong) try Tag.identifier.create(c.arena, "usize") else try transQualTypeIntWidthOf(c, subscr_qt, false);
30593142
break :blk try Tag.int_cast.create(c.arena, .{ .lhs = typeid_node, .rhs = try transExpr(c, scope, subscr_expr, .used) });
30603143
} else try transExpr(c, scope, subscr_expr, .used);
30613144

test/run_translated_c.zig

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1476,4 +1476,44 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
14761476
\\ return 0;
14771477
\\}
14781478
, "");
1479+
1480+
cases.add("signed array subscript. Issue #8556",
1481+
\\#include <stdint.h>
1482+
\\#include <stdlib.h>
1483+
\\#define TEST_NEGATIVE(type) { type x = -1; if (ptr[x] != 42) abort(); }
1484+
\\#define TEST_UNSIGNED(type) { type x = 2; if (arr[x] != 42) abort(); }
1485+
\\int main(void) {
1486+
\\ int arr[] = {40, 41, 42, 43};
1487+
\\ int *ptr = arr + 3;
1488+
\\ if (ptr[-1] != 42) abort();
1489+
\\ TEST_NEGATIVE(int);
1490+
\\ TEST_NEGATIVE(long);
1491+
\\ TEST_NEGATIVE(long long);
1492+
\\ TEST_NEGATIVE(int64_t);
1493+
\\ TEST_NEGATIVE(__int128);
1494+
\\ TEST_UNSIGNED(unsigned);
1495+
\\ TEST_UNSIGNED(unsigned long);
1496+
\\ TEST_UNSIGNED(unsigned long long);
1497+
\\ TEST_UNSIGNED(uint64_t);
1498+
\\ TEST_UNSIGNED(size_t);
1499+
\\ TEST_UNSIGNED(unsigned __int128);
1500+
\\ return 0;
1501+
\\}
1502+
, "");
1503+
1504+
cases.add("Ensure side-effects only evaluated once for signed array indices",
1505+
\\#include <stdlib.h>
1506+
\\int main(void) {
1507+
\\ int foo[] = {1, 2, 3, 4};
1508+
\\ int *p = foo;
1509+
\\ int idx = 1;
1510+
\\ if ((++p)[--idx] != 2) abort();
1511+
\\ if (p != foo + 1) abort();
1512+
\\ if (idx != 0) abort();
1513+
\\ if ((p++)[idx++] != 2) abort();
1514+
\\ if (p != foo + 2) abort();
1515+
\\ if (idx != 1) abort();
1516+
\\ return 0;
1517+
\\}
1518+
, "");
14791519
}

0 commit comments

Comments
 (0)