Commit fe14e33945

Andrew Kelley <andrew@ziglang.org>
2021-07-21 00:22:37
stage2: separate work queue item for functions than decls
Previously we had codegen_decl for both constant values as well as function bodies. A recent commit updated the linker backends to add updateFunc as a separate function than updateDecl, and now this commit does the same with work queue tasks. The frontend now distinguishes between function pointers and function bodies.
1 parent a97e5e1
Changed files (2)
src/Compilation.zig
@@ -169,8 +169,10 @@ pub const CSourceFile = struct {
 };
 
 const Job = union(enum) {
-    /// Write the machine code for a Decl to the output file.
+    /// Write the constant value for a Decl to the output file.
     codegen_decl: *Module.Decl,
+    /// Write the machine code for a function to the output file.
+    codegen_func: *Module.Fn,
     /// Render the .h file snippet for the Decl.
     emit_h_decl: *Module.Decl,
     /// The Decl needs to be analyzed and possibly export itself.
@@ -2006,54 +2008,56 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
                 const module = self.bin_file.options.module.?;
                 assert(decl.has_tv);
                 if (decl.val.castTag(.function)) |payload| {
-                    const func = payload.data;
+                    if (decl.owns_tv) {
+                        const func = payload.data;
+
+                        var air = switch (func.state) {
+                            .sema_failure, .dependency_failure => continue,
+                            .queued => module.analyzeFnBody(decl, func) catch |err| switch (err) {
+                                error.AnalysisFail => {
+                                    assert(func.state != .in_progress);
+                                    continue;
+                                },
+                                error.OutOfMemory => return error.OutOfMemory,
+                            },
+                            .in_progress => unreachable,
+                            .inline_only => unreachable, // don't queue work for this
+                            .success => unreachable, // don't queue it twice
+                        };
+                        defer air.deinit(gpa);
+
+                        log.debug("analyze liveness of {s}", .{decl.name});
+                        var liveness = try Liveness.analyze(gpa, air, decl.namespace.file_scope.zir);
+                        defer liveness.deinit(gpa);
+
+                        if (builtin.mode == .Debug and self.verbose_air) {
+                            std.debug.print("# Begin Function AIR: {s}:\n", .{decl.name});
+                            @import("print_air.zig").dump(gpa, air, liveness);
+                            std.debug.print("# End Function AIR: {s}:\n", .{decl.name});
+                        }
 
-                    var air = switch (func.state) {
-                        .queued => module.analyzeFnBody(decl, func) catch |err| switch (err) {
+                        assert(decl.ty.hasCodeGenBits());
+
+                        self.bin_file.updateFunc(module, func, air, liveness) catch |err| switch (err) {
+                            error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {
-                                assert(func.state != .in_progress);
+                                decl.analysis = .codegen_failure;
                                 continue;
                             },
-                            error.OutOfMemory => return error.OutOfMemory,
-                        },
-                        .in_progress => unreachable,
-                        .inline_only => unreachable, // don't queue work for this
-                        .sema_failure, .dependency_failure => continue,
-                        .success => unreachable, // don't queue it twice
-                    };
-                    defer air.deinit(gpa);
-
-                    log.debug("analyze liveness of {s}", .{decl.name});
-                    var liveness = try Liveness.analyze(gpa, air, decl.namespace.file_scope.zir);
-                    defer liveness.deinit(gpa);
-
-                    if (builtin.mode == .Debug and self.verbose_air) {
-                        std.debug.print("# Begin Function AIR: {s}:\n", .{decl.name});
-                        @import("print_air.zig").dump(gpa, air, liveness);
-                        std.debug.print("# End Function AIR: {s}:\n", .{decl.name});
+                            else => {
+                                try module.failed_decls.ensureUnusedCapacity(gpa, 1);
+                                module.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create(
+                                    gpa,
+                                    decl.srcLoc(),
+                                    "unable to codegen: {s}",
+                                    .{@errorName(err)},
+                                ));
+                                decl.analysis = .codegen_failure_retryable;
+                                continue;
+                            },
+                        };
+                        continue;
                     }
-
-                    assert(decl.ty.hasCodeGenBits());
-
-                    self.bin_file.updateFunc(module, func, air, liveness) catch |err| switch (err) {
-                        error.OutOfMemory => return error.OutOfMemory,
-                        error.AnalysisFail => {
-                            decl.analysis = .codegen_failure;
-                            continue;
-                        },
-                        else => {
-                            try module.failed_decls.ensureUnusedCapacity(gpa, 1);
-                            module.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create(
-                                gpa,
-                                decl.srcLoc(),
-                                "unable to codegen: {s}",
-                                .{@errorName(err)},
-                            ));
-                            decl.analysis = .codegen_failure_retryable;
-                            continue;
-                        },
-                    };
-                    continue;
                 }
 
                 assert(decl.ty.hasCodeGenBits());
