Commit 3af9731600

Andrew Kelley <andrew@ziglang.org>
2021-10-27 07:29:43
stage2: implement runtime pointer access to global constants
The main problem that motivated these changes is that global constants which are referenced by pointer would not be emitted into the binary. This happened because `semaDecl` did not add `codegen_decl` tasks for global constants, instead relying on the constant values being copied as necessary. However when the global constants are referenced by pointer, they need to be sent to the linker to be emitted. After making global const arrays, structs, and unions get emitted, this uncovered a latent issue: the anonymous decls that they referenced would get garbage collected (via `deleteUnusedDecl`) even though they would later be referenced by the global const. In order to solve this problem, I introduced `anon_work_queue` which is the same as `work_queue` except a lower priority. The `codegen_decl` task for anon decls goes into the `anon_work_queue` ensuring that the owner decl gets a chance to mark its anon decls as alive before they are possibly deleted. This caused a few regressions, which I made the judgement call to add workarounds for. Two steps forward, one step back, is still progress. The regressions were: * Two behavior tests having to do with unions. These tests were intentionally exercising the LLVM constant value lowering, however, due to the bug with garbage collection that was fixed in this commit, the LLVM code was not getting exercised, and union types/values were not implemented correctly, due to me forgetting that LLVM does not allow bitcasting aggregate values. - This is worked around by allowing those 2 test cases to regress, moving them to the "passing for stage1 only" section. * The test-stage2 test cases (in test/cases/*) for non-LLVM backends previously did not have any calls to lower struct values, but now they do. The code that was there was just `@panic("TODO")`. I replaced that code with a stub that generates the wrong value. This is an intentional miscompilation that will obviously need to get fixed before any struct behavior tests pass. None of the current tests we have exercise loading any values from these global const structs, so there is not a problem until we try to improve these backends.
1 parent c1fd459
src/codegen/wasm.zig
@@ -809,6 +809,11 @@ pub const Context = struct {
                 try self.emitConstant(val, ty);
                 return Result.appended;
             },
+            .Struct => {
+                // TODO write the fields for real
+                try self.code.writer().writeByteNTimes(0xaa, ty.abiSize(self.target));
+                return Result{ .appended = {} };
+            },
             else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}),
         }
     }
src/codegen.zig
@@ -285,6 +285,13 @@ pub fn generateSymbol(
             }
             return Result{ .appended = {} };
         },
+        .Struct => {
+            const field_vals = typed_value.val.castTag(.@"struct").?.data;
+            _ = field_vals; // TODO write the fields for real
+            const target = bin_file.options.target;
+            try code.writer().writeByteNTimes(0xaa, typed_value.ty.abiSize(target));
+            return Result{ .appended = {} };
+        },
         else => |t| {
             return Result{
                 .fail = try ErrorMsg.create(
src/Compilation.zig
@@ -46,6 +46,7 @@ stage1_cache_manifest: *Cache.Manifest = undefined,
 link_error_flags: link.File.ErrorFlags = .{},
 
 work_queue: std.fifo.LinearFifo(Job, .Dynamic),
+anon_work_queue: std.fifo.LinearFifo(Job, .Dynamic),
 
 /// These jobs are to invoke the Clang compiler to create an object file, which
 /// gets linked with the Compilation.
@@ -1460,6 +1461,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             .emit_analysis = options.emit_analysis,
             .emit_docs = options.emit_docs,
             .work_queue = std.fifo.LinearFifo(Job, .Dynamic).init(gpa),
+            .anon_work_queue = std.fifo.LinearFifo(Job, .Dynamic).init(gpa),
             .c_object_work_queue = std.fifo.LinearFifo(*CObject, .Dynamic).init(gpa),
             .astgen_work_queue = std.fifo.LinearFifo(*Module.File, .Dynamic).init(gpa),
             .embed_file_work_queue = std.fifo.LinearFifo(*Module.EmbedFile, .Dynamic).init(gpa),
@@ -1646,6 +1648,7 @@ pub fn destroy(self: *Compilation) void {
 
     const gpa = self.gpa;
     self.work_queue.deinit();
+    self.anon_work_queue.deinit();
     self.c_object_work_queue.deinit();
     self.astgen_work_queue.deinit();
     self.embed_file_work_queue.deinit();
@@ -2072,7 +2075,6 @@ pub fn getCompileLogOutput(self: *Compilation) []const u8 {
 }
 
 pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemory }!void {
-    const gpa = self.gpa;
     // If the terminal is dumb, we dont want to show the user all the
     // output.
     var progress: std.Progress = .{ .dont_print_on_dumb = true };
@@ -2146,7 +2148,24 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
         }
     }
 
-    while (self.work_queue.readItem()) |work_item| switch (work_item) {
+    // In this main loop we give priority to non-anonymous Decls in the work queue, so
+    // that they can establish references to anonymous Decls, setting alive=true in the
+    // backend, preventing anonymous Decls from being prematurely destroyed.
+    while (true) {
+        if (self.work_queue.readItem()) |work_item| {
+            try processOneJob(self, work_item, main_progress_node);
+            continue;
+        }
+        if (self.anon_work_queue.readItem()) |work_item| {
+            try processOneJob(self, work_item, main_progress_node);
+            continue;
+        }
+        break;
+    }
+}
+
+fn processOneJob(comp: *Compilation, job: Job, main_progress_node: *std.Progress.Node) !void {
+    switch (job) {
         .codegen_decl => |decl| switch (decl.analysis) {
             .unreferenced => unreachable,
             .in_progress => unreachable,
@@ -2157,24 +2176,25 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
             .codegen_failure,
             .dependency_failure,
             .sema_failure_retryable,
-            => continue,
+            => return,
 
             .complete, .codegen_failure_retryable => {
                 if (build_options.omit_stage2)
                     @panic("sadly stage2 is omitted from this build to save memory on the CI server");
 
-                const module = self.bin_file.options.module.?;
+                const module = comp.bin_file.options.module.?;
                 assert(decl.has_tv);
                 assert(decl.ty.hasCodeGenBits());
 
                 if (decl.alive) {
                     try module.linkerUpdateDecl(decl);
-                    continue;
+                    return;
                 }
 
                 // Instead of sending this decl to the linker, we actually will delete it
                 // because we found out that it in fact was never referenced.
                 module.deleteUnusedDecl(decl);
+                return;
             },
         },
         .codegen_func => |func| switch (func.owner_decl.analysis) {
@@ -2187,20 +2207,21 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
             .codegen_failure,
             .dependency_failure,
             .sema_failure_retryable,
-            => continue,
+            => return,
 
             .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,
+                    .sema_failure, .dependency_failure => return,
                     .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 gpa = comp.gpa;
+                const module = comp.bin_file.options.module.?;
                 const decl = func.owner_decl;
 
                 var tmp_arena = std.heap.ArenaAllocator.init(gpa);
@@ -2210,7 +2231,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
                 var air = module.analyzeFnBody(decl, func, sema_arena) catch |err| switch (err) {
                     error.AnalysisFail => {
                         assert(func.state != .in_progress);
-                        continue;
+                        return;
                     },
                     error.OutOfMemory => return error.OutOfMemory,
                 };
@@ -2220,17 +2241,17 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
                 var liveness = try Liveness.analyze(gpa, air, decl.getFileScope().zir);
                 defer liveness.deinit(gpa);
 
-                if (builtin.mode == .Debug and self.verbose_air) {
+                if (builtin.mode == .Debug and comp.verbose_air) {
                     std.debug.print("# Begin Function AIR: {s}:\n", .{decl.name});
                     @import("print_air.zig").dump(gpa, air, decl.getFileScope().zir, liveness);
                     std.debug.print("# End Function AIR: {s}\n\n", .{decl.name});
                 }
 
-                self.bin_file.updateFunc(module, func, air, liveness) catch |err| switch (err) {
+                comp.bin_file.updateFunc(module, func, air, liveness) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {
                         decl.analysis = .codegen_failure;
-                        continue;
+                        return;
                     },
                     else => {
                         try module.failed_decls.ensureUnusedCapacity(gpa, 1);
@@ -2241,10 +2262,10 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
                             .{@errorName(err)},
                         ));
                         decl.analysis = .codegen_failure_retryable;
-                        continue;
+                        return;
                     },
                 };
-                continue;
+                return;
             },
         },
         .emit_h_decl => |decl| switch (decl.analysis) {
@@ -2256,14 +2277,15 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
             .sema_failure,
             .dependency_failure,
             .sema_failure_retryable,
-            => continue,
+            => return,
 
             // emit-h only requires semantic analysis of the Decl to be complete,
             // it does not depend on machine code generation to succeed.
             .codegen_failure, .codegen_failure_retryable, .complete => {
                 if (build_options.omit_stage2)
                     @panic("sadly stage2 is omitted from this build to save memory on the CI server");
-                const module = self.bin_file.options.module.?;
+                const gpa = comp.gpa;
+                const module = comp.bin_file.options.module.?;
                 const emit_h = module.emit_h.?;
                 _ = try emit_h.decl_table.getOrPut(gpa, decl);
                 const decl_emit_h = decl.getEmitH(module);
@@ -2287,7 +2309,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
                 c_codegen.genHeader(&dg) catch |err| switch (err) {
                     error.AnalysisFail => {
                         try emit_h.failed_decls.put(gpa, decl, dg.error_msg.?);
-                        continue;
+                        return;
                     },
                     else => |e| return e,
                 };
@@ -2299,26 +2321,27 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
         .analyze_decl => |decl| {
             if (build_options.omit_stage2)
                 @panic("sadly stage2 is omitted from this build to save memory on the CI server");
-            const module = self.bin_file.options.module.?;
+            const module = comp.bin_file.options.module.?;
             module.ensureDeclAnalyzed(decl) catch |err| switch (err) {
                 error.OutOfMemory => return error.OutOfMemory,
-                error.AnalysisFail => continue,
+                error.AnalysisFail => return,
             };
         },
         .update_embed_file => |embed_file| {
             if (build_options.omit_stage2)
                 @panic("sadly stage2 is omitted from this build to save memory on the CI server");
-            const module = self.bin_file.options.module.?;
+            const module = comp.bin_file.options.module.?;
             module.updateEmbedFile(embed_file) catch |err| switch (err) {
                 error.OutOfMemory => return error.OutOfMemory,
-                error.AnalysisFail => continue,
+                error.AnalysisFail => return,
             };
         },
         .update_line_number => |decl| {
             if (build_options.omit_stage2)
                 @panic("sadly stage2 is omitted from this build to save memory on the CI server");
-            const module = self.bin_file.options.module.?;
-            self.bin_file.updateDeclLineNumber(module, decl) catch |err| {
+            const gpa = comp.gpa;
+            const module = comp.bin_file.options.module.?;
+            comp.bin_file.updateDeclLineNumber(module, decl) catch |err| {
                 try module.failed_decls.ensureUnusedCapacity(gpa, 1);
                 module.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create(
                     gpa,
@@ -2332,31 +2355,31 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
         .analyze_pkg => |pkg| {
             if (build_options.omit_stage2)
                 @panic("sadly stage2 is omitted from this build to save memory on the CI server");
-            const module = self.bin_file.options.module.?;
+            const module = comp.bin_file.options.module.?;
             module.semaPkg(pkg) catch |err| switch (err) {
                 error.CurrentWorkingDirectoryUnlinked,
                 error.Unexpected,
-                => try self.setMiscFailure(
+                => try comp.setMiscFailure(
                     .analyze_pkg,
                     "unexpected problem analyzing package '{s}'",
                     .{pkg.root_src_path},
                 ),
                 error.OutOfMemory => return error.OutOfMemory,
-                error.AnalysisFail => continue,
+                error.AnalysisFail => return,
             };
         },
         .glibc_crt_file => |crt_file| {
-            glibc.buildCRTFile(self, crt_file) catch |err| {
+            glibc.buildCRTFile(comp, crt_file) catch |err| {
                 // TODO Surface more error details.
-                try self.setMiscFailure(.glibc_crt_file, "unable to build glibc CRT file: {s}", .{
+                try comp.setMiscFailure(.glibc_crt_file, "unable to build glibc CRT file: {s}", .{
                     @errorName(err),
                 });
             };
         },
         .glibc_shared_objects => {
-            glibc.buildSharedObjects(self) catch |err| {
+            glibc.buildSharedObjects(comp) catch |err| {
                 // TODO Surface more error details.
-                try self.setMiscFailure(
+                try comp.setMiscFailure(
                     .glibc_shared_objects,
                     "unable to build glibc shared objects: {s}",
                     .{@errorName(err)},
@@ -2364,9 +2387,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
             };
         },
         .musl_crt_file => |crt_file| {
-            musl.buildCRTFile(self, crt_file) catch |err| {
+            musl.buildCRTFile(comp, crt_file) catch |err| {
                 // TODO Surface more error details.
-                try self.setMiscFailure(
+                try comp.setMiscFailure(
                     .musl_crt_file,
                     "unable to build musl CRT file: {s}",
                     .{@errorName(err)},
@@ -2374,9 +2397,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
             };
         },
         .mingw_crt_file => |crt_file| {
-            mingw.buildCRTFile(self, crt_file) catch |err| {
+            mingw.buildCRTFile(comp, crt_file) catch |err| {
                 // TODO Surface more error details.
-                try self.setMiscFailure(
+                try comp.setMiscFailure(
                     .mingw_crt_file,
                     "unable to build mingw-w64 CRT file: {s}",
                     .{@errorName(err)},
@@ -2384,10 +2407,10 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
             };
         },
         .windows_import_lib => |index| {
-            const link_lib = self.bin_file.options.system_libs.keys()[index];
-            mingw.buildImportLib(self, link_lib) catch |err| {
+            const link_lib = comp.bin_file.options.system_libs.keys()[index];
+            mingw.buildImportLib(comp, link_lib) catch |err| {
                 // TODO Surface more error details.
-                try self.setMiscFailure(
+                try comp.setMiscFailure(
                     .windows_import_lib,
                     "unable to generate DLL import .lib file: {s}",
                     .{@errorName(err)},
@@ -2395,9 +2418,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
             };
         },
         .libunwind => {
-            libunwind.buildStaticLib(self) catch |err| {
+            libunwind.buildStaticLib(comp) catch |err| {
                 // TODO Surface more error details.
-                try self.setMiscFailure(
+                try comp.setMiscFailure(
                     .libunwind,
                     "unable to build libunwind: {s}",
                     .{@errorName(err)},
@@ -2405,9 +2428,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
             };
         },
         .libcxx => {
-            libcxx.buildLibCXX(self) catch |err| {
+            libcxx.buildLibCXX(comp) catch |err| {
                 // TODO Surface more error details.
-                try self.setMiscFailure(
+                try comp.setMiscFailure(
                     .libcxx,
                     "unable to build libcxx: {s}",
                     .{@errorName(err)},
@@ -2415,9 +2438,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
             };
         },
         .libcxxabi => {
-            libcxx.buildLibCXXABI(self) catch |err| {
+            libcxx.buildLibCXXABI(comp) catch |err| {
                 // TODO Surface more error details.
-                try self.setMiscFailure(
+                try comp.setMiscFailure(
                     .libcxxabi,
                     "unable to build libcxxabi: {s}",
                     .{@errorName(err)},
@@ -2425,9 +2448,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
             };
         },
         .libtsan => {
-            libtsan.buildTsan(self) catch |err| {
+            libtsan.buildTsan(comp) catch |err| {
                 // TODO Surface more error details.
-                try self.setMiscFailure(
+                try comp.setMiscFailure(
                     .libtsan,
                     "unable to build TSAN library: {s}",
                     .{@errorName(err)},
@@ -2435,9 +2458,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
             };
         },
         .wasi_libc_crt_file => |crt_file| {
-            wasi_libc.buildCRTFile(self, crt_file) catch |err| {
+            wasi_libc.buildCRTFile(comp, crt_file) catch |err| {
                 // TODO Surface more error details.
-                try self.setMiscFailure(
+                try comp.setMiscFailure(
                     .wasi_libc_crt_file,
                     "unable to build WASI libc CRT file: {s}",
                     .{@errorName(err)},
@@ -2445,15 +2468,15 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
             };
         },
         .compiler_rt_lib => {
-            self.buildOutputFromZig(
+            comp.buildOutputFromZig(
                 "compiler_rt.zig",
                 .Lib,
-                &self.compiler_rt_static_lib,
+                &comp.compiler_rt_static_lib,
                 .compiler_rt,
             ) catch |err| switch (err) {
                 error.OutOfMemory => return error.OutOfMemory,
-                error.SubCompilationFailed => continue, // error reported already
-                else => try self.setMiscFailure(
+                error.SubCompilationFailed => return, // error reported already
+                else => try comp.setMiscFailure(
                     .compiler_rt,
                     "unable to build compiler_rt: {s}",
                     .{@errorName(err)},
@@ -2461,15 +2484,15 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
             };
         },
         .compiler_rt_obj => {
-            self.buildOutputFromZig(
+            comp.buildOutputFromZig(
                 "compiler_rt.zig",
                 .Obj,
-                &self.compiler_rt_obj,
+                &comp.compiler_rt_obj,
                 .compiler_rt,
             ) catch |err| switch (err) {
                 error.OutOfMemory => return error.OutOfMemory,
-                error.SubCompilationFailed => continue, // error reported already
-                else => try self.setMiscFailure(
+                error.SubCompilationFailed => return, // error reported already
+                else => try comp.setMiscFailure(
                     .compiler_rt,
                     "unable to build compiler_rt: {s}",
                     .{@errorName(err)},
@@ -2477,15 +2500,15 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
             };
         },
         .libssp => {
-            self.buildOutputFromZig(
+            comp.buildOutputFromZig(
                 "ssp.zig",
                 .Lib,
-                &self.libssp_static_lib,
+                &comp.libssp_static_lib,
                 .libssp,
             ) catch |err| switch (err) {
                 error.OutOfMemory => return error.OutOfMemory,
-                error.SubCompilationFailed => continue, // error reported already
-                else => try self.setMiscFailure(
+                error.SubCompilationFailed => return, // error reported already
+                else => try comp.setMiscFailure(
                     .libssp,
                     "unable to build libssp: {s}",
                     .{@errorName(err)},
@@ -2493,15 +2516,15 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
             };
         },
         .zig_libc => {
-            self.buildOutputFromZig(
+            comp.buildOutputFromZig(
                 "c.zig",
                 .Lib,
-                &self.libc_static_lib,
+                &comp.libc_static_lib,
                 .zig_libc,
             ) catch |err| switch (err) {
                 error.OutOfMemory => return error.OutOfMemory,
-                error.SubCompilationFailed => continue, // error reported already
-                else => try self.setMiscFailure(
+                error.SubCompilationFailed => return, // error reported already
+                else => try comp.setMiscFailure(
                     .zig_libc,
                     "unable to build zig's multitarget libc: {s}",
                     .{@errorName(err)},
@@ -2512,11 +2535,11 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
             if (!build_options.is_stage1)
                 unreachable;
 
-            self.updateStage1Module(main_progress_node) catch |err| {
+            comp.updateStage1Module(main_progress_node) catch |err| {
                 fatal("unable to build stage1 zig object: {s}", .{@errorName(err)});
             };
         },
-    };
+    }
 }
 
 const AstGenSrc = union(enum) {
src/Module.zig
@@ -3039,10 +3039,7 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl: *Decl) SemaError!void {
                     log.debug("insert {*} ({s}) dependant {*} ({s}) into deletion set", .{
                         decl, decl.name, dep, dep.name,
                     });
-                    // We don't perform a deletion here, because this Decl or another one
-                    // may end up referencing it before the update is complete.
-                    dep.deletion_flag = true;
-                    try mod.deletion_set.put(mod.gpa, dep, {});
+                    try mod.markDeclForDeletion(dep);
                 }
             }
             decl.dependencies.clearRetainingCapacity();
@@ -3433,21 +3430,29 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
 
     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;
+    switch (decl_tv.val.tag()) {
+        .variable => {
+            const variable = decl_tv.val.castTag(.variable).?.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;
+            }
+        },
+        .extern_fn => {
+            const owner_decl = decl_tv.val.castTag(.extern_fn).?.data;
+            if (decl == owner_decl) {
+                decl.owns_tv = true;
+                queue_linker_work = true;
+            }
+        },
+        .array, .@"struct", .@"union" => {
+            log.debug("send global const to linker: {*} ({s})", .{ decl, decl.name });
             queue_linker_work = true;
-        }
+        },
+        else => {},
     }
 
     decl.ty = try decl_tv.ty.copy(&decl_arena.allocator);
@@ -3462,6 +3467,8 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
     decl.generation = mod.generation;
 
     if (queue_linker_work and decl.ty.hasCodeGenBits()) {
+        log.debug("queue linker work for {*} ({s})", .{ decl, decl.name });
+
         try mod.comp.bin_file.allocateDeclIndexes(decl);
         try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl });
 
@@ -3985,6 +3992,7 @@ pub fn clearDecl(
     decl.analysis = .unreferenced;
 }
 
+/// This function is exclusively called for anonymous decls.
 pub fn deleteUnusedDecl(mod: *Module, decl: *Decl) void {
     log.debug("deleteUnusedDecl {*} ({s})", .{ decl, decl.name });
 
@@ -4019,6 +4027,13 @@ pub fn deleteUnusedDecl(mod: *Module, decl: *Decl) void {
     decl.destroy(mod);
 }
 
+/// We don't perform a deletion here, because this Decl or another one
+/// may end up referencing it before the update is complete.
+fn markDeclForDeletion(mod: *Module, decl: *Decl) !void {
+    decl.deletion_flag = true;
+    try mod.deletion_set.put(mod.gpa, decl, {});
+}
+
 /// Cancel the creation of an anon decl and delete any references to it.
 /// If other decls depend on this decl, they must be aborted first.
 pub fn abortAnonDecl(mod: *Module, decl: *Decl) void {
@@ -4369,12 +4384,13 @@ pub fn createAnonymousDeclFromDeclNamed(
 
     namespace.anon_decls.putAssumeCapacityNoClobber(new_decl, {});
 
-    // TODO: This generates the Decl into the machine code file if it is of a
-    // type that is non-zero size. We should be able to further improve the
-    // compiler to omit Decls which are only referenced at compile-time and not runtime.
+    // The Decl starts off with alive=false and the codegen backend will set alive=true
+    // if the Decl is referenced by an instruction or another constant. Otherwise,
+    // the Decl will be garbage collected by the `codegen_decl` task instead of sent
+    // to the linker.
     if (typed_value.ty.hasCodeGenBits()) {
         try mod.comp.bin_file.allocateDeclIndexes(new_decl);
-        try mod.comp.work_queue.writeItem(.{ .codegen_decl = new_decl });
+        try mod.comp.anon_work_queue.writeItem(.{ .codegen_decl = new_decl });
     }
 
     return new_decl;
test/behavior/basic.zig
@@ -459,3 +459,18 @@ var gdt = [_]GDTEntry{
     GDTEntry{ .field = 2 },
 };
 var global_ptr = &gdt[0];
+
+test "global constant is loaded with a runtime-known index" {
+    const S = struct {
+        fn doTheTest() !void {
+            var index: usize = 1;
+            const ptr = &pieces[index].field;
+            try expect(ptr.* == 2);
+        }
+        const Piece = struct {
+            field: i32,
+        };
+        const pieces = [_]Piece{ Piece{ .field = 1 }, Piece{ .field = 2 }, Piece{ .field = 3 } };
+    };
+    try S.doTheTest();
+}
test/behavior/switch.zig
@@ -90,25 +90,6 @@ fn returnsFive() i32 {
     return 5;
 }
 
-const Number = union(enum) {
-    One: u64,
-    Two: u8,
-    Three: f32,
-};
-
-const number = Number{ .Three = 1.23 };
-
-fn returnsFalse() bool {
-    switch (number) {
-        Number.One => |x| return x > 1234,
-        Number.Two => |x| return x == 'a',
-        Number.Three => |x| return x > 12.34,
-    }
-}
-test "switch on const enum with var" {
-    try expect(!returnsFalse());
-}
-
 test "switch on type" {
     try expect(trueIfBoolFalseOtherwise(bool));
     try expect(!trueIfBoolFalseOtherwise(i32));
test/behavior/switch_stage1.zig
@@ -3,6 +3,24 @@ const expect = std.testing.expect;
 const expectError = std.testing.expectError;
 const expectEqual = std.testing.expectEqual;
 
+const Number = union(enum) {
+    One: u64,
+    Two: u8,
+    Three: f32,
+};
+
+const number = Number{ .Three = 1.23 };
+
+fn returnsFalse() bool {
+    switch (number) {
+        Number.One => |x| return x > 1234,
+        Number.Two => |x| return x == 'a',
+        Number.Three => |x| return x > 12.34,
+    }
+}
+test "switch on const enum with var" {
+    try expect(!returnsFalse());
+}
 test "switch all prongs unreachable" {
     try testAllProngsUnreachable();
     comptime try testAllProngsUnreachable();
test/behavior/union.zig
@@ -71,34 +71,3 @@ test "0-sized extern union definition" {
 
     try expect(U.f == 1);
 }
-
-const Value = union(enum) {
-    Int: u64,
-    Array: [9]u8,
-};
-
-const Agg = struct {
-    val1: Value,
-    val2: Value,
-};
-
-const v1 = Value{ .Int = 1234 };
-const v2 = Value{ .Array = [_]u8{3} ** 9 };
-
-const err = @as(anyerror!Agg, Agg{
-    .val1 = v1,
-    .val2 = v2,
-});
-
-const array = [_]Value{ v1, v2, v1, v2 };
-
-test "unions embedded in aggregate types" {
-    switch (array[1]) {
-        Value.Array => |arr| try expect(arr[4] == 3),
-        else => unreachable,
-    }
-    switch ((err catch unreachable).val1) {
-        Value.Int => |x| try expect(x == 1234),
-        else => unreachable,
-    }
-}
test/behavior/union_stage1.zig
@@ -3,6 +3,37 @@ const expect = std.testing.expect;
 const expectEqual = std.testing.expectEqual;
 const Tag = std.meta.Tag;
 
+const Value = union(enum) {
+    Int: u64,
+    Array: [9]u8,
+};
+
+const Agg = struct {
+    val1: Value,
+    val2: Value,
+};
+
+const v1 = Value{ .Int = 1234 };
+const v2 = Value{ .Array = [_]u8{3} ** 9 };
+
+const err = @as(anyerror!Agg, Agg{
+    .val1 = v1,
+    .val2 = v2,
+});
+
+const array = [_]Value{ v1, v2, v1, v2 };
+
+test "unions embedded in aggregate types" {
+    switch (array[1]) {
+        Value.Array => |arr| try expect(arr[4] == 3),
+        else => unreachable,
+    }
+    switch ((err catch unreachable).val1) {
+        Value.Int => |x| try expect(x == 1234),
+        else => unreachable,
+    }
+}
+
 const Letter = enum { A, B, C };
 const Payload = union(Letter) {
     A: i32,