Commit db9500a314

Jakub Konka <kubkon@jakubkonka.com>
2022-02-06 09:14:15
stage2: handle extern lib name annotation for vars
For example, a situation like this is allowed ```zig extern "c" var stderrp: c_int; ``` In this case, `Module.Var` wrapping `stderrp` will have `lib_name` populated with the library name where this import is expected.
1 parent 556f0ce
Changed files (2)
src/Module.zig
@@ -510,6 +510,7 @@ pub const Decl = struct {
             gpa.destroy(func);
         }
         if (decl.getVariable()) |variable| {
+            variable.deinit(gpa);
             gpa.destroy(variable);
         }
         if (decl.value_arena) |arena_state| {
@@ -694,6 +695,8 @@ pub const Decl = struct {
         return func;
     }
 
+    /// If the Decl has a value and it is an extern function, returns it,
+    /// otherwise null.
     pub fn getExternFn(decl: *const Decl) ?*ExternFn {
         if (!decl.owns_tv) return null;
         const extern_fn = (decl.val.castTag(.extern_fn) orelse return null).data;
@@ -701,6 +704,8 @@ pub const Decl = struct {
         return extern_fn;
     }
 
+    /// If the Decl has a value and it is a variable, returns it,
+    /// otherwise null.
     pub fn getVariable(decl: *Decl) ?*Var {
         if (!decl.owns_tv) return null;
         const variable = (decl.val.castTag(.variable) orelse return null).data;
@@ -1469,9 +1474,20 @@ pub const Var = struct {
     init: Value,
     owner_decl: *Decl,
 
+    /// Library name if specified.
+    /// For example `extern "c" var stderrp = ...` would have 'c' as library name.
+    /// Allocated with Module's allocator; outlives the ZIR code.
+    lib_name: ?[*:0]const u8,
+
     is_extern: bool,
     is_mutable: bool,
     is_threadlocal: bool,
+
+    pub fn deinit(variable: *Var, gpa: Allocator) void {
+        if (variable.lib_name) |lib_name| {
+            gpa.free(mem.sliceTo(lib_name, 0));
+        }
+    }
 };
 
 /// The container that structs, enums, unions, and opaques have.
src/Sema.zig
@@ -5430,6 +5430,69 @@ fn zirFunc(
     );
 }
 
+/// Given a library name, examines if the library name should end up in
+/// `link.File.Options.system_libs` table (for example, libc is always
+/// specified via dedicated flag `link.File.Options.link_libc` instead),
+/// and puts it there if it doesn't exist.
+/// It also dupes the library name which can then be saved as part of the
+/// respective `Decl` (either `ExternFn` or `Var`).
+/// The liveness of the duped library name is tied to liveness of `Module`.
+/// To deallocate, call `deinit` on the respective `Decl` (`ExternFn` or `Var`).
+fn handleExternLibName(
+    sema: *Sema,
+    block: *Block,
+    src_loc: LazySrcLoc,
+    lib_name: []const u8,
+) CompileError![:0]u8 {
+    blk: {
+        const mod = sema.mod;
+        const target = mod.getTarget();
+        log.debug("extern fn symbol expected in lib '{s}'", .{lib_name});
+        if (target_util.is_libc_lib_name(target, lib_name)) {
+            if (!mod.comp.bin_file.options.link_libc) {
+                return sema.fail(
+                    block,
+                    src_loc,
+                    "dependency on libc must be explicitly specified in the build command",
+                    .{},
+                );
+            }
+            mod.comp.bin_file.options.link_libc = true;
+            break :blk;
+        }
+        if (target_util.is_libcpp_lib_name(target, lib_name)) {
+            if (!mod.comp.bin_file.options.link_libcpp) {
+                return sema.fail(
+                    block,
+                    src_loc,
+                    "dependency on libc++ must be explicitly specified in the build command",
+                    .{},
+                );
+            }
+            mod.comp.bin_file.options.link_libcpp = true;
+            break :blk;
+        }
+        if (mem.eql(u8, lib_name, "unwind")) {
+            mod.comp.bin_file.options.link_libunwind = true;
+            break :blk;
+        }
+        if (!target.isWasm() and !mod.comp.bin_file.options.pic) {
+            return sema.fail(
+                block,
+                src_loc,
+                "dependency on dynamic library '{s}' requires enabling Position Independent Code. Fixed by `-l{s}` or `-fPIC`.",
+                .{ lib_name, lib_name },
+            );
+        }
+        mod.comp.stage1AddLinkLib(lib_name) catch |err| {
+            return sema.fail(block, src_loc, "unable to add link lib '{s}': {s}", .{
+                lib_name, @errorName(err),
+            });
+        };
+    }
+    return sema.gpa.dupeZ(u8, lib_name);
+}
+
 fn funcCommon(
     sema: *Sema,
     block: *Block,
@@ -5567,65 +5630,21 @@ fn funcCommon(
         });
     };
 
-    if (opt_lib_name) |lib_name| blk: {
-        const lib_name_src: LazySrcLoc = .{ .node_offset_lib_name = src_node_offset };
-        log.debug("extern fn symbol expected in lib '{s}'", .{lib_name});
-        if (target_util.is_libc_lib_name(target, lib_name)) {
-            if (!mod.comp.bin_file.options.link_libc) {
-                return sema.fail(
-                    block,
-                    lib_name_src,
-                    "dependency on libc must be explicitly specified in the build command",
-                    .{},
-                );
-            }
-            mod.comp.bin_file.options.link_libc = true;
-            break :blk;
-        }
-        if (target_util.is_libcpp_lib_name(target, lib_name)) {
-            if (!mod.comp.bin_file.options.link_libcpp) {
-                return sema.fail(
-                    block,
-                    lib_name_src,
-                    "dependency on libc++ must be explicitly specified in the build command",
-                    .{},
-                );
-            }
-            mod.comp.bin_file.options.link_libcpp = true;
-            break :blk;
-        }
-        if (mem.eql(u8, lib_name, "unwind")) {
-            mod.comp.bin_file.options.link_libunwind = true;
-            break :blk;
-        }
-        if (!target.isWasm() and !mod.comp.bin_file.options.pic) {
-            return sema.fail(
-                block,
-                lib_name_src,
-                "dependency on dynamic library '{s}' requires enabling Position Independent Code. Fixed by `-l{s}` or `-fPIC`.",
-                .{ lib_name, lib_name },
-            );
-        }
-        mod.comp.stage1AddLinkLib(lib_name) catch |err| {
-            return sema.fail(block, lib_name_src, "unable to add link lib '{s}': {s}", .{
-                lib_name, @errorName(err),
-            });
-        };
-    }
-
     if (is_extern) {
         const new_extern_fn = try sema.gpa.create(Module.ExternFn);
         errdefer sema.gpa.destroy(new_extern_fn);
 
-        const lib_name: ?[*:0]const u8 = if (opt_lib_name) |lib_name| blk: {
-            break :blk try sema.gpa.dupeZ(u8, lib_name);
-        } else null;
-
         new_extern_fn.* = Module.ExternFn{
             .owner_decl = sema.owner_decl,
-            .lib_name = lib_name,
+            .lib_name = null,
         };
 
+        if (opt_lib_name) |lib_name| {
+            new_extern_fn.lib_name = try sema.handleExternLibName(block, .{
+                .node_offset_lib_name = src_node_offset,
+            }, lib_name);
+        }
+
         const extern_fn_payload = try sema.arena.create(Value.Payload.ExternFn);
         extern_fn_payload.* = .{
             .base = .{ .tag = .extern_fn },
@@ -12456,13 +12475,8 @@ fn zirVarExtended(
 
     try sema.validateVarType(block, mut_src, var_ty, small.is_extern);
 
-    if (lib_name != null) {
-        // Look at the sema code for functions which has this logic, it just needs to
-        // be extracted and shared by both var and func
-        return sema.fail(block, src, "TODO: handle var with lib_name in Sema", .{});
-    }
-
     const new_var = try sema.gpa.create(Module.Var);
+    errdefer sema.gpa.destroy(new_var);
 
     log.debug("created variable {*} owner_decl: {*} ({s})", .{
         new_var, sema.owner_decl, sema.owner_decl.name,
@@ -12474,7 +12488,13 @@ fn zirVarExtended(
         .is_extern = small.is_extern,
         .is_mutable = true, // TODO get rid of this unused field
         .is_threadlocal = small.is_threadlocal,
+        .lib_name = null,
     };
+
+    if (lib_name) |lname| {
+        new_var.lib_name = try sema.handleExternLibName(block, ty_src, lname);
+    }
+
     const result = try sema.addConstant(
         var_ty,
         try Value.Tag.variable.create(sema.arena, new_var),