Commit 6ac2047142

Andrew Kelley <andrew@ziglang.org>
2021-05-08 01:05:44
stage2: implement extern functions
1 parent 47531b7
src/AstGen.zig
@@ -1081,6 +1081,7 @@ fn fnProtoExpr(
         .is_var_args = is_var_args,
         .is_inferred_error = false,
         .is_test = false,
+        .is_extern = false,
     });
     return rvalue(gz, scope, rl, result, fn_proto.ast.proto_node);
 }
@@ -2807,6 +2808,7 @@ fn fnDecl(
             .is_var_args = is_var_args,
             .is_inferred_error = false,
             .is_test = false,
+            .is_extern = true,
         });
     } else func: {
         if (is_var_args) {
@@ -2883,6 +2885,7 @@ fn fnDecl(
             .is_var_args = is_var_args,
             .is_inferred_error = is_inferred_error,
             .is_test = false,
+            .is_extern = false,
         });
     };
 
@@ -3223,6 +3226,7 @@ fn testDecl(
         .is_var_args = false,
         .is_inferred_error = true,
         .is_test = true,
+        .is_extern = false,
     });
 
     _ = try decl_block.addBreak(.break_inline, block_inst, func_inst);
@@ -7973,6 +7977,7 @@ const GenZir = struct {
         is_var_args: bool,
         is_inferred_error: bool,
         is_test: bool,
+        is_extern: bool,
     }) !Zir.Inst.Ref {
         assert(args.src_node != 0);
         assert(args.ret_ty != .none);
@@ -8010,7 +8015,8 @@ const GenZir = struct {
         }
 
         if (args.cc != .none or args.lib_name != 0 or
-            args.is_var_args or args.is_test or args.align_inst != .none)
+            args.is_var_args or args.is_test or args.align_inst != .none or
+            args.is_extern)
         {
             try astgen.extra.ensureUnusedCapacity(
                 gpa,
@@ -8051,6 +8057,7 @@ const GenZir = struct {
                         .has_cc = args.cc != .none,
                         .has_align = args.align_inst != .none,
                         .is_test = args.is_test,
+                        .is_extern = args.is_extern,
                     }),
                     .operand = payload_index,
                 } },
src/Module.zig
@@ -282,13 +282,10 @@ pub const Decl = struct {
 
     pub fn destroy(decl: *Decl, module: *Module) void {
         const gpa = module.gpa;
-        log.debug("destroy Decl {*} ({s})", .{ decl, decl.name });
+        log.debug("destroy {*} ({s})", .{ decl, decl.name });
         decl.clearName(gpa);
         if (decl.has_tv) {
-            if (decl.getFunction()) |func| {
-                func.deinit(gpa);
-                gpa.destroy(func);
-            } else if (decl.val.getTypeNamespace()) |namespace| {
+            if (decl.val.getTypeNamespace()) |namespace| {
                 if (namespace.getDecl() == decl) {
                     namespace.clearDecls(module);
                 }
@@ -470,7 +467,6 @@ pub const Decl = struct {
     /// otherwise null.
     pub fn getFunction(decl: *Decl) ?*Fn {
         if (!decl.has_tv) return null;
-        if (decl.ty.zigTypeTag() != .Fn) return null;
         const func = (decl.val.castTag(.function) orelse return null).data;
         if (func.owner_decl != decl) return null;
         return func;
@@ -2960,17 +2956,26 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
             decl.clearValues(gpa);
         }
 
-        const new_variable = try decl_arena.allocator.create(Var);
-        new_variable.* = .{
-            .owner_decl = decl,
-            .init = try decl_tv.val.copy(&decl_arena.allocator),
-            .is_extern = is_extern,
-            .is_mutable = is_mutable,
-            .is_threadlocal = is_threadlocal,
+        const copied_val = try decl_tv.val.copy(&decl_arena.allocator);
+        const is_extern_fn = copied_val.tag() == .extern_fn;
+
+        // TODO: also avoid allocating this Var structure if `!is_mutable`.
+        // I think this will require adjusting Sema to copy the value or something
+        // like that; otherwise it causes use of undefined value when freeing resources.
+        const decl_val: Value = if (is_extern_fn) copied_val else blk: {
+            const new_variable = try decl_arena.allocator.create(Var);
+            new_variable.* = .{
+                .owner_decl = decl,
+                .init = copied_val,
+                .is_extern = is_extern,
+                .is_mutable = is_mutable,
+                .is_threadlocal = is_threadlocal,
+            };
+            break :blk try Value.Tag.variable.create(&decl_arena.allocator, new_variable);
         };
 
         decl.ty = try decl_tv.ty.copy(&decl_arena.allocator);
-        decl.val = try Value.Tag.variable.create(&decl_arena.allocator, new_variable);
+        decl.val = decl_val;
         decl.align_val = try align_val.copy(&decl_arena.allocator);
         decl.linksection_val = try linksection_val.copy(&decl_arena.allocator);
         decl.has_tv = true;
@@ -2984,6 +2989,16 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
             // The scope needs to have the decl in it.
             try mod.analyzeExport(&block_scope.base, export_src, mem.spanZ(decl.name), decl);
         }
+
+        if (decl.val.tag() == .extern_fn) {
+            try mod.comp.bin_file.allocateDeclIndexes(decl);
+            try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl });
+
+            if (type_changed and mod.emit_h != null) {
+                try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl });
+            }
+        }
+
         return type_changed;
     }
 }