@@ -2078,6 +2082,72 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
                 };
             },
         },
+        .codegen_func => |func| switch (func.owner_decl.analysis) {
+            .unreferenced => unreachable,
+            .in_progress => unreachable,
+            .outdated => unreachable,
+
+            .file_failure,
+            .sema_failure,
+            .codegen_failure,
+            .dependency_failure,
+            .sema_failure_retryable,
+            => continue,
+
+            .complete, .codegen_failure_retryable => {
+                if (build_options.omit_stage2)
+                    @panic("sadly stage2 is omitted from this build to save memory on the CI server");
+                switch (func.state) {
+                    .sema_failure, .dependency_failure => continue,
+                    .queued => {},
+                    .in_progress => unreachable,
+                    .inline_only => unreachable, // don't queue work for this
+                    .success => unreachable, // don't queue it twice
+                }
+
+                const module = self.bin_file.options.module.?;
+                const decl = func.owner_decl;
+
+                var air = module.analyzeFnBody(decl, func) catch |err| switch (err) {
+                    error.AnalysisFail => {
+                        assert(func.state != .in_progress);
+                        continue;
+                    },
+                    error.OutOfMemory => return error.OutOfMemory,
+                };
+                defer air.deinit(gpa);
+
+                log.debug("analyze liveness of {s}", .{decl.name});
+                var liveness = try Liveness.analyze(gpa, air, decl.namespace.file_scope.zir);
+                defer liveness.deinit(gpa);
+
+                if (builtin.mode == .Debug and self.verbose_air) {
+                    std.debug.print("# Begin Function AIR: {s}:\n", .{decl.name});
+                    @import("print_air.zig").dump(gpa, air, liveness);
+                    std.debug.print("# End Function AIR: {s}:\n", .{decl.name});
+                }
+
+                self.bin_file.updateFunc(module, func, air, liveness) catch |err| switch (err) {
+                    error.OutOfMemory => return error.OutOfMemory,
+                    error.AnalysisFail => {
+                        decl.analysis = .codegen_failure;
+                        continue;
+                    },
+                    else => {
+                        try module.failed_decls.ensureUnusedCapacity(gpa, 1);
+                        module.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create(
+                            gpa,
+                            decl.srcLoc(),
+                            "unable to codegen: {s}",
+                            .{@errorName(err)},
+                        ));
+                        decl.analysis = .codegen_failure_retryable;
+                        continue;
+                    },
+                };
+                continue;
+            },
+        },
         .emit_h_decl => |decl| switch (decl.analysis) {
             .unreferenced => unreachable,
             .in_progress => unreachable,
src/Module.zig
@@ -2902,6 +2902,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
         decl.generation = mod.generation;
         return false;
     }
