diff --git a/lib/compiler/aro_translate_c.zig b/lib/compiler/aro_translate_c.zig index 8f8498592b019a25aeace198480906fdd9ec53e7..064414863f187839887c972b6c87b35a136b108c 100644 --- a/lib/compiler/aro_translate_c.zig +++ b/lib/compiler/aro_translate_c.zig @@ -1277,6 +1277,12 @@ pub fn ScopeExtra(comptime ScopeExtraContext: type, comptime ScopeExtraType: typ /// struct itself is given this name. pub const static_inner_name = "static"; + /// C extern variables declared within a block are wrapped in a block-local + /// struct. The struct is named ExternLocal_[variable_name], the Zig variable + /// within the struct itself is [variable_name] by neccessity since it's an + /// extern reference to an existing symbol. + pub const extern_inner_prepend = "ExternLocal"; + pub fn init(c: *ScopeExtraContext, parent: *ScopeExtraScope, labeled: bool) !Block { var blk = Block{ .base = .{ @@ -1356,6 +1362,24 @@ pub fn ScopeExtra(comptime ScopeExtraContext: type, comptime ScopeExtraType: typ return scope.base.parent.?.getAlias(name); } + /// Finds the (potentially) mangled struct name for a locally scoped extern variable given the original declaration name. + /// + /// Block scoped extern declarations translate to: + /// const MangledStructName = struct {extern [qualifiers] original_extern_variable_name: [type]}; + /// This finds MangledStructName given original_extern_variable_name for referencing correctly in transDeclRefExpr() + pub fn getLocalExternAlias(scope: *Block, name: []const u8) ?[]const u8 { + for (scope.statements.items) |node| { + if (node.tag() == .extern_local_var) { + const parent_node = node.castTag(.extern_local_var).?; + const init_node = parent_node.data.init.castTag(.var_decl).?; + if (std.mem.eql(u8, init_node.data.name, name)) { + return parent_node.data.name; + } + } + } + return null; + } + pub fn localContains(scope: *Block, name: []const u8) bool { for (scope.variables.items) |p| { if (std.mem.eql(u8, p.alias, name)) @@ -1451,6 +1475,16 @@ pub fn ScopeExtra(comptime ScopeExtraContext: type, comptime ScopeExtraType: typ }; } + pub fn getLocalExternAlias(scope: *ScopeExtraScope, name: []const u8) ?[]const u8 { + return switch (scope.id) { + .block => ret: { + const block = @as(*Block, @fieldParentPtr("base", scope)); + break :ret block.getLocalExternAlias(name); + }, + .root, .loop, .do_loop, .condition => null, + }; + } + pub fn contains(scope: *ScopeExtraScope, name: []const u8) bool { return switch (scope.id) { .root => @as(*Root, @fieldParentPtr("base", scope)).contains(name), diff --git a/lib/compiler/aro_translate_c/ast.zig b/lib/compiler/aro_translate_c/ast.zig index 74594af8fe6f05b64292314904591c24fa8c8279..9283ef5bbfaa3069a2f448525c4d49d3e85a37cb 100644 --- a/lib/compiler/aro_translate_c/ast.zig +++ b/lib/compiler/aro_translate_c/ast.zig @@ -55,6 +55,8 @@ pub const Node = extern union { var_decl, /// const name = struct { init } static_local_var, + /// const ExternLocal_name = struct { init } + extern_local_var, /// var name = init.* mut_str, func, @@ -365,7 +367,7 @@ pub const Node = extern union { .c_pointer, .single_pointer => Payload.Pointer, .array_type, .null_sentinel_array_type => Payload.Array, .arg_redecl, .alias, .fail_decl => Payload.ArgRedecl, - .var_simple, .pub_var_simple, .static_local_var, .mut_str => Payload.SimpleVarDecl, + .var_simple, .pub_var_simple, .static_local_var, .extern_local_var, .mut_str => Payload.SimpleVarDecl, .enum_constant => Payload.EnumConstant, .array_filler => Payload.ArrayFiller, .pub_inline_fn => Payload.PubInlineFn, @@ -1269,6 +1271,36 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { }, }); }, + .extern_local_var => { + const payload = node.castTag(.extern_local_var).?.data; + + const const_tok = try c.addToken(.keyword_const, "const"); + _ = try c.addIdentifier(payload.name); + _ = try c.addToken(.equal, "="); + + const kind_tok = try c.addToken(.keyword_struct, "struct"); + _ = try c.addToken(.l_brace, "{"); + + const container_def = try c.addNode(.{ + .tag = .container_decl_two_trailing, + .main_token = kind_tok, + .data = .{ + .lhs = try renderNode(c, payload.init), + .rhs = 0, + }, + }); + _ = try c.addToken(.r_brace, "}"); + _ = try c.addToken(.semicolon, ";"); + + return c.addNode(.{ + .tag = .simple_var_decl, + .main_token = const_tok, + .data = .{ + .lhs = 0, + .rhs = container_def, + }, + }); + }, .mut_str => { const payload = node.castTag(.mut_str).?.data; @@ -2292,7 +2324,7 @@ fn renderNullSentinelArrayType(c: *Context, len: usize, elem_type: Node) !NodeIn fn addSemicolonIfNeeded(c: *Context, node: Node) !void { switch (node.tag()) { .warning => unreachable, - .var_decl, .var_simple, .arg_redecl, .alias, .block, .empty_block, .block_single, .@"switch", .static_local_var, .mut_str => {}, + .var_decl, .var_simple, .arg_redecl, .alias, .block, .empty_block, .block_single, .@"switch", .static_local_var, .extern_local_var, .mut_str => {}, .while_true => { const payload = node.castTag(.while_true).?.data; return addSemicolonIfNotBlock(c, payload); @@ -2388,6 +2420,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { .shuffle, .builtin_extern, .static_local_var, + .extern_local_var, .mut_str, .macro_arithmetic, => { diff --git a/src/translate_c.zig b/src/translate_c.zig index 87783107517fa744766f68f7ade580d555165df3..416cfa765263ab6d6d0f17723583f4819a595226 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -1734,6 +1734,48 @@ const ClangAlignment = struct { } }; +/// Translate an "extern" variable that's been declared within a scoped block. +/// Similar to static local variables, this will be wrapped in a struct to work with Zig's syntax requirements. +/// +/// Assumptions made: +/// - No need to mangle the actual NamedDecl, as by definition this MUST be the same name as the external symbol it's referencing +/// - It's not valid C to have an initializer with this type of declaration, so we can safely operate assuming no initializer +/// - No need to look for any cleanup attributes with getCleanupAttribute(), not relevant for this type of decl +fn transLocalExternStmt(c: *Context, scope: *Scope, var_decl: *const clang.VarDecl, block_scope: *Scope.Block) TransError!void { + const extern_var_name = try c.str(@as(*const clang.NamedDecl, @ptrCast(var_decl)).getName_bytes_begin()); + + // Special naming convention for local extern variable wrapper struct + const name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ Scope.Block.extern_inner_prepend, extern_var_name }); + + // On the off chance there's already a variable in scope named "ExternLocal_[extern_var_name]" + const mangled_name = try block_scope.makeMangledName(c, name); + + const qual_type = var_decl.getTypeSourceInfo_getType(); + const is_const = qual_type.isConstQualified(); + const loc = var_decl.getLocation(); + const type_node = try transQualType(c, scope, qual_type, loc); + + // Inner Node for the extern variable declaration + var node = try Tag.var_decl.create(c.arena, .{ + .is_pub = false, + .is_const = is_const, + .is_extern = true, + .is_export = false, + .is_threadlocal = var_decl.getTLSKind() != .None, // TODO: Neccessary? + .linksection_string = null, // TODO: Neccessary? + .alignment = ClangAlignment.forVar(c, var_decl).zigAlignment(), + .name = extern_var_name, + .type = type_node, + .init = null, + }); + + // Outer Node for the wrapper struct + node = try Tag.extern_local_var.create(c.arena, .{ .name = mangled_name, .init = node }); + + try block_scope.statements.append(node); + try block_scope.discardVariable(c, mangled_name); +} + fn transDeclStmtOne( c: *Context, scope: *Scope, @@ -1743,6 +1785,13 @@ fn transDeclStmtOne( switch (decl.getKind()) { .Var => { const var_decl = @as(*const clang.VarDecl, @ptrCast(decl)); + + // Translation behavior for a block scope declared "extern" variable + // is enough of an outlier that it needs it's own function + if (var_decl.getStorageClass() == .Extern) { + return transLocalExternStmt(c, scope, var_decl, block_scope); + } + const decl_init = var_decl.getInit(); const loc = decl.getLocation(); @@ -1750,11 +1799,7 @@ fn transDeclStmtOne( const name = try c.str(@as(*const clang.NamedDecl, @ptrCast(var_decl)).getName_bytes_begin()); const mangled_name = try block_scope.makeMangledName(c, name); - if (var_decl.getStorageClass() == .Extern) { - // This is actually a global variable, put it in the global scope and reference it. - // `_ = mangled_name;` - return visitVarDecl(c, var_decl, mangled_name); - } else if (qualTypeWasDemotedToOpaque(c, qual_type)) { + if (qualTypeWasDemotedToOpaque(c, qual_type)) { return fail(c, error.UnsupportedTranslation, loc, "local variable has opaque type", .{}); } @@ -1851,18 +1896,37 @@ fn transDeclRefExpr( const value_decl = expr.getDecl(); const name = try c.str(@as(*const clang.NamedDecl, @ptrCast(value_decl)).getName_bytes_begin()); const mangled_name = scope.getAlias(name); - var ref_expr = if (cIsFunctionDeclRef(@as(*const clang.Expr, @ptrCast(expr)))) - try Tag.fn_identifier.create(c.arena, mangled_name) - else - try Tag.identifier.create(c.arena, mangled_name); + const decl_is_var = @as(*const clang.Decl, @ptrCast(value_decl)).getKind() == .Var; + const potential_local_extern = if (decl_is_var) ((@as(*const clang.VarDecl, @ptrCast(value_decl)).getStorageClass() == .Extern) and (scope.id == .block)) else false; - if (@as(*const clang.Decl, @ptrCast(value_decl)).getKind() == .Var) { + var confirmed_local_extern = false; + var ref_expr = val: { + if (cIsFunctionDeclRef(@as(*const clang.Expr, @ptrCast(expr)))) { + break :val try Tag.fn_identifier.create(c.arena, mangled_name); + } else if (potential_local_extern) { + if (scope.getLocalExternAlias(name)) |v| { + confirmed_local_extern = true; + break :val try Tag.identifier.create(c.arena, v); + } else { + break :val try Tag.identifier.create(c.arena, mangled_name); + } + } else { + break :val try Tag.identifier.create(c.arena, mangled_name); + } + }; + + if (decl_is_var) { const var_decl = @as(*const clang.VarDecl, @ptrCast(value_decl)); if (var_decl.isStaticLocal()) { ref_expr = try Tag.field_access.create(c.arena, .{ .lhs = ref_expr, .field_name = Scope.Block.static_inner_name, }); + } else if (confirmed_local_extern) { + ref_expr = try Tag.field_access.create(c.arena, .{ + .lhs = ref_expr, + .field_name = name, // by necessity, name will always == mangled_name + }); } } scope.skipVariableDiscard(mangled_name); diff --git a/test/cases/run_translated_c/extern_typedef_variables_in_functions.c b/test/cases/run_translated_c/extern_typedef_variables_in_functions.c new file mode 100644 index 0000000000000000000000000000000000000000..bfb4087af2606567801839409b2237b267b1a0ba --- /dev/null +++ b/test/cases/run_translated_c/extern_typedef_variables_in_functions.c @@ -0,0 +1,18 @@ +const int ev = 40; + +static int func(void) +{ + typedef int test_type_t; + extern const test_type_t ev; + return ev + 2; +} + +int main() +{ + if (func() != 42) + return 1; + return 0; +} + +// run-translated-c +// c_frontend=clang