Commit 00da182e68

Jacob Young <jacobly0@users.noreply.github.com>
2024-06-30 06:11:51
cbe: fix for export changes
1 parent 0e5335a
Changed files (6)
lib/zig.h
@@ -207,16 +207,16 @@ typedef char bool;
     __asm(zig_mangle_c(name) " = " zig_mangle_c(symbol))
 #endif
 
+#define zig_mangled_tentative zig_mangled
+#define zig_mangled_final zig_mangled
 #if _MSC_VER
-#define zig_mangled_tentative(mangled, unmangled)
-#define zig_mangled_final(mangled, unmangled) ; \
+#define zig_mangled(mangled, unmangled) ; \
     zig_export(#mangled, unmangled)
 #define zig_mangled_export(mangled, unmangled, symbol) \
     zig_export(unmangled, #mangled) \
     zig_export(symbol, unmangled)
 #else /* _MSC_VER */
-#define zig_mangled_tentative(mangled, unmangled) __asm(zig_mangle_c(unmangled))
-#define zig_mangled_final(mangled, unmangled) zig_mangled_tentative(mangled, unmangled)
+#define zig_mangled(mangled, unmangled) __asm(zig_mangle_c(unmangled))
 #define zig_mangled_export(mangled, unmangled, symbol) \
     zig_mangled_final(mangled, unmangled) \
     zig_export(symbol, unmangled)
src/codegen/c.zig
@@ -731,8 +731,6 @@ pub const DeclGen = struct {
         if (decl.val.getExternFunc(zcu)) |extern_func| if (extern_func.decl != decl_index)
             return dg.renderDeclValue(writer, extern_func.decl, location);
 
-        if (decl.val.getVariable(zcu)) |variable| try dg.renderFwdDecl(decl_index, variable, .tentative);
-
         // We shouldn't cast C function pointers as this is UB (when you call
         // them).  The analysis until now should ensure that the C function
         // pointers are compatible.  If they are not, then there is a bug
@@ -748,7 +746,7 @@ pub const DeclGen = struct {
             try writer.writeByte(')');
         }
         try writer.writeByte('&');
-        try dg.renderDeclName(writer, decl_index, 0);
+        try dg.renderDeclName(writer, decl_index);
         if (need_cast) try writer.writeByte(')');
     }
 
@@ -1765,19 +1763,22 @@ pub const DeclGen = struct {
     fn renderFunctionSignature(
         dg: *DeclGen,
         w: anytype,
-        fn_decl_index: InternPool.DeclIndex,
+        fn_val: Value,
+        fn_align: InternPool.Alignment,
         kind: CType.Kind,
         name: union(enum) {
-            export_index: u32,
-            ident: []const u8,
+            decl: InternPool.DeclIndex,
             fmt_ctype_pool_string: std.fmt.Formatter(formatCTypePoolString),
+            @"export": struct {
+                main_name: InternPool.NullTerminatedString,
+                extern_name: InternPool.NullTerminatedString,
+            },
         },
     ) !void {
         const zcu = dg.zcu;
         const ip = &zcu.intern_pool;
 
-        const fn_decl = zcu.declPtr(fn_decl_index);
-        const fn_ty = fn_decl.typeOf(zcu);
+        const fn_ty = fn_val.typeOf(zcu);
         const fn_ctype = try dg.ctypeFromType(fn_ty, kind);
 
         const fn_info = zcu.typeToFunc(fn_ty).?;
@@ -1788,7 +1789,7 @@ pub const DeclGen = struct {
                 else => unreachable,
             }
         }
-        if (fn_decl.val.getFunction(zcu)) |func| if (func.analysis(ip).is_cold)
+        if (fn_val.getFunction(zcu)) |func| if (func.analysis(ip).is_cold)
             try w.writeAll("zig_cold ");
         if (fn_info.return_type == .noreturn_type) try w.writeAll("zig_noreturn ");
 
@@ -1799,22 +1800,11 @@ pub const DeclGen = struct {
             trailing = .maybe_space;
         }
 
-        switch (kind) {
-            .forward => {},
-            .complete => if (fn_decl.alignment.toByteUnits()) |a| {
-                try w.print("{}zig_align_fn({})", .{ trailing, a });
-                trailing = .maybe_space;
-            },
-            else => unreachable,
-        }
-
+        try w.print("{}", .{trailing});
         switch (name) {
-            .export_index => |export_index| {
-                try w.print("{}", .{trailing});
-                try dg.renderDeclName(w, fn_decl_index, export_index);
-            },
-            .ident => |ident| try w.print("{}{ }", .{ trailing, fmtIdent(ident) }),
-            .fmt_ctype_pool_string => |fmt| try w.print("{}{ }", .{ trailing, fmt }),
+            .decl => |decl_index| try dg.renderDeclName(w, decl_index),
+            .fmt_ctype_pool_string => |fmt| try w.print("{ }", .{fmt}),
+            .@"export" => |@"export"| try w.print("{ }", .{fmtIdent(@"export".extern_name.toSlice(ip))}),
         }
 
         try renderTypeSuffix(
@@ -1833,44 +1823,30 @@ pub const DeclGen = struct {
 
         switch (kind) {
             .forward => {
-                if (fn_decl.alignment.toByteUnits()) |a| {
-                    try w.print(" zig_align_fn({})", .{a});
-                }
+                if (fn_align.toByteUnits()) |a| try w.print(" zig_align_fn({})", .{a});
                 switch (name) {
-                    .export_index => |export_index| mangled: {
-                        const maybe_exports = zcu.decl_exports.get(fn_decl_index);
-                        const external_name = (if (maybe_exports) |exports|
-                            exports.items[export_index].opts.name
-                        else if (fn_decl.isExtern(zcu))
-                            fn_decl.name
-                        else
-                            break :mangled).toSlice(ip);
-                        const is_mangled = isMangledIdent(external_name, true);
-                        const is_export = export_index > 0;
+                    .decl, .fmt_ctype_pool_string => {},
+                    .@"export" => |@"export"| {
+                        const extern_name = @"export".extern_name.toSlice(ip);
+                        const is_mangled = isMangledIdent(extern_name, true);
+                        const is_export = @"export".extern_name != @"export".main_name;
                         if (is_mangled and is_export) {
                             try w.print(" zig_mangled_export({ }, {s}, {s})", .{
-                                fmtIdent(external_name),
-                                fmtStringLiteral(external_name, null),
-                                fmtStringLiteral(
-                                    maybe_exports.?.items[0].opts.name.toSlice(ip),
-                                    null,
-                                ),
+                                fmtIdent(extern_name),
+                                fmtStringLiteral(extern_name, null),
+                                fmtStringLiteral(@"export".main_name.toSlice(ip), null),
                             });
                         } else if (is_mangled) {
-                            try w.print(" zig_mangled_final({ }, {s})", .{
-                                fmtIdent(external_name), fmtStringLiteral(external_name, null),
+                            try w.print(" zig_mangled({ }, {s})", .{
+                                fmtIdent(extern_name), fmtStringLiteral(extern_name, null),
                             });
                         } else if (is_export) {
                             try w.print(" zig_export({s}, {s})", .{
-                                fmtStringLiteral(
-                                    maybe_exports.?.items[0].opts.name.toSlice(ip),
-                                    null,
-                                ),
-                                fmtStringLiteral(external_name, null),
+                                fmtStringLiteral(@"export".main_name.toSlice(ip), null),
+                                fmtStringLiteral(extern_name, null),
                             });
                         }
                     },
-                    .ident, .fmt_ctype_pool_string => {},
                 }
             },
             .complete => {},
@@ -2085,21 +2061,11 @@ pub const DeclGen = struct {
         try renderTypeSuffix(dg.pass, &dg.ctype_pool, dg.zcu, w, ctype, .suffix, .{});
     }
 
-    fn declIsGlobal(dg: *DeclGen, val: Value) bool {
-        const zcu = dg.zcu;
-        return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
-            .variable => |variable| zcu.decl_exports.contains(variable.decl),
-            .extern_func => true,
-            .func => |func| zcu.decl_exports.contains(func.owner_decl),
-            else => unreachable,
-        };
-    }
-
     fn writeName(dg: *DeclGen, w: anytype, c_value: CValue) !void {
         switch (c_value) {
             .new_local, .local => |i| try w.print("t{d}", .{i}),
             .constant => |val| try renderAnonDeclName(w, val),
-            .decl => |decl| try dg.renderDeclName(w, decl, 0),
+            .decl => |decl| try dg.renderDeclName(w, decl),
             .identifier => |ident| try w.print("{ }", .{fmtIdent(ident)}),
             else => unreachable,
         }
@@ -2111,10 +2077,10 @@ pub const DeclGen = struct {
             .constant => |val| try renderAnonDeclName(w, val),
             .arg, .arg_array => unreachable,
             .field => |i| try w.print("f{d}", .{i}),
-            .decl => |decl| try dg.renderDeclName(w, decl, 0),
+            .decl => |decl| try dg.renderDeclName(w, decl),
             .decl_ref => |decl| {
                 try w.writeByte('&');
-                try dg.renderDeclName(w, decl, 0);
+                try dg.renderDeclName(w, decl);
             },
             .undef => |ty| try dg.renderUndefValue(w, ty, .Other),
             .identifier => |ident| try w.print("{ }", .{fmtIdent(ident)}),
@@ -2142,10 +2108,10 @@ pub const DeclGen = struct {
             .field => |i| try w.print("f{d}", .{i}),
             .decl => |decl| {
                 try w.writeAll("(*");
-                try dg.renderDeclName(w, decl, 0);
+                try dg.renderDeclName(w, decl);
                 try w.writeByte(')');
             },
-            .decl_ref => |decl| try dg.renderDeclName(w, decl, 0),
+            .decl_ref => |decl| try dg.renderDeclName(w, decl),
             .undef => unreachable,
             .identifier => |ident| try w.print("(*{ })", .{fmtIdent(ident)}),
             .payload_identifier => |ident| try w.print("(*{ }.{ })", .{
@@ -2195,19 +2161,12 @@ pub const DeclGen = struct {
         dg: *DeclGen,
         decl_index: InternPool.DeclIndex,
         variable: InternPool.Key.Variable,
-        fwd_kind: enum { tentative, final },
     ) !void {
         const zcu = dg.zcu;
         const decl = zcu.declPtr(decl_index);
         const fwd = dg.fwdDeclWriter();
-        const is_global = variable.is_extern or dg.declIsGlobal(decl.val);
-        try fwd.writeAll(if (is_global) "zig_extern " else "static ");
-        const maybe_exports = zcu.decl_exports.get(decl_index);
-        const export_weak_linkage = if (maybe_exports) |exports|
-            exports.items[0].opts.linkage == .weak
-        else
-            false;
-        if (variable.is_weak_linkage or export_weak_linkage) try fwd.writeAll("zig_weak_linkage ");
+        try fwd.writeAll(if (variable.is_extern) "zig_extern " else "static ");
+        if (variable.is_weak_linkage) try fwd.writeAll("zig_weak_linkage ");
         if (variable.is_threadlocal and !dg.mod.single_threaded) try fwd.writeAll("zig_threadlocal ");
         try dg.renderTypeAndName(
             fwd,
@@ -2217,38 +2176,17 @@ pub const DeclGen = struct {
             decl.alignment,
             .complete,
         );
-        mangled: {
-            const external_name = (if (maybe_exports) |exports|
-                exports.items[0].opts.name
-            else if (variable.is_extern)
-                decl.name
-            else
-                break :mangled).toSlice(&zcu.intern_pool);
-            if (isMangledIdent(external_name, true)) {
-                try fwd.print(" zig_mangled_{s}({ }, {s})", .{
-                    @tagName(fwd_kind),
-                    fmtIdent(external_name),
-                    fmtStringLiteral(external_name, null),
-                });
-            }
-        }
         try fwd.writeAll(";\n");
     }
 
-    fn renderDeclName(dg: *DeclGen, writer: anytype, decl_index: InternPool.DeclIndex, export_index: u32) !void {
+    fn renderDeclName(dg: *DeclGen, writer: anytype, decl_index: InternPool.DeclIndex) !void {
         const zcu = dg.zcu;
         const ip = &zcu.intern_pool;
         const decl = zcu.declPtr(decl_index);
 
-        if (zcu.decl_exports.get(decl_index)) |exports| {
-            try writer.print("{ }", .{
-                fmtIdent(exports.items[export_index].opts.name.toSlice(ip)),
-            });
-        } else if (decl.getExternDecl(zcu).unwrap()) |extern_decl_index| {
-            try writer.print("{ }", .{
-                fmtIdent(zcu.declPtr(extern_decl_index).name.toSlice(ip)),
-            });
-        } else {
+        if (decl.getExternDecl(zcu).unwrap()) |extern_decl_index| try writer.print("{ }", .{
+            fmtIdent(zcu.declPtr(extern_decl_index).name.toSlice(ip)),
+        }) else {
             // MSVC has a limit of 4095 character token length limit, and fmtIdent can (worst case),
             // expand to 3x the length of its input, but let's cut it off at a much shorter limit.
             var name: [100]u8 = undefined;
@@ -2761,69 +2699,6 @@ pub fn genErrDecls(o: *Object) !void {
     try writer.writeAll("};\n");
 }
 
-fn genExports(o: *Object) !void {
-    const tracy = trace(@src());
-    defer tracy.end();
-
-    const zcu = o.dg.zcu;
-    const ip = &zcu.intern_pool;
-    const decl_index = switch (o.dg.pass) {
-        .decl => |decl| decl,
-        .anon, .flush => return,
-    };
-    const decl = zcu.declPtr(decl_index);
-    const fwd = o.dg.fwdDeclWriter();
-
-    const exports = zcu.decl_exports.get(decl_index) orelse return;
-    if (exports.items.len < 2) return;
-
-    const is_variable_const = switch (ip.indexToKey(decl.val.toIntern())) {
-        .func => return for (exports.items[1..], 1..) |@"export", i| {
-            try fwd.writeAll("zig_extern ");
-            if (@"export".opts.linkage == .weak) try fwd.writeAll("zig_weak_linkage_fn ");
-            try o.dg.renderFunctionSignature(
-                fwd,
-                decl_index,
-                .forward,
-                .{ .export_index = @intCast(i) },
-            );
-            try fwd.writeAll(";\n");
-        },
-        .extern_func => {
-            // TODO: when sema allows re-exporting extern decls
-            unreachable;
-        },
-        .variable => |variable| variable.is_const,
-        else => true,
-    };
-    for (exports.items[1..]) |@"export"| {
-        try fwd.writeAll("zig_extern ");
-        if (@"export".opts.linkage == .weak) try fwd.writeAll("zig_weak_linkage ");
-        const export_name = @"export".opts.name.toSlice(ip);
-        try o.dg.renderTypeAndName(
-            fwd,
-            decl.typeOf(zcu),
-            .{ .identifier = export_name },
-            CQualifiers.init(.{ .@"const" = is_variable_const }),
-            decl.alignment,
-            .complete,
-        );
-        if (isMangledIdent(export_name, true)) {
-            try fwd.print(" zig_mangled_export({ }, {s}, {s})", .{
-                fmtIdent(export_name),
-                fmtStringLiteral(export_name, null),
-                fmtStringLiteral(exports.items[0].opts.name.toSlice(ip), null),
-            });
-        } else {
-            try fwd.print(" zig_export({s}, {s})", .{
-                fmtStringLiteral(exports.items[0].opts.name.toSlice(ip), null),
-                fmtStringLiteral(export_name, null),
-            });
-        }
-        try fwd.writeAll(";\n");
-    }
-}
-
 pub fn genLazyFn(o: *Object, lazy_ctype_pool: *const CType.Pool, lazy_fn: LazyFnMap.Entry) !void {
     const zcu = o.dg.zcu;
     const ip = &zcu.intern_pool;
@@ -2885,19 +2760,19 @@ pub fn genLazyFn(o: *Object, lazy_ctype_pool: *const CType.Pool, lazy_fn: LazyFn
             const fn_info = fn_ctype.info(ctype_pool).function;
             const fn_name = fmtCTypePoolString(val.fn_name, lazy_ctype_pool);
 
-            const fwd_decl_writer = o.dg.fwdDeclWriter();
-            try fwd_decl_writer.print("static zig_{s} ", .{@tagName(key)});
-            try o.dg.renderFunctionSignature(fwd_decl_writer, fn_decl_index, .forward, .{
+            const fwd = o.dg.fwdDeclWriter();
+            try fwd.print("static zig_{s} ", .{@tagName(key)});
+            try o.dg.renderFunctionSignature(fwd, fn_decl.val, fn_decl.alignment, .forward, .{
                 .fmt_ctype_pool_string = fn_name,
             });
-            try fwd_decl_writer.writeAll(";\n");
+            try fwd.writeAll(";\n");
 
-            try w.print("static zig_{s} ", .{@tagName(key)});
-            try o.dg.renderFunctionSignature(w, fn_decl_index, .complete, .{
+            try w.print("zig_{s} ", .{@tagName(key)});
+            try o.dg.renderFunctionSignature(w, fn_decl.val, .none, .complete, .{
                 .fmt_ctype_pool_string = fn_name,
             });
             try w.writeAll(" {\n return ");
-            try o.dg.renderDeclName(w, fn_decl_index, 0);
+            try o.dg.renderDeclName(w, fn_decl_index);
             try w.writeByte('(');
             for (0..fn_info.param_ctypes.len) |arg| {
                 if (arg > 0) try w.writeAll(", ");
@@ -2921,21 +2796,26 @@ pub fn genFunc(f: *Function) !void {
     o.code_header = std.ArrayList(u8).init(gpa);
     defer o.code_header.deinit();
 
-    const is_global = o.dg.declIsGlobal(decl.val);
-    const fwd_decl_writer = o.dg.fwdDeclWriter();
-    try fwd_decl_writer.writeAll(if (is_global) "zig_extern " else "static ");
-
-    if (zcu.decl_exports.get(decl_index)) |exports|
-        if (exports.items[0].opts.linkage == .weak) try fwd_decl_writer.writeAll("zig_weak_linkage_fn ");
-    try o.dg.renderFunctionSignature(fwd_decl_writer, decl_index, .forward, .{ .export_index = 0 });
-    try fwd_decl_writer.writeAll(";\n");
-    try genExports(o);
+    const fwd = o.dg.fwdDeclWriter();
+    try fwd.writeAll("static ");
+    try o.dg.renderFunctionSignature(
+        fwd,
+        decl.val,
+        decl.alignment,
+        .forward,
+        .{ .decl = decl_index },
+    );
+    try fwd.writeAll(";\n");
 
-    try o.indent_writer.insertNewline();
-    if (!is_global) try o.writer().writeAll("static ");
     if (decl.@"linksection".toSlice(&zcu.intern_pool)) |s|
         try o.writer().print("zig_linksection_fn({s}) ", .{fmtStringLiteral(s, null)});
-    try o.dg.renderFunctionSignature(o.writer(), decl_index, .complete, .{ .export_index = 0 });
+    try o.dg.renderFunctionSignature(
+        o.writer(),
+        decl.val,
+        .none,
+        .complete,
+        .{ .decl = decl_index },
+    );
     try o.writer().writeByte(' ');
 
     // In case we need to use the header, populate it with a copy of the function
@@ -2949,7 +2829,6 @@ pub fn genFunc(f: *Function) !void {
 
     const main_body = f.air.getMainBody();
     try genBodyResolveState(f, undefined, &.{}, main_body, false);
-
     try o.indent_writer.insertNewline();
 
     // Take advantage of the free_locals map to bucket locals per type. All
@@ -3007,20 +2886,25 @@ pub fn genDecl(o: *Object) !void {
 
     if (!decl_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) return;
     if (decl.val.getExternFunc(zcu)) |_| {
-        const fwd_decl_writer = o.dg.fwdDeclWriter();
-        try fwd_decl_writer.writeAll("zig_extern ");
-        try o.dg.renderFunctionSignature(fwd_decl_writer, decl_index, .forward, .{ .export_index = 0 });
-        try fwd_decl_writer.writeAll(";\n");
-        try genExports(o);
+        const fwd = o.dg.fwdDeclWriter();
+        try fwd.writeAll("zig_extern ");
+        try o.dg.renderFunctionSignature(
+            fwd,
+            decl.val,
+            decl.alignment,
+            .forward,
+            .{ .@"export" = .{
+                .main_name = decl.name,
+                .extern_name = decl.name,
+            } },
+        );
+        try fwd.writeAll(";\n");
     } else if (decl.val.getVariable(zcu)) |variable| {
-        try o.dg.renderFwdDecl(decl_index, variable, .final);
-        try genExports(o);
+        try o.dg.renderFwdDecl(decl_index, variable);
 
         if (variable.is_extern) return;
 
-        const is_global = variable.is_extern or o.dg.declIsGlobal(decl.val);
         const w = o.writer();
-        if (!is_global) try w.writeAll("static ");
         if (variable.is_weak_linkage) try w.writeAll("zig_weak_linkage ");
         if (variable.is_threadlocal and !o.dg.mod.single_threaded) try w.writeAll("zig_threadlocal ");
         if (decl.@"linksection".toSlice(&zcu.intern_pool)) |s|
@@ -3032,46 +2916,27 @@ pub fn genDecl(o: *Object) !void {
         try w.writeByte(';');
         try o.indent_writer.insertNewline();
     } else {
-        const is_global = o.dg.zcu.decl_exports.contains(decl_index);
         const decl_c_value = .{ .decl = decl_index };
-        try genDeclValue(o, decl.val, is_global, decl_c_value, decl.alignment, decl.@"linksection");
+        try genDeclValue(o, decl.val, decl_c_value, decl.alignment, decl.@"linksection");
     }
 }
 
 pub fn genDeclValue(
     o: *Object,
     val: Value,
-    is_global: bool,
     decl_c_value: CValue,
     alignment: Alignment,
     @"linksection": InternPool.OptionalNullTerminatedString,
 ) !void {
     const zcu = o.dg.zcu;
-    const fwd_decl_writer = o.dg.fwdDeclWriter();
-
     const ty = val.typeOf(zcu);
 
-    try fwd_decl_writer.writeAll(if (is_global) "zig_extern " else "static ");
-    try o.dg.renderTypeAndName(fwd_decl_writer, ty, decl_c_value, Const, alignment, .complete);
-    switch (o.dg.pass) {
-        .decl => |decl_index| {
-            if (zcu.decl_exports.get(decl_index)) |exports| {
-                const export_name = exports.items[0].opts.name.toSlice(&zcu.intern_pool);
-                if (isMangledIdent(export_name, true)) {
-                    try fwd_decl_writer.print(" zig_mangled_final({ }, {s})", .{
-                        fmtIdent(export_name), fmtStringLiteral(export_name, null),
-                    });
-                }
-            }
-        },
-        .anon => {},
-        .flush => unreachable,
-    }
-    try fwd_decl_writer.writeAll(";\n");
-    try genExports(o);
+    const fwd = o.dg.fwdDeclWriter();
+    try fwd.writeAll("static ");
+    try o.dg.renderTypeAndName(fwd, ty, decl_c_value, Const, alignment, .complete);
+    try fwd.writeAll(";\n");
 
     const w = o.writer();
-    if (!is_global) try w.writeAll("static ");
     if (@"linksection".toSlice(&zcu.intern_pool)) |s|
         try w.print("zig_linksection({s}) ", .{fmtStringLiteral(s, null)});
     try o.dg.renderTypeAndName(w, ty, decl_c_value, Const, alignment, .complete);
@@ -3080,24 +2945,73 @@ pub fn genDeclValue(
     try w.writeAll(";\n");
 }
 
-pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void {
-    if (true) @panic("TODO jacobly");
-
-    const tracy = trace(@src());
-    defer tracy.end();
-
+pub fn genExports(dg: *DeclGen, exported: Zcu.Exported, export_indices: []const u32) !void {
     const zcu = dg.zcu;
-    const decl_index = dg.pass.decl;
-    const decl = zcu.declPtr(decl_index);
-    const writer = dg.fwdDeclWriter();
+    const ip = &zcu.intern_pool;
+    const fwd = dg.fwdDeclWriter();
 
-    switch (decl.typeOf(zcu).zigTypeTag(zcu)) {
-        .Fn => if (dg.declIsGlobal(decl.val)) {
-            try writer.writeAll("zig_extern ");
-            try dg.renderFunctionSignature(writer, dg.pass.decl, .complete, .{ .export_index = 0 });
-            try dg.fwd_decl.appendSlice(";\n");
+    const main_name = zcu.all_exports.items[export_indices[0]].opts.name;
+    try fwd.writeAll("#define ");
+    switch (exported) {
+        .decl_index => |decl_index| try dg.renderDeclName(fwd, decl_index),
+        .value => |value| try DeclGen.renderAnonDeclName(fwd, Value.fromInterned(value)),
+    }
+    try fwd.writeByte(' ');
+    try fwd.print("{ }", .{fmtIdent(main_name.toSlice(ip))});
+    try fwd.writeByte('\n');
+
+    const is_const = switch (ip.indexToKey(exported.getValue(zcu).toIntern())) {
+        .func, .extern_func => return for (export_indices) |export_index| {
+            const @"export" = &zcu.all_exports.items[export_index];
+            try fwd.writeAll("zig_extern ");
+            if (@"export".opts.linkage == .weak) try fwd.writeAll("zig_weak_linkage_fn ");
+            try dg.renderFunctionSignature(
+                fwd,
+                exported.getValue(zcu),
+                exported.getAlign(zcu),
+                .forward,
+                .{ .@"export" = .{
+                    .main_name = main_name,
+                    .extern_name = @"export".opts.name,
+                } },
+            );
+            try fwd.writeAll(";\n");
         },
-        else => {},
+        .variable => |variable| variable.is_const,
+        else => true,
+    };
+    for (export_indices) |export_index| {
+        const @"export" = &zcu.all_exports.items[export_index];
+        try fwd.writeAll("zig_extern ");
+        if (@"export".opts.linkage == .weak) try fwd.writeAll("zig_weak_linkage ");
+        const extern_name = @"export".opts.name.toSlice(ip);
+        const is_mangled = isMangledIdent(extern_name, true);
+        const is_export = @"export".opts.name != main_name;
+        try dg.renderTypeAndName(
+            fwd,
+            exported.getValue(zcu).typeOf(zcu),
+            .{ .identifier = extern_name },
+            CQualifiers.init(.{ .@"const" = is_const }),
+            exported.getAlign(zcu),
+            .complete,
+        );
+        if (is_mangled and is_export) {
+            try fwd.print(" zig_mangled_export({ }, {s}, {s})", .{
+                fmtIdent(extern_name),
+                fmtStringLiteral(extern_name, null),
+                fmtStringLiteral(main_name.toSlice(ip), null),
+            });
+        } else if (is_mangled) {
+            try fwd.print(" zig_mangled({ }, {s})", .{
+                fmtIdent(extern_name), fmtStringLiteral(extern_name, null),
+            });
+        } else if (is_export) {
+            try fwd.print(" zig_export({s}, {s})", .{
+                fmtStringLiteral(main_name.toSlice(ip), null),
+                fmtStringLiteral(extern_name, null),
+            });
+        }
+        try fwd.writeAll(";\n");
     }
 }
 
@@ -4554,7 +4468,7 @@ fn airCall(
                 };
             };
             switch (modifier) {
-                .auto, .always_tail => try f.object.dg.renderDeclName(writer, fn_decl, 0),
+                .auto, .always_tail => try f.object.dg.renderDeclName(writer, fn_decl),
                 inline .never_tail, .never_inline => |m| try writer.writeAll(try f.getLazyFnName(
                     @unionInit(LazyFnKey, @tagName(m), fn_decl),
                     @unionInit(LazyFnValue.Data, @tagName(m), {}),
src/link/C.zig
@@ -39,6 +39,9 @@ anon_decls: std.AutoArrayHashMapUnmanaged(InternPool.Index, DeclBlock) = .{},
 /// the keys of `anon_decls`.
 aligned_anon_decls: std.AutoArrayHashMapUnmanaged(InternPool.Index, Alignment) = .{},
 
+exported_decls: std.AutoArrayHashMapUnmanaged(InternPool.DeclIndex, ExportedBlock) = .{},
+exported_values: std.AutoArrayHashMapUnmanaged(InternPool.Index, ExportedBlock) = .{},
+
 /// Optimization, `updateDecl` reuses this buffer rather than creating a new
 /// one with every call.
 fwd_decl_buf: std.ArrayListUnmanaged(u8) = .{},
@@ -80,6 +83,11 @@ pub const DeclBlock = struct {
     }
 };
 
+/// Per-exported-symbol data.
+pub const ExportedBlock = struct {
+    fwd_decl: String = String.empty,
+};
+
 pub fn getString(this: C, s: String) []const u8 {
     return this.string_bytes.items[s.start..][0..s.len];
 }
@@ -183,8 +191,6 @@ pub fn updateFunc(
     air: Air,
     liveness: Liveness,
 ) !void {
-    if (true) @panic("TODO jacobly");
-
     const gpa = self.base.comp.gpa;
 
     const func = zcu.funcInfo(func_index);
@@ -240,9 +246,13 @@ pub fn updateFunc(
         function.deinit();
     }
 
+    try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1);
     codegen.genFunc(&function) catch |err| switch (err) {
         error.AnalysisFail => {
-            try zcu.failed_decls.put(gpa, decl_index, function.object.dg.error_msg.?);
+            zcu.failed_analysis.putAssumeCapacityNoClobber(
+                InternPool.AnalUnit.wrap(.{ .decl = decl_index }),
+                function.object.dg.error_msg.?,
+            );
             return;
         },
         else => |e| return e,
@@ -252,8 +262,6 @@ pub fn updateFunc(
 }
 
 fn updateAnonDecl(self: *C, zcu: *Zcu, i: usize) !void {
-    if (true) @panic("TODO jacobly");
-
     const gpa = self.base.comp.gpa;
     const anon_decl = self.anon_decls.keys()[i];
 
@@ -292,7 +300,7 @@ fn updateAnonDecl(self: *C, zcu: *Zcu, i: usize) !void {
 
     const c_value: codegen.CValue = .{ .constant = Value.fromInterned(anon_decl) };
     const alignment: Alignment = self.aligned_anon_decls.get(anon_decl) orelse .none;
-    codegen.genDeclValue(&object, c_value.constant, false, c_value, alignment, .none) catch |err| switch (err) {
+    codegen.genDeclValue(&object, c_value.constant, c_value, alignment, .none) catch |err| switch (err) {
         error.AnalysisFail => {
             @panic("TODO: C backend AnalysisFail on anonymous decl");
             //try zcu.failed_decls.put(gpa, decl_index, object.dg.error_msg.?);
@@ -310,8 +318,6 @@ fn updateAnonDecl(self: *C, zcu: *Zcu, i: usize) !void {
 }
 
 pub fn updateDecl(self: *C, zcu: *Zcu, decl_index: InternPool.DeclIndex) !void {
-    if (true) @panic("TODO jacobly");
-
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -357,9 +363,13 @@ pub fn updateDecl(self: *C, zcu: *Zcu, decl_index: InternPool.DeclIndex) !void {
         code.* = object.code.moveToUnmanaged();
     }
 
+    try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1);
     codegen.genDecl(&object) catch |err| switch (err) {
         error.AnalysisFail => {
-            try zcu.failed_decls.put(gpa, decl_index, object.dg.error_msg.?);
+            zcu.failed_analysis.putAssumeCapacityNoClobber(
+                InternPool.AnalUnit.wrap(.{ .decl = decl_index }),
+                object.dg.error_msg.?,
+            );
             return;
         },
         else => |e| return e,
@@ -396,8 +406,6 @@ fn abiDefines(self: *C, target: std.Target) !std.ArrayList(u8) {
 }
 
 pub fn flushModule(self: *C, arena: Allocator, prog_node: std.Progress.Node) !void {
-    if (true) @panic("TODO jacobly");
-
     _ = arena; // Has the same lifetime as the call to Compilation.update.
 
     const tracy = trace(@src());
@@ -460,26 +468,39 @@ pub fn flushModule(self: *C, arena: Allocator, prog_node: std.Progress.Node) !vo
         var export_names: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{};
         defer export_names.deinit(gpa);
         try export_names.ensureTotalCapacity(gpa, @intCast(zcu.single_exports.count()));
-        for (zcu.single_exports.values()) |export_idx| {
-            export_names.putAssumeCapacity(gpa, zcu.all_exports.items[export_idx].opts.name, {});
+        for (zcu.single_exports.values()) |export_index| {
+            export_names.putAssumeCapacity(zcu.all_exports.items[export_index].opts.name, {});
         }
         for (zcu.multi_exports.values()) |info| {
-            try export_names.ensureUnusedCapacity(info.len);
-            for (zcu.all_exports.items[info.index..][0..info.len]) |export_idx| {
-                export_names.putAssumeCapacity(gpa, zcu.all_exports.items[export_idx].opts.name, {});
+            try export_names.ensureUnusedCapacity(gpa, info.len);
+            for (zcu.all_exports.items[info.index..][0..info.len]) |@"export"| {
+                export_names.putAssumeCapacity(@"export".opts.name, {});
             }
         }
 
-        for (self.anon_decls.values()) |*decl_block| {
-            try self.flushDeclBlock(zcu, zcu.root_mod, &f, decl_block, export_names, .none);
-        }
+        for (self.anon_decls.keys(), self.anon_decls.values()) |value, *decl_block| try self.flushDeclBlock(
+            zcu,
+            zcu.root_mod,
+            &f,
+            decl_block,
+            self.exported_values.getPtr(value),
+            export_names,
+            .none,
+        );
 
         for (self.decl_table.keys(), self.decl_table.values()) |decl_index, *decl_block| {
             const decl = zcu.declPtr(decl_index);
-            assert(decl.has_tv);
-            const extern_symbol_name = if (decl.isExtern(zcu)) decl.name.toOptional() else .none;
+            const extern_name = if (decl.isExtern(zcu)) decl.name.toOptional() else .none;
             const mod = zcu.namespacePtr(decl.src_namespace).file_scope.mod;
-            try self.flushDeclBlock(zcu, mod, &f, decl_block, export_names, extern_symbol_name);
+            try self.flushDeclBlock(
+                zcu,
+                mod,
+                &f,
+                decl_block,
+                self.exported_decls.getPtr(decl_index),
+                export_names,
+                extern_name,
+            );
         }
     }
 
@@ -512,12 +533,16 @@ pub fn flushModule(self: *C, arena: Allocator, prog_node: std.Progress.Node) !vo
     f.file_size += lazy_fwd_decl_len;
 
     // Now the code.
-    const anon_decl_values = self.anon_decls.values();
-    const decl_values = self.decl_table.values();
-    try f.all_buffers.ensureUnusedCapacity(gpa, 1 + anon_decl_values.len + decl_values.len);
+    try f.all_buffers.ensureUnusedCapacity(gpa, 1 + (self.anon_decls.count() + self.decl_table.count()) * 2);
     f.appendBufAssumeCapacity(self.lazy_code_buf.items);
-    for (anon_decl_values) |db| f.appendBufAssumeCapacity(self.getString(db.code));
-    for (decl_values) |db| f.appendBufAssumeCapacity(self.getString(db.code));
+    for (self.anon_decls.keys(), self.anon_decls.values()) |anon_decl, decl_block| f.appendCodeAssumeCapacity(
+        self.exported_values.contains(anon_decl),
+        self.getString(decl_block.code),
+    );
+    for (self.decl_table.keys(), self.decl_table.values()) |decl_index, decl_block| f.appendCodeAssumeCapacity(
+        self.exported_decls.contains(decl_index),
+        self.getString(decl_block.code),
+    );
 
     const file = self.base.file.?;
     try file.setEndPos(f.file_size);
@@ -547,6 +572,12 @@ const Flush = struct {
         f.file_size += buf.len;
     }
 
+    fn appendCodeAssumeCapacity(f: *Flush, is_extern: bool, code: []const u8) void {
+        if (code.len == 0) return;
+        f.appendBufAssumeCapacity(if (is_extern) "\nzig_extern " else "\nstatic ");
+        f.appendBufAssumeCapacity(code);
+    }
+
     fn deinit(f: *Flush, gpa: Allocator) void {
         f.all_buffers.deinit(gpa);
         f.asm_buf.deinit(gpa);
@@ -734,19 +765,20 @@ fn flushDeclBlock(
     zcu: *Zcu,
     mod: *Module,
     f: *Flush,
-    decl_block: *DeclBlock,
+    decl_block: *const DeclBlock,
+    exported_block: ?*const ExportedBlock,
     export_names: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void),
-    extern_symbol_name: InternPool.OptionalNullTerminatedString,
+    extern_name: InternPool.OptionalNullTerminatedString,
 ) FlushDeclError!void {
     const gpa = self.base.comp.gpa;
     try self.flushLazyFns(zcu, mod, f, &decl_block.ctype_pool, decl_block.lazy_fns);
     try f.all_buffers.ensureUnusedCapacity(gpa, 1);
-    fwd_decl: {
-        if (extern_symbol_name.unwrap()) |name| {
-            if (export_names.contains(name)) break :fwd_decl;
-        }
-        f.appendBufAssumeCapacity(self.getString(decl_block.fwd_decl));
-    }
+    // avoid emitting extern decls that are already exported
+    if (extern_name.unwrap()) |name| if (export_names.contains(name)) return;
+    f.appendBufAssumeCapacity(self.getString(if (exported_block) |exported|
+        exported.fwd_decl
+    else
+        decl_block.fwd_decl));
 }
 
 pub fn flushEmitH(zcu: *Zcu) !void {
@@ -798,8 +830,56 @@ pub fn updateExports(
     exported: Zcu.Exported,
     export_indices: []const u32,
 ) !void {
-    _ = self;
-    _ = zcu;
-    _ = exported;
-    _ = export_indices;
+    const gpa = self.base.comp.gpa;
+    const mod, const pass: codegen.DeclGen.Pass, const decl_block, const exported_block = switch (exported) {
+        .decl_index => |decl_index| .{
+            zcu.namespacePtr(zcu.declPtr(decl_index).src_namespace).file_scope.mod,
+            .{ .decl = decl_index },
+            self.decl_table.getPtr(decl_index).?,
+            (try self.exported_decls.getOrPut(gpa, decl_index)).value_ptr,
+        },
+        .value => |value| .{
+            zcu.root_mod,
+            .{ .anon = value },
+            self.anon_decls.getPtr(value).?,
+            (try self.exported_values.getOrPut(gpa, value)).value_ptr,
+        },
+    };
+    const ctype_pool = &decl_block.ctype_pool;
+    const fwd_decl = &self.fwd_decl_buf;
+    fwd_decl.clearRetainingCapacity();
+    var dg: codegen.DeclGen = .{
+        .gpa = gpa,
+        .zcu = zcu,
+        .mod = mod,
+        .error_msg = null,
+        .pass = pass,
+        .is_naked_fn = false,
+        .fwd_decl = fwd_decl.toManaged(gpa),
+        .ctype_pool = decl_block.ctype_pool,
+        .scratch = .{},
+        .anon_decl_deps = .{},
+        .aligned_anon_decls = .{},
+    };
+    defer {
+        assert(dg.anon_decl_deps.count() == 0);
+        assert(dg.aligned_anon_decls.count() == 0);
+        fwd_decl.* = dg.fwd_decl.moveToUnmanaged();
+        ctype_pool.* = dg.ctype_pool.move();
+        ctype_pool.freeUnusedCapacity(gpa);
+        dg.scratch.deinit(gpa);
+    }
+    try codegen.genExports(&dg, exported, export_indices);
+    exported_block.* = .{ .fwd_decl = try self.addString(dg.fwd_decl.items) };
+}
+
+pub fn deleteExport(
+    self: *C,
+    exported: Zcu.Exported,
+    _: InternPool.NullTerminatedString,
+) void {
+    switch (exported) {
+        .decl_index => |decl_index| _ = self.exported_decls.swapRemove(decl_index),
+        .value => |value| _ = self.exported_values.swapRemove(value),
+    }
 }
src/Compilation.zig
@@ -3466,6 +3466,9 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: std.Progress.Node) !vo
             };
         },
         .emit_h_decl => |decl_index| {
+            if (true) @panic("regressed compiler feature: emit-h should hook into updateExports, " ++
+                "not decl analysis, which is too early to know about @export calls");
+
             const module = comp.module.?;
             const decl = module.declPtr(decl_index);
 
src/link.zig
@@ -679,7 +679,6 @@ pub const File = struct {
         if (build_options.only_c) @compileError("unreachable");
         switch (base.tag) {
             .plan9,
-            .c,
             .spirv,
             .nvptx,
             => {},
src/Zcu.zig
@@ -268,6 +268,20 @@ pub const Exported = union(enum) {
     decl_index: Decl.Index,
     /// Constant value being exported.
     value: InternPool.Index,
+
+    pub fn getValue(exported: Exported, zcu: *Zcu) Value {
+        return switch (exported) {
+            .decl_index => |decl_index| zcu.declPtr(decl_index).val,
+            .value => |value| Value.fromInterned(value),
+        };
+    }
+
+    pub fn getAlign(exported: Exported, zcu: *Zcu) Alignment {
+        return switch (exported) {
+            .decl_index => |decl_index| zcu.declPtr(decl_index).alignment,
+            .value => .none,
+        };
+    }
 };
 
 pub const Export = struct {