Skip to content
This repository was archived by the owner on Nov 26, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 37 additions & 5 deletions src/Translator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ pub const QualTypeHashContext = struct {
pub const Error = std.mem.Allocator.Error;
pub const MacroProcessingError = Error || error{UnexpectedMacroToken};
pub const TypeError = Error || error{UnsupportedType};
pub const TransError = TypeError || error{UnsupportedTranslation};
pub const TransError = TypeError || error{ UnsupportedTranslation, SelfReferential };

const Translator = @This();

Expand Down Expand Up @@ -117,6 +117,10 @@ typedefs: std.StringArrayHashMapUnmanaged(void) = .empty,
/// The lhs lval of a compound assignment expression.
compound_assign_dummy: ?ZigNode = null,

/// Set of variables whose initializers are currently being translated.
/// Used to detect self-referential initializers.
wip_var_inits: std.AutoHashMapUnmanaged(Node.Index, void) = .empty,

pub fn getMangle(t: *Translator) u32 {
t.mangle_count += 1;
return t.mangle_count;
Expand Down Expand Up @@ -243,6 +247,7 @@ pub fn translate(options: Options) mem.Allocator.Error![]u8 {
translator.anonymous_record_field_names.deinit(gpa);
translator.typedefs.deinit(gpa);
translator.global_scope.deinit();
translator.wip_var_inits.deinit(gpa);
}

try translator.prepopulateGlobalNameTable();
Expand Down Expand Up @@ -420,7 +425,7 @@ fn transDecl(t: *Translator, scope: *Scope, decl: Node.Index) !void {

.variable => |variable| {
if (variable.definition != null) return;
try t.transVarDecl(scope, variable);
try t.transVarDecl(scope, variable, decl);
},
.static_assert => |static_assert| {
try t.transStaticAssert(&t.global_scope.base, static_assert);
Expand Down Expand Up @@ -817,6 +822,7 @@ fn transFnDecl(t: *Translator, scope: *Scope, function: Node.Function) Error!voi

t.transCompoundStmtInline(body_stmt, &block_scope) catch |err| switch (err) {
error.OutOfMemory => |e| return e,
error.SelfReferential => unreachable,
error.UnsupportedTranslation,
error.UnsupportedType,
=> {
Expand All @@ -833,7 +839,7 @@ fn transFnDecl(t: *Translator, scope: *Scope, function: Node.Function) Error!voi
return t.addTopLevelDecl(fn_name, proto_node);
}

fn transVarDecl(t: *Translator, scope: *Scope, variable: Node.Variable) Error!void {
fn transVarDecl(t: *Translator, scope: *Scope, variable: Node.Variable, decl_node: Node.Index) Error!void {
const base_name = t.tree.tokSlice(variable.name_tok);
const toplevel = scope.id == .root;
const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined;
Expand Down Expand Up @@ -871,13 +877,21 @@ fn transVarDecl(t: *Translator, scope: *Scope, variable: Node.Variable) Error!vo
var is_const = variable.qt.@"const" or (array_ty != null and array_ty.?.elem.@"const");
var is_extern = variable.storage_class == .@"extern";

var self_referential = false;
const init_node = init: {
if (variable.initializer) |init| {
const maybe_literal = init.get(t.tree);
if (!toplevel) try t.wip_var_inits.putNoClobber(t.gpa, decl_node, {});
defer _ = t.wip_var_inits.remove(decl_node);

const init_node = (if (maybe_literal == .string_literal_expr)
t.transStringLiteralInitializer(init, maybe_literal.string_literal_expr, type_node)
else
t.transExprCoercing(scope, init, .used)) catch |err| switch (err) {
error.SelfReferential => {
self_referential = true;
break :init ZigTag.undefined_literal.init();
},
error.UnsupportedTranslation, error.UnsupportedType => {
return t.failDecl(scope, variable.name_tok, name, "unable to resolve var init expr", .{});
},
Expand Down Expand Up @@ -932,7 +946,7 @@ fn transVarDecl(t: *Translator, scope: *Scope, variable: Node.Variable) Error!vo
const alignment: ?c_uint = variable.qt.requestedAlignment(t.comp) orelse null;
var node = try ZigTag.var_decl.create(t.arena, .{
.is_pub = toplevel,
.is_const = is_const,
.is_const = is_const and !self_referential,
.is_extern = is_extern,
.is_export = toplevel and variable.storage_class == .auto and linkage == .strong,
.is_threadlocal = variable.thread_local,
Expand All @@ -950,6 +964,21 @@ fn transVarDecl(t: *Translator, scope: *Scope, variable: Node.Variable) Error!vo
node = try ZigTag.wrapped_local.create(t.arena, .{ .name = name, .init = node });
}
try scope.appendNode(node);
if (self_referential) {
var deferred_init = t.transExprCoercing(scope, variable.initializer.?, .used) catch |err| switch (err) {
error.SelfReferential => unreachable,
error.UnsupportedTranslation, error.UnsupportedType => {
return t.failDecl(scope, variable.name_tok, name, "unable to resolve var init expr", .{});
},
else => |e| return e,
};
if (!variable.qt.is(t.comp, .bool) and deferred_init.isBoolRes()) {
deferred_init = try ZigTag.int_from_bool.create(t.arena, deferred_init);
}
const varname = try ZigTag.identifier.create(t.arena, name);
const assign = try ZigTag.assign.create(t.arena, .{ .lhs = varname, .rhs = deferred_init });
try scope.appendNode(assign);
}
try bs.discardVariable(name);

if (variable.qt.getAttribute(t.comp, .cleanup)) |cleanup_attr| {
Expand Down Expand Up @@ -1057,6 +1086,7 @@ fn transEnumDecl(t: *Translator, scope: *Scope, enum_qt: QualType) Error!void {

fn transStaticAssert(t: *Translator, scope: *Scope, static_assert: Node.StaticAssert) Error!void {
const condition = t.transExpr(scope, static_assert.cond, .used) catch |err| switch (err) {
error.SelfReferential => unreachable,
error.UnsupportedTranslation, error.UnsupportedType => {
return try t.warn(&t.global_scope.base, static_assert.cond.tok(t.tree), "unable to translate _Static_assert condition", .{});
},
Expand Down Expand Up @@ -1596,7 +1626,7 @@ fn transStmt(t: *Translator, scope: *Scope, stmt: Node.Index) TransError!ZigNode
return ZigTag.declaration.init();
},
.variable => |variable| {
try t.transVarDecl(scope, variable);
try t.transVarDecl(scope, variable, stmt);
return ZigTag.declaration.init();
},
.switch_stmt => |switch_stmt| return t.transSwitch(scope, switch_stmt),
Expand Down Expand Up @@ -2616,6 +2646,8 @@ fn transPointerCastExpr(t: *Translator, scope: *Scope, expr: Node.Index) TransEr
}

fn transDeclRefExpr(t: *Translator, scope: *Scope, decl_ref: Node.DeclRef) TransError!ZigNode {
if (t.wip_var_inits.contains(decl_ref.decl)) return error.SelfReferential;

const name = t.tree.tokSlice(decl_ref.name_tok);
const maybe_alias = scope.getAlias(name);
const mangled_name = maybe_alias orelse name;
Expand Down
24 changes: 24 additions & 0 deletions test/cases/translate/self_referential_init.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
int *a = (int*)(&a + 1); // TODO result missing casts

extern void *alloc(unsigned long long);
void foo(void) {
int b = b + 1;
int *c = alloc(sizeof(*c));
const double *d = alloc(sizeof(*d));
}

// translate
//
// pub export var a: [*c]c_int = @ptrCast(@alignCast((&a) + @as(usize, @bitCast(@as(isize, @intCast(@as(c_int, 1)))))));
// pub extern fn alloc(c_ulonglong) ?*anyopaque;
// pub export fn foo() void {
// var b: c_int = undefined;
// b = b + @as(c_int, 1);
// _ = &b;
// var c: [*c]c_int = undefined;
// c = @ptrCast(@alignCast(alloc(@sizeOf(@TypeOf(c.*)))));
// _ = &c;
// var d: [*c]const f64 = undefined;
// d = @ptrCast(@alignCast(alloc(@sizeOf(@TypeOf(d.*)))));
// _ = &d;
// }