+    log.debug("semaDecl {*} ({s})", .{ decl, decl.name });
 
     var block_scope: Scope.Block = .{
         .parent = null,
@@ -2938,106 +2939,109 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
     const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State);
 
     if (decl_tv.val.castTag(.function)) |fn_payload| {
-        var prev_type_has_bits = false;
-        var prev_is_inline = false;
-        var type_changed = true;
-
-        if (decl.has_tv) {
-            prev_type_has_bits = decl.ty.hasCodeGenBits();
-            type_changed = !decl.ty.eql(decl_tv.ty);
-            if (decl.getFunction()) |prev_func| {
-                prev_is_inline = prev_func.state == .inline_only;
+        const func = fn_payload.data;
+        const owns_tv = func.owner_decl == decl;
+        if (owns_tv) {
+            var prev_type_has_bits = false;
+            var prev_is_inline = false;
+            var type_changed = true;
+
+            if (decl.has_tv) {
+                prev_type_has_bits = decl.ty.hasCodeGenBits();
+                type_changed = !decl.ty.eql(decl_tv.ty);
+                if (decl.getFunction()) |prev_func| {
+                    prev_is_inline = prev_func.state == .inline_only;
+                }
+                decl.clearValues(gpa);
             }
-            decl.clearValues(gpa);
-        }
-
-        decl.ty = try decl_tv.ty.copy(&decl_arena.allocator);
-        decl.val = try decl_tv.val.copy(&decl_arena.allocator);
-        decl.align_val = try align_val.copy(&decl_arena.allocator);
-        decl.linksection_val = try linksection_val.copy(&decl_arena.allocator);
-        decl.has_tv = true;
-        decl.owns_tv = fn_payload.data.owner_decl == decl;
-        decl_arena_state.* = decl_arena.state;
-        decl.value_arena = decl_arena_state;
-        decl.analysis = .complete;
-        decl.generation = mod.generation;
 
-        const is_inline = decl_tv.ty.fnCallingConvention() == .Inline;
-        if (!is_inline and decl_tv.ty.hasCodeGenBits()) {
-            // We don't fully codegen the decl until later, but we do need to reserve a global
-            // offset table index for it. This allows us to codegen decls out of dependency order,
-            // increasing how many computations can be done in parallel.
-            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 });
+            decl.ty = try decl_tv.ty.copy(&decl_arena.allocator);
+            decl.val = try decl_tv.val.copy(&decl_arena.allocator);
+            decl.align_val = try align_val.copy(&decl_arena.allocator);
+            decl.linksection_val = try linksection_val.copy(&decl_arena.allocator);
+            decl.has_tv = true;
+            decl.owns_tv = owns_tv;
+            decl_arena_state.* = decl_arena.state;
+            decl.value_arena = decl_arena_state;
+            decl.analysis = .complete;
+            decl.generation = mod.generation;
+
+            const is_inline = decl_tv.ty.fnCallingConvention() == .Inline;
+            if (!is_inline and decl_tv.ty.hasCodeGenBits()) {
+                // We don't fully codegen the decl until later, but we do need to reserve a global
+                // offset table index for it. This allows us to codegen decls out of dependency order,
+                // increasing how many computations can be done in parallel.
+                try mod.comp.bin_file.allocateDeclIndexes(decl);
+                try mod.comp.work_queue.writeItem(.{ .codegen_func = func });
+                if (type_changed and mod.emit_h != null) {
+                    try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl });
+                }
+            } else if (!prev_is_inline and prev_type_has_bits) {
+                mod.comp.bin_file.freeDecl(decl);
             }
-        } else if (!prev_is_inline and prev_type_has_bits) {
-            mod.comp.bin_file.freeDecl(decl);
-        }
 
-        if (decl.is_exported) {
-            const export_src = src; // TODO make this point at `export` token
-            if (is_inline) {
-                return mod.fail(&block_scope.base, export_src, "export of inline function", .{});
+            if (decl.is_exported) {
+                const export_src = src; // TODO make this point at `export` token
+                if (is_inline) {
+                    return mod.fail(&block_scope.base, export_src, "export of inline function", .{});
+                }
+                // The scope needs to have the decl in it.
+                try mod.analyzeExport(&block_scope.base, export_src, mem.spanZ(decl.name), decl);
             }
-            // The scope needs to have the decl in it.
-            try mod.analyzeExport(&block_scope.base, export_src, mem.spanZ(decl.name), decl);
-        }
-        return type_changed or is_inline != prev_is_inline;
-    } else {
-        var type_changed = true;
-        if (decl.has_tv) {
-            type_changed = !decl.ty.eql(decl_tv.ty);
-            decl.clearValues(gpa);
+            return type_changed or is_inline != prev_is_inline;
         }
+    }
+    var type_changed = true;
+    if (decl.has_tv) {
+        type_changed = !decl.ty.eql(decl_tv.ty);
+        decl.clearValues(gpa);
+    }
 
