Skip to content

Commit 33535d5

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 24dfa61 commit 33535d5

File tree

2 files changed

+133
-10
lines changed

2 files changed

+133
-10
lines changed

src/translate_c.zig

+93-10
Original file line numberDiff line numberDiff line change
@@ -3038,30 +3038,113 @@ fn transMemberExpr(c: *Context, scope: *Scope, stmt: *const clang.MemberExpr, re
30383038
return maybeSuppressResult(c, scope, result_used, node);
30393039
}
30403040

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

30443129
// Unwrap the base statement if it's an array decayed to a bare pointer type
30453130
// so that we index the array itself
3131+
var unwrapped_base = base_stmt;
30463132
if (@ptrCast(*const clang.Stmt, base_stmt).getStmtClass() == .ImplicitCastExprClass) {
30473133
const implicit_cast = @ptrCast(*const clang.ImplicitCastExpr, base_stmt);
30483134

30493135
if (implicit_cast.getCastKind() == .ArrayToPointerDecay) {
3050-
base_stmt = implicit_cast.getSubExpr();
3136+
unwrapped_base = implicit_cast.getSubExpr();
30513137
}
30523138
}
30533139

3054-
const container_node = try transExpr(c, scope, base_stmt, .used);
3055-
3056-
// cast if the index is long long or signed
3057-
const subscr_expr = stmt.getIdx();
3058-
const qt = getExprQualType(c, subscr_expr);
3059-
const is_longlong = cIsLongLongInteger(qt);
3060-
const is_signed = cIsSignedInteger(qt);
3140+
// Special case: actual pointer (not decayed array) and signed integer subscript
3141+
// See discussion at https://github.com/ziglang/zig/pull/8589
3142+
if (is_signed and (base_stmt == unwrapped_base) and !is_vector) return transSignedArrayAccess(c, scope, base_stmt, subscr_expr, result_used);
30613143

3144+
const container_node = try transExpr(c, scope, unwrapped_base, .used);
30623145
const rhs = if (is_longlong or is_signed) blk: {
30633146
// check if long long first so that signed long long doesn't just become unsigned long long
3064-
var typeid_node = if (is_longlong) try Tag.identifier.create(c.arena, "usize") else try transQualTypeIntWidthOf(c, qt, false);
3147+
const typeid_node = if (is_longlong) try Tag.identifier.create(c.arena, "usize") else try transQualTypeIntWidthOf(c, subscr_qt, false);
30653148
break :blk try Tag.int_cast.create(c.arena, .{ .lhs = typeid_node, .rhs = try transExpr(c, scope, subscr_expr, .used) });
30663149
} else try transExpr(c, scope, subscr_expr, .used);
30673150

test/run_translated_c.zig

+40
Original file line numberDiff line numberDiff line change
@@ -1490,4 +1490,44 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
14901490
\\ return 0;
14911491
\\}
14921492
, "");
1493+
1494+
cases.add("signed array subscript. Issue #8556",
1495+
\\#include <stdint.h>
1496+
\\#include <stdlib.h>
1497+
\\#define TEST_NEGATIVE(type) { type x = -1; if (ptr[x] != 42) abort(); }
1498+
\\#define TEST_UNSIGNED(type) { type x = 2; if (arr[x] != 42) abort(); }
1499+
\\int main(void) {
1500+
\\ int arr[] = {40, 41, 42, 43};
1501+
\\ int *ptr = arr + 3;
1502+
\\ if (ptr[-1] != 42) abort();
1503+
\\ TEST_NEGATIVE(int);
1504+
\\ TEST_NEGATIVE(long);
1505+
\\ TEST_NEGATIVE(long long);
1506+
\\ TEST_NEGATIVE(int64_t);
1507+
\\ TEST_NEGATIVE(__int128);
1508+
\\ TEST_UNSIGNED(unsigned);
1509+
\\ TEST_UNSIGNED(unsigned long);
1510+
\\ TEST_UNSIGNED(unsigned long long);
1511+
\\ TEST_UNSIGNED(uint64_t);
1512+
\\ TEST_UNSIGNED(size_t);
1513+
\\ TEST_UNSIGNED(unsigned __int128);
1514+
\\ return 0;
1515+
\\}
1516+
, "");
1517+
1518+
cases.add("Ensure side-effects only evaluated once for signed array indices",
1519+
\\#include <stdlib.h>
1520+
\\int main(void) {
1521+
\\ int foo[] = {1, 2, 3, 4};
1522+
\\ int *p = foo;
1523+
\\ int idx = 1;
1524+
\\ if ((++p)[--idx] != 2) abort();
1525+
\\ if (p != foo + 1) abort();
1526+
\\ if (idx != 0) abort();
1527+
\\ if ((p++)[idx++] != 2) abort();
1528+
\\ if (p != foo + 2) abort();
1529+
\\ if (idx != 1) abort();
1530+
\\ return 0;
1531+
\\}
1532+
, "");
14931533
}

0 commit comments

Comments
 (0)