Skip to content

Commit c6f0b24

Browse files
ehaasandrewrk
authored andcommitted
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 ziglang#8556
1 parent c47b46f commit c6f0b24

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
@@ -3273,30 +3273,113 @@ fn transMemberExpr(c: *Context, scope: *Scope, stmt: *const clang.MemberExpr, re
32733273
return maybeSuppressResult(c, scope, result_used, node);
32743274
}
32753275

3276+
/// ptr[subscr] (`subscr` is a signed integer expression, `ptr` a pointer) becomes:
3277+
/// (blk: {
3278+
/// const tmp = subscr;
3279+
/// if (tmp >= 0) break :blk ptr + @intCast(usize, tmp) else break :blk ptr - ~@bitCast(usize, @intCast(isize, tmp) +% -1);
3280+
/// }).*
3281+
/// Todo: rip this out once `[*]T + isize` becomes valid.
3282+
fn transSignedArrayAccess(
3283+
c: *Context,
3284+
scope: *Scope,
3285+
container_expr: *const clang.Expr,
3286+
subscr_expr: *const clang.Expr,
3287+
result_used: ResultUsed,
3288+
) TransError!Node {
3289+
var block_scope = try Scope.Block.init(c, scope, true);
3290+
defer block_scope.deinit();
3291+
3292+
const tmp = try block_scope.makeMangledName(c, "tmp");
3293+
3294+
const subscr_node = try transExpr(c, &block_scope.base, subscr_expr, .used);
3295+
const subscr_decl = try Tag.var_simple.create(c.arena, .{ .name = tmp, .init = subscr_node });
3296+
try block_scope.statements.append(subscr_decl);
3297+
3298+
const tmp_ref = try Tag.identifier.create(c.arena, tmp);
3299+
3300+
const container_node = try transExpr(c, &block_scope.base, container_expr, .used);
3301+
3302+
const cond_node = try Tag.greater_than_equal.create(c.arena, .{ .lhs = tmp_ref, .rhs = Tag.zero_literal.init() });
3303+
3304+
const then_value = try Tag.add.create(c.arena, .{
3305+
.lhs = container_node,
3306+
.rhs = try Tag.int_cast.create(c.arena, .{
3307+
.lhs = try Tag.identifier.create(c.arena, "usize"),
3308+
.rhs = tmp_ref,
3309+
}),
3310+
});
3311+
3312+
const then_body = try Tag.break_val.create(c.arena, .{
3313+
.label = block_scope.label,
3314+
.val = then_value,
3315+
});
3316+
3317+
const minuend = container_node;
3318+
const signed_size = try Tag.int_cast.create(c.arena, .{
3319+
.lhs = try Tag.identifier.create(c.arena, "isize"),
3320+
.rhs = tmp_ref,
3321+
});
3322+
const to_cast = try Tag.add_wrap.create(c.arena, .{
3323+
.lhs = signed_size,
3324+
.rhs = try Tag.negate.create(c.arena, Tag.one_literal.init()),
3325+
});
3326+
const bitcast_node = try Tag.bit_cast.create(c.arena, .{
3327+
.lhs = try Tag.identifier.create(c.arena, "usize"),
3328+
.rhs = to_cast,
3329+
});
3330+
const subtrahend = try Tag.bit_not.create(c.arena, bitcast_node);
3331+
const difference = try Tag.sub.create(c.arena, .{
3332+
.lhs = minuend,
3333+
.rhs = subtrahend,
3334+
});
3335+
const else_body = try Tag.break_val.create(c.arena, .{
3336+
.label = block_scope.label,
3337+
.val = difference,
3338+
});
3339+
3340+
const if_node = try Tag.@"if".create(c.arena, .{
3341+
.cond = cond_node,
3342+
.then = then_body,
3343+
.@"else" = else_body,
3344+
});
3345+
3346+
try block_scope.statements.append(if_node);
3347+
const block_node = try block_scope.complete(c);
3348+
3349+
const derefed = try Tag.deref.create(c.arena, block_node);
3350+
3351+
return maybeSuppressResult(c, &block_scope.base, result_used, derefed);
3352+
}
3353+
32763354
fn transArrayAccess(c: *Context, scope: *Scope, stmt: *const clang.ArraySubscriptExpr, result_used: ResultUsed) TransError!Node {
3277-
var base_stmt = stmt.getBase();
3355+
const base_stmt = stmt.getBase();
3356+
const base_qt = getExprQualType(c, base_stmt);
3357+
const is_vector = cIsVector(base_qt);
3358+
3359+
const subscr_expr = stmt.getIdx();
3360+
const subscr_qt = getExprQualType(c, subscr_expr);
3361+
const is_longlong = cIsLongLongInteger(subscr_qt);
3362+
const is_signed = cIsSignedInteger(subscr_qt);
32783363

32793364
// Unwrap the base statement if it's an array decayed to a bare pointer type
32803365
// so that we index the array itself
3366+
var unwrapped_base = base_stmt;
32813367
if (@ptrCast(*const clang.Stmt, base_stmt).getStmtClass() == .ImplicitCastExprClass) {
32823368
const implicit_cast = @ptrCast(*const clang.ImplicitCastExpr, base_stmt);
32833369

32843370
if (implicit_cast.getCastKind() == .ArrayToPointerDecay) {
3285-
base_stmt = implicit_cast.getSubExpr();
3371+
unwrapped_base = implicit_cast.getSubExpr();
32863372
}
32873373
}
32883374

3289-
const container_node = try transExpr(c, scope, base_stmt, .used);
3290-
3291-
// cast if the index is long long or signed
3292-
const subscr_expr = stmt.getIdx();
3293-
const qt = getExprQualType(c, subscr_expr);
3294-
const is_longlong = cIsLongLongInteger(qt);
3295-
const is_signed = cIsSignedInteger(qt);
3375+
// Special case: actual pointer (not decayed array) and signed integer subscript
3376+
// See discussion at https://github.com/ziglang/zig/pull/8589
3377+
if (is_signed and (base_stmt == unwrapped_base) and !is_vector) return transSignedArrayAccess(c, scope, base_stmt, subscr_expr, result_used);
32963378

3379+
const container_node = try transExpr(c, scope, unwrapped_base, .used);
32973380
const rhs = if (is_longlong or is_signed) blk: {
32983381
// check if long long first so that signed long long doesn't just become unsigned long long
3299-
var typeid_node = if (is_longlong) try Tag.identifier.create(c.arena, "usize") else try transQualTypeIntWidthOf(c, qt, false);
3382+
const typeid_node = if (is_longlong) try Tag.identifier.create(c.arena, "usize") else try transQualTypeIntWidthOf(c, subscr_qt, false);
33003383
break :blk try Tag.int_cast.create(c.arena, .{ .lhs = typeid_node, .rhs = try transExpr(c, scope, subscr_expr, .used) });
33013384
} else try transExpr(c, scope, subscr_expr, .used);
33023385

test/run_translated_c.zig

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1709,4 +1709,44 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
17091709
\\ return 0;
17101710
\\}
17111711
, "");
1712+
1713+
cases.add("signed array subscript. Issue #8556",
1714+
\\#include <stdint.h>
1715+
\\#include <stdlib.h>
1716+
\\#define TEST_NEGATIVE(type) { type x = -1; if (ptr[x] != 42) abort(); }
1717+
\\#define TEST_UNSIGNED(type) { type x = 2; if (arr[x] != 42) abort(); }
1718+
\\int main(void) {
1719+
\\ int arr[] = {40, 41, 42, 43};
1720+
\\ int *ptr = arr + 3;
1721+
\\ if (ptr[-1] != 42) abort();
1722+
\\ TEST_NEGATIVE(int);
1723+
\\ TEST_NEGATIVE(long);
1724+
\\ TEST_NEGATIVE(long long);
1725+
\\ TEST_NEGATIVE(int64_t);
1726+
\\ TEST_NEGATIVE(__int128);
1727+
\\ TEST_UNSIGNED(unsigned);
1728+
\\ TEST_UNSIGNED(unsigned long);
1729+
\\ TEST_UNSIGNED(unsigned long long);
1730+
\\ TEST_UNSIGNED(uint64_t);
1731+
\\ TEST_UNSIGNED(size_t);
1732+
\\ TEST_UNSIGNED(unsigned __int128);
1733+
\\ return 0;
1734+
\\}
1735+
, "");
1736+
1737+
cases.add("Ensure side-effects only evaluated once for signed array indices",
1738+
\\#include <stdlib.h>
1739+
\\int main(void) {
1740+
\\ int foo[] = {1, 2, 3, 4};
1741+
\\ int *p = foo;
1742+
\\ int idx = 1;
1743+
\\ if ((++p)[--idx] != 2) abort();
1744+
\\ if (p != foo + 1) abort();
1745+
\\ if (idx != 0) abort();
1746+
\\ if ((p++)[idx++] != 2) abort();
1747+
\\ if (p != foo + 2) abort();
1748+
\\ if (idx != 1) abort();
1749+
\\ return 0;
1750+
\\}
1751+
, "");
17121752
}

0 commit comments

Comments
 (0)