src/Sema.zig
@@ -2819,6 +2819,7 @@ fn zirFunc(
         Value.initTag(.null_value),
         false,
         inferred_error_set,
+        false,
         src_locs,
         null,
     );
@@ -2835,6 +2836,7 @@ fn funcCommon(
     align_val: Value,
     var_args: bool,
     inferred_error_set: bool,
+    is_extern: bool,
     src_locs: Zir.Inst.Func.SrcLocs,
     opt_lib_name: ?[]const u8,
 ) InnerError!*Inst {
@@ -2885,10 +2887,6 @@ fn funcCommon(
         });
     };
 
-    if (body_inst == 0) {
-        return mod.constType(sema.arena, src, fn_ty);
-    }
-
     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});
@@ -2930,6 +2928,17 @@ fn funcCommon(
         }
     }
 
+    if (is_extern) {
+        return sema.mod.constInst(sema.arena, src, .{
+            .ty = fn_ty,
+            .val = try Value.Tag.extern_fn.create(sema.arena, sema.owner_decl),
+        });
+    }
+
+    if (body_inst == 0) {
+        return mod.constType(sema.arena, src, fn_ty);
+    }
+
     const is_inline = fn_ty.fnCallingConvention() == .Inline;
     const anal_state: Module.Fn.Analysis = if (is_inline) .inline_only else .queued;
 
@@ -5795,6 +5804,7 @@ fn zirFuncExtended(
         align_val,
         small.is_var_args,
         small.is_inferred_error,
+        small.is_extern,
         src_locs,
         lib_name,
     );
src/Zir.zig
@@ -2217,7 +2217,8 @@ pub const Inst = struct {
             has_cc: bool,
             has_align: bool,
             is_test: bool,
-            _: u10 = undefined,
+            is_extern: bool,
+            _: u9 = undefined,
         };
     };
 
@@ -4103,6 +4104,7 @@ const Writer = struct {
             extra.data.return_type,
             inferred_error_set,
             false,
+            false,
             .none,
             .none,
             body,
@@ -4150,6 +4152,7 @@ const Writer = struct {
             extra.data.return_type,
             small.is_inferred_error,
             small.is_var_args,
+            small.is_extern,
             cc,
             align_inst,
             body,
@@ -4232,6 +4235,7 @@ const Writer = struct {
         ret_ty: Inst.Ref,
         inferred_error_set: bool,
         var_args: bool,
+        is_extern: bool,
         cc: Inst.Ref,
         align_inst: Inst.Ref,
         body: []const Inst.Index,
@@ -4248,6 +4252,7 @@ const Writer = struct {
         try self.writeOptionalInstRef(stream, ", cc=", cc);
         try self.writeOptionalInstRef(stream, ", align=", align_inst);
         try self.writeFlag(stream, ", vargs", var_args);
+        try self.writeFlag(stream, ", extern", is_extern);
         try self.writeFlag(stream, ", inferror", inferred_error_set);
 
         if (body.len == 0) {