-        decl.owns_tv = false;
-        var queue_linker_work = false;
-        if (decl_tv.val.castTag(.variable)) |payload| {
-            const variable = payload.data;
-            if (variable.owner_decl == decl) {
-                decl.owns_tv = true;
-                queue_linker_work = true;
+    decl.owns_tv = false;
+    var queue_linker_work = false;
+    if (decl_tv.val.castTag(.variable)) |payload| {
+        const variable = payload.data;
+        if (variable.owner_decl == decl) {
+            decl.owns_tv = true;
+            queue_linker_work = true;
 
-                const copied_init = try variable.init.copy(&decl_arena.allocator);
-                variable.init = copied_init;
-            }
-        } else if (decl_tv.val.castTag(.extern_fn)) |payload| {
-            const owner_decl = payload.data;
-            if (decl == owner_decl) {
-                decl.owns_tv = true;
-                queue_linker_work = true;
-            }
+            const copied_init = try variable.init.copy(&decl_arena.allocator);
+            variable.init = copied_init;
         }
+    } else if (decl_tv.val.castTag(.extern_fn)) |payload| {
+        const owner_decl = payload.data;
+        if (decl == owner_decl) {
+            decl.owns_tv = true;
+            queue_linker_work = true;
+        }
+    }
 
-        decl.ty = try decl_tv.ty.copy(&decl_arena.allocator);
-        decl.val = try decl_tv.val.copy(&decl_arena.allocator);
-        decl.align_val = try align_val.copy(&decl_arena.allocator);
-        decl.linksection_val = try linksection_val.copy(&decl_arena.allocator);
-        decl.has_tv = true;
-        decl_arena_state.* = decl_arena.state;
-        decl.value_arena = decl_arena_state;
-        decl.analysis = .complete;
-        decl.generation = mod.generation;
-
-        if (queue_linker_work and decl.ty.hasCodeGenBits()) {
-            try mod.comp.bin_file.allocateDeclIndexes(decl);
-            try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl });
+    decl.ty = try decl_tv.ty.copy(&decl_arena.allocator);
+    decl.val = try decl_tv.val.copy(&decl_arena.allocator);
+    decl.align_val = try align_val.copy(&decl_arena.allocator);
+    decl.linksection_val = try linksection_val.copy(&decl_arena.allocator);
+    decl.has_tv = true;
+    decl_arena_state.* = decl_arena.state;
+    decl.value_arena = decl_arena_state;
+    decl.analysis = .complete;
+    decl.generation = mod.generation;
 
-            if (type_changed and mod.emit_h != null) {
-                try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl });
-            }
-        }
+    if (queue_linker_work and decl.ty.hasCodeGenBits()) {
+        try mod.comp.bin_file.allocateDeclIndexes(decl);
+        try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl });
 
-        if (decl.is_exported) {
-            const export_src = src; // TODO point to the export token
-            // The scope needs to have the decl in it.
-            try mod.analyzeExport(&block_scope.base, export_src, mem.spanZ(decl.name), decl);
+        if (type_changed and mod.emit_h != null) {
+            try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl });
         }
+    }
 
-        return type_changed;
+    if (decl.is_exported) {
+        const export_src = src; // TODO point to the export token
+        // The scope needs to have the decl in it.
+        try mod.analyzeExport(&block_scope.base, export_src, mem.spanZ(decl.name), decl);
     }
+
+    return type_changed;
 }
 
 /// Returns the depender's index of the dependee.