Commit 20563d8457

Hayden Riddiford <hayden@terrakaffe.com>
2024-06-14 18:06:05
- Added special handling for translating C extern variables declared within scoped blocks - Added test/cases/run_translated_c/extern_typedef_variables_in_functions.c to test for issue 19687
1 parent 88bb0fd
Changed files (4)
lib
compiler
src
test
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,
         => {
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),
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;
+
+    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 (@as(*const clang.Decl, @ptrCast(value_decl)).getKind() == .Var) {
+    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);
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