Commit 4765294ca4

Jacob Young <jacobly0@users.noreply.github.com>
2022-10-19 13:56:02
cbe: get enough things working to support basic programs
* Enable advanced start support. * Enable advanced test_runner support. * Zig Language Reference's Hello World now works.
1 parent 912b84b
Changed files (11)
lib/include/zig.h
@@ -14,6 +14,12 @@
 #define zig_threadlocal zig_threadlocal_unavailable
 #endif
 
+#if defined(_MSC_VER)
+#define ZIG_NAKED __declspec(naked)
+#else
+#define ZIG_NAKED __attribute__((naked))
+#endif
+
 #if __GNUC__
 #define ZIG_COLD __attribute__ ((cold))
 #else
@@ -165,7 +171,6 @@
 #include <stdint.h>
 #include <stddef.h>
 #include <limits.h>
-#include <math.h>
 
 #define int128_t __int128
 #define uint128_t unsigned __int128
lib/std/os/linux/x86_64.zig
@@ -108,11 +108,18 @@ pub extern fn clone(func: CloneFn, stack: usize, flags: usize, arg: usize, ptid:
 pub const restore = restore_rt;
 
 pub fn restore_rt() callconv(.Naked) void {
-    return asm volatile ("syscall"
-        :
-        : [number] "{rax}" (@enumToInt(SYS.rt_sigreturn)),
-        : "rcx", "r11", "memory"
-    );
+    switch (@import("builtin").zig_backend) {
+        .stage2_c => return asm volatile (std.fmt.comptimePrint(
+                \\ movl ${d}, %%eax
+                \\ syscall
+                \\ retq
+            , .{@enumToInt(SYS.rt_sigreturn)}) ::: "rcx", "r11", "memory"),
+        else => return asm volatile ("syscall"
+            :
+            : [number] "{rax}" (@enumToInt(SYS.rt_sigreturn)),
+            : "rcx", "r11", "memory"
+        ),
+    }
 }
 
 pub const mode_t = usize;
lib/std/debug.zig
@@ -1222,7 +1222,13 @@ pub const DebugInfo = struct {
     }
 
     pub fn getModuleForAddress(self: *DebugInfo, address: usize) !*ModuleDebugInfo {
-        if (comptime builtin.target.isDarwin()) {
+        if (builtin.zig_backend == .stage2_c) {
+            return @as(error{
+                InvalidDebugInfo,
+                MissingDebugInfo,
+                UnsupportedBackend,
+            }, error.UnsupportedBackend);
+        } else if (comptime builtin.target.isDarwin()) {
             return self.lookupModuleDyld(address);
         } else if (native_os == .windows) {
             return self.lookupModuleWin32(address);
lib/std/start.zig
@@ -23,7 +23,6 @@ comptime {
     // Until then, we have simplified logic here for self-hosted. TODO remove this once
     // self-hosted is capable enough to handle all of the real start.zig logic.
     if (builtin.zig_backend == .stage2_wasm or
-        builtin.zig_backend == .stage2_c or
         builtin.zig_backend == .stage2_x86_64 or
         builtin.zig_backend == .stage2_x86 or
         builtin.zig_backend == .stage2_aarch64 or
@@ -265,75 +264,90 @@ fn EfiMain(handle: uefi.Handle, system_table: *uefi.tables.SystemTable) callconv
 }
 
 fn _start() callconv(.Naked) noreturn {
-    switch (native_arch) {
-        .x86_64 => {
-            argc_argv_ptr = asm volatile (
-                \\ xor %%rbp, %%rbp
-                : [argc] "={rsp}" (-> [*]usize),
-            );
-        },
-        .i386 => {
-            argc_argv_ptr = asm volatile (
-                \\ xor %%ebp, %%ebp
-                : [argc] "={esp}" (-> [*]usize),
-            );
-        },
-        .aarch64, .aarch64_be, .arm, .armeb, .thumb => {
-            argc_argv_ptr = asm volatile (
-                \\ mov fp, #0
-                \\ mov lr, #0
-                : [argc] "={sp}" (-> [*]usize),
-            );
-        },
-        .riscv64 => {
-            argc_argv_ptr = asm volatile (
-                \\ li s0, 0
-                \\ li ra, 0
-                : [argc] "={sp}" (-> [*]usize),
-            );
-        },
-        .mips, .mipsel => {
-            // The lr is already zeroed on entry, as specified by the ABI.
-            argc_argv_ptr = asm volatile (
-                \\ move $fp, $0
-                : [argc] "={sp}" (-> [*]usize),
-            );
-        },
-        .powerpc => {
-            // Setup the initial stack frame and clear the back chain pointer.
-            argc_argv_ptr = asm volatile (
-                \\ mr 4, 1
-                \\ li 0, 0
-                \\ stwu 1,-16(1)
-                \\ stw 0, 0(1)
-                \\ mtlr 0
-                : [argc] "={r4}" (-> [*]usize),
-                :
-                : "r0"
-            );
-        },
-        .powerpc64le => {
-            // Setup the initial stack frame and clear the back chain pointer.
-            // TODO: Support powerpc64 (big endian) on ELFv2.
-            argc_argv_ptr = asm volatile (
-                \\ mr 4, 1
-                \\ li 0, 0
-                \\ stdu 0, -32(1)
-                \\ mtlr 0
-                : [argc] "={r4}" (-> [*]usize),
-                :
-                : "r0"
-            );
+    switch (builtin.zig_backend) {
+        .stage2_c => switch (native_arch) {
+            .x86_64 => {
+                @export(argc_argv_ptr, .{ .name = "argc_argv_ptr" });
+                @export(posixCallMainAndExit, .{ .name = "_posixCallMainAndExit" });
+                asm volatile (
+                    \\ xor %%rbp, %%rbp
+                    \\ mov %%rsp, argc_argv_ptr
+                    \\ call _posixCallMainAndExit
+                );
+                unreachable;
+            },
+            else => @compileError("unsupported arch"),
         },
-        .sparc64 => {
-            // argc is stored after a register window (16 registers) plus stack bias
-            argc_argv_ptr = asm (
-                \\ mov %%g0, %%i6
-                \\ add %%o6, 2175, %[argc]
-                : [argc] "=r" (-> [*]usize),
-            );
+        else => switch (native_arch) {
+            .x86_64 => {
+                argc_argv_ptr = asm volatile (
+                    \\ xor %%rbp, %%rbp
+                    : [argc] "={rsp}" (-> [*]usize),
+                );
+            },
+            .i386 => {
+                argc_argv_ptr = asm volatile (
+                    \\ xor %%ebp, %%ebp
+                    : [argc] "={esp}" (-> [*]usize),
+                );
+            },
+            .aarch64, .aarch64_be, .arm, .armeb, .thumb => {
+                argc_argv_ptr = asm volatile (
+                    \\ mov fp, #0
+                    \\ mov lr, #0
+                    : [argc] "={sp}" (-> [*]usize),
+                );
+            },
+            .riscv64 => {
+                argc_argv_ptr = asm volatile (
+                    \\ li s0, 0
+                    \\ li ra, 0
+                    : [argc] "={sp}" (-> [*]usize),
+                );
+            },
+            .mips, .mipsel => {
+                // The lr is already zeroed on entry, as specified by the ABI.
+                argc_argv_ptr = asm volatile (
+                    \\ move $fp, $0
+                    : [argc] "={sp}" (-> [*]usize),
+                );
+            },
+            .powerpc => {
+                // Setup the initial stack frame and clear the back chain pointer.
+                argc_argv_ptr = asm volatile (
+                    \\ mr 4, 1
+                    \\ li 0, 0
+                    \\ stwu 1,-16(1)
+                    \\ stw 0, 0(1)
+                    \\ mtlr 0
+                    : [argc] "={r4}" (-> [*]usize),
+                    :
+                    : "r0"
+                );
+            },
+            .powerpc64le => {
+                // Setup the initial stack frame and clear the back chain pointer.
+                // TODO: Support powerpc64 (big endian) on ELFv2.
+                argc_argv_ptr = asm volatile (
+                    \\ mr 4, 1
+                    \\ li 0, 0
+                    \\ stdu 0, -32(1)
+                    \\ mtlr 0
+                    : [argc] "={r4}" (-> [*]usize),
+                    :
+                    : "r0"
+                );
+            },
+            .sparc64 => {
+                // argc is stored after a register window (16 registers) plus stack bias
+                argc_argv_ptr = asm (
+                    \\ mov %%g0, %%i6
+                    \\ add %%o6, 2175, %[argc]
+                    : [argc] "=r" (-> [*]usize),
+                );
+            },
+            else => @compileError("unsupported arch"),
         },
-        else => @compileError("unsupported arch"),
     }
     // If LLVM inlines stack variables into _start, they will overwrite
     // the command line argument data.
@@ -363,7 +377,7 @@ fn wWinMainCRTStartup() callconv(std.os.windows.WINAPI) noreturn {
     std.os.windows.kernel32.ExitProcess(@bitCast(std.os.windows.UINT, result));
 }
 
-fn posixCallMainAndExit() noreturn {
+fn posixCallMainAndExit() callconv(.C) noreturn {
     @setAlignStack(16);
 
     const argc = argc_argv_ptr[0];
@@ -462,7 +476,7 @@ fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {
     return initEventLoopAndCallMain();
 }
 
-fn main(c_argc: i32, c_argv: [*][*:0]u8, c_envp: [*:null]?[*:0]u8) callconv(.C) i32 {
+fn main(c_argc: c_int, c_argv: [*c][*c]u8, c_envp: [*c][*c]u8) callconv(.C) c_int {
     var env_count: usize = 0;
     while (c_envp[env_count] != null) : (env_count += 1) {}
     const envp = @ptrCast([*][*:0]u8, c_envp)[0..env_count];
@@ -474,10 +488,10 @@ fn main(c_argc: i32, c_argv: [*][*:0]u8, c_envp: [*:null]?[*:0]u8) callconv(.C)
         expandStackSize(phdrs);
     }
 
-    return @call(.{ .modifier = .always_inline }, callMainWithArgs, .{ @intCast(usize, c_argc), c_argv, envp });
+    return @call(.{ .modifier = .always_inline }, callMainWithArgs, .{ @intCast(usize, c_argc), @ptrCast([*][*:0]u8, c_argv), envp });
 }
 
-fn mainWithoutEnv(c_argc: i32, c_argv: [*][*:0]u8) callconv(.C) usize {
+fn mainWithoutEnv(c_argc: c_int, c_argv: [*c][*c]u8) callconv(.C) c_int {
     std.os.argv = c_argv[0..@intCast(usize, c_argc)];
     return @call(.{ .modifier = .always_inline }, callMain, .{});
 }
lib/test_runner.zig
@@ -8,7 +8,8 @@ var log_err_count: usize = 0;
 
 pub fn main() void {
     if (builtin.zig_backend != .stage1 and
-        (builtin.zig_backend != .stage2_llvm or builtin.cpu.arch == .wasm32))
+        (builtin.zig_backend != .stage2_llvm or builtin.cpu.arch == .wasm32) and
+        builtin.zig_backend != .stage2_c)
     {
         return main2() catch @panic("test failure");
     }
src/codegen/c.zig
@@ -34,8 +34,8 @@ pub const CValue = union(enum) {
     /// By-value
     decl: Decl.Index,
     decl_ref: Decl.Index,
-    /// An undefined (void *) pointer (cannot be dereferenced)
-    undefined_ptr: Type,
+    /// An undefined value (cannot be dereferenced)
+    undef: Type,
     /// Render the slice as an identifier (using fmtIdent)
     identifier: []const u8,
     /// Render these bytes literally.
@@ -342,8 +342,9 @@ pub const Function = struct {
         const float_bits = float_ty.floatBits(target);
         const is_longdouble = float_bits == CType.longdouble.sizeInBits(target);
         const writer = f.object.writer();
-        if (!is_longdouble and float_bits == 80) {
-            try writer.writeAll("__");
+        try writer.writeAll("__");
+        if (is_longdouble or float_bits != 80) {
+            try writer.writeAll("builtin_");
         }
         try writer.writeAll(fn_name);
         if (is_longdouble) {
@@ -410,10 +411,17 @@ pub const DeclGen = struct {
         const decl = dg.module.declPtr(decl_index);
         assert(decl.has_tv);
 
+        // Render an undefined pointer if we have a pointer to a zero-bit or comptime type.
         if (ty.isPtrAtRuntime() and !decl.ty.isFnOrHasRuntimeBits()) {
-            return dg.writeCValue(writer, CValue{ .undefined_ptr = ty });
+            return dg.writeCValue(writer, CValue{ .undef = ty });
         }
 
+        // Chase function values in order to be able to reference the original function.
+        inline for (.{ .function, .extern_fn }) |tag|
+            if (decl.val.castTag(tag)) |func|
+                if (func.data.owner_decl != decl_index)
+                    return dg.renderDeclValue(writer, ty, val, func.data.owner_decl);
+
         if (ty.isSlice()) {
             try writer.writeByte('(');
             try dg.renderTypecast(writer, ty);
@@ -588,9 +596,9 @@ pub const DeclGen = struct {
                     try writer.writeByte('(');
                     try dg.renderTypecast(writer, ty);
                     try writer.writeAll("){ .payload = ");
-                    try dg.renderValue(writer, Type.bool, val, location);
-                    try writer.writeAll(", .is_null = ");
                     try dg.renderValue(writer, payload_ty, val, location);
+                    try writer.writeAll(", .is_null = ");
+                    try dg.renderValue(writer, Type.bool, val, location);
                     return writer.writeAll(" }");
                 },
                 .Struct => {
@@ -635,7 +643,7 @@ pub const DeclGen = struct {
                     try writer.writeAll("){ .payload = ");
                     try dg.renderValue(writer, ty.errorUnionPayload(), val, location);
                     return writer.print(", .error = {x} }}", .{
-                        try dg.fmtIntLiteral(ty.errorUnionSet(), Value.undef),
+                        try dg.fmtIntLiteral(ty.errorUnionSet(), val),
                     });
                 },
                 .Array => {
@@ -844,13 +852,13 @@ pub const DeclGen = struct {
                 }
             },
             .ErrorUnion => {
-                const error_type = ty.errorUnionSet();
-                const payload_type = ty.errorUnionPayload();
+                const error_ty = ty.errorUnionSet();
+                const payload_ty = ty.errorUnionPayload();
 
-                if (!payload_type.hasRuntimeBits()) {
+                if (!payload_ty.hasRuntimeBits()) {
                     // We use the error type directly as the type.
                     const err_val = if (val.errorUnionIsPayload()) Value.initTag(.zero) else val;
-                    return dg.renderValue(writer, error_type, err_val, location);
+                    return dg.renderValue(writer, error_ty, err_val, location);
                 }
 
                 try writer.writeByte('(');
@@ -859,11 +867,15 @@ pub const DeclGen = struct {
                 if (val.castTag(.eu_payload)) |pl| {
                     const payload_val = pl.data;
                     try writer.writeAll(" .payload = ");
-                    try dg.renderValue(writer, payload_type, payload_val, location);
-                    try writer.writeAll(", .error = 0 }");
+                    try dg.renderValue(writer, payload_ty, payload_val, location);
+                    try writer.print(", .error = {} }}", .{
+                        try dg.fmtIntLiteral(error_ty, Value.zero),
+                    });
                 } else {
-                    try writer.writeAll(" .error = ");
-                    try dg.renderValue(writer, error_type, val, location);
+                    try writer.writeAll(" .payload = ");
+                    try dg.renderValue(writer, payload_ty, Value.undef, location);
+                    try writer.writeAll(", .error = ");
+                    try dg.renderValue(writer, error_ty, val, location);
                     try writer.writeAll(" }");
                 }
             },
@@ -987,13 +999,16 @@ pub const DeclGen = struct {
         if (!is_global) {
             try w.writeAll("static ");
         }
+        const fn_info = dg.decl.ty.fnInfo();
+        if (fn_info.cc == .Naked) {
+            try w.writeAll("ZIG_NAKED ");
+        }
         if (dg.decl.val.castTag(.function)) |func_payload| {
             const func: *Module.Fn = func_payload.data;
             if (func.is_cold) {
                 try w.writeAll("ZIG_COLD ");
             }
         }
-        const fn_info = dg.decl.ty.fnInfo();
         if (fn_info.return_type.hasRuntimeBits()) {
             try dg.renderType(w, fn_info.return_type);
         } else if (fn_info.return_type.isError()) {
@@ -1007,21 +1022,21 @@ pub const DeclGen = struct {
         try dg.renderDeclName(w, dg.decl_index);
         try w.writeByte('(');
 
-        var params_written: usize = 0;
-        for (fn_info.param_types) |param_type, index| {
+        var index: usize = 0;
+        for (fn_info.param_types) |param_type| {
             if (!param_type.hasRuntimeBitsIgnoreComptime()) continue;
-            if (params_written > 0) {
+            if (index > 0) {
                 try w.writeAll(", ");
             }
             const name = CValue{ .arg = index };
             try dg.renderTypeAndName(w, param_type, name, .Mut, 0);
-            params_written += 1;
+            index += 1;
         }
 
         if (fn_info.is_var_args) {
-            if (params_written != 0) try w.writeAll(", ");
+            if (index > 0) try w.writeAll(", ");
             try w.writeAll("...");
-        } else if (params_written == 0) {
+        } else if (index == 0) {
             try w.writeAll("void");
         }
         try w.writeByte(')');
@@ -1307,7 +1322,7 @@ pub const DeclGen = struct {
 
     fn renderErrorUnionTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
         const payload_ty = t.errorUnionPayload();
-        const error_ty = t.errorUnionSet();
+        assert(t.errorUnionSet().tag() == .anyerror);
 
         var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
         defer buffer.deinit();
@@ -1328,15 +1343,7 @@ pub const DeclGen = struct {
         }
 
         const name_begin = buffer.items.len;
-        if (error_ty.castTag(.error_set_inferred)) |inf_err_set_payload| {
-            const func = inf_err_set_payload.data.func;
-            try bw.writeAll("zig_E_");
-            try dg.renderDeclName(bw, func.owner_decl);
-        } else {
-            try bw.print("zig_E_{}_{}", .{
-                typeToCIdentifier(error_ty, dg.module), typeToCIdentifier(payload_ty, dg.module),
-            });
-        }
+        try bw.print("zig_E_{}", .{typeToCIdentifier(payload_ty, dg.module)});
         const name_end = buffer.items.len;
         try bw.writeAll(";\n");
 
@@ -1505,11 +1512,11 @@ pub const DeclGen = struct {
             },
             .Pointer => {
                 if (t.isSlice()) {
-                    var slice_ty_pl = Type.Payload.ElemType{
+                    var slice_pl = Type.Payload.ElemType{
                         .base = .{ .tag = if (t.ptrIsMutable()) .mut_slice else .const_slice },
                         .data = t.childType(),
                     };
-                    const slice_ty = Type.initPayload(&slice_ty_pl.base);
+                    const slice_ty = Type.initPayload(&slice_pl.base);
 
                     const name = dg.getTypedefName(slice_ty) orelse
                         try dg.renderSliceTypedef(slice_ty);
@@ -1525,7 +1532,9 @@ pub const DeclGen = struct {
                 }
 
                 const child_ty = t.childType();
-                if (t.isCPtr() and child_ty.eql(Type.u8, dg.module) and dg.decl.val.tag() == .extern_fn) {
+                if (t.isCPtr() and child_ty.eql(Type.u8, dg.module) and
+                    (dg.decl.val.tag() == .extern_fn or std.mem.eql(u8, std.mem.span(dg.decl.name), "main")))
+                {
                     // This is a hack, since the c compiler expects a lot of external
                     // library functions to have char pointers in their signatures, but
                     // u8 and i8 produce unsigned char and signed char respectively,
@@ -1543,11 +1552,11 @@ pub const DeclGen = struct {
                 return w.writeAll(" *");
             },
             .Array => {
-                var array_ty_pl = Type.Payload.Array{ .base = .{ .tag = .array }, .data = .{
+                var array_pl = Type.Payload.Array{ .base = .{ .tag = .array }, .data = .{
                     .len = t.arrayLenIncludingSentinel(),
                     .elem_type = t.childType(),
                 } };
-                const array_ty = Type.initPayload(&array_ty_pl.base);
+                const array_ty = Type.initPayload(&array_pl.base);
 
                 const name = dg.getTypedefName(array_ty) orelse
                     try dg.renderArrayTypedef(array_ty);
@@ -1582,8 +1591,13 @@ pub const DeclGen = struct {
                     return dg.renderType(w, Type.anyerror);
                 }
 
-                const name = dg.getTypedefName(t) orelse
-                    try dg.renderErrorUnionTypedef(t);
+                var error_union_pl = Type.Payload.ErrorUnion{
+                    .data = .{ .error_set = Type.anyerror, .payload = payload_ty },
+                };
+                const error_union_ty = Type.initPayload(&error_union_pl.base);
+
+                const name = dg.getTypedefName(error_union_ty) orelse
+                    try dg.renderErrorUnionTypedef(error_union_ty);
 
                 return w.writeAll(name);
             },
@@ -1603,8 +1617,8 @@ pub const DeclGen = struct {
             },
             .Enum => {
                 // For enums, we simply use the integer tag type.
-                var int_tag_ty_buffer: Type.Payload.Bits = undefined;
-                const int_tag_ty = t.intTagType(&int_tag_ty_buffer);
+                var int_tag_buf: Type.Payload.Bits = undefined;
+                const int_tag_ty = t.intTagType(&int_tag_buf);
 
                 try dg.renderType(w, int_tag_ty);
             },
@@ -1801,11 +1815,7 @@ pub const DeclGen = struct {
                 try w.writeByte('&');
                 return dg.renderDeclName(w, decl);
             },
-            .undefined_ptr => |ty| {
-                try w.writeAll("((");
-                try dg.renderTypecast(w, ty);
-                return w.print("){x})", .{try dg.fmtIntLiteral(Type.usize, Value.undef)});
-            },
+            .undef => |ty| return dg.renderValue(w, ty, Value.undef, .Other),
             .identifier => |ident| return w.print("{ }", .{fmtIdent(ident)}),
             .bytes => |bytes| return w.writeAll(bytes),
         }
@@ -1824,7 +1834,7 @@ pub const DeclGen = struct {
                 return w.writeByte(')');
             },
             .decl_ref => |decl| return dg.renderDeclName(w, decl),
-            .undefined_ptr => unreachable,
+            .undef => unreachable,
             .identifier => |ident| return w.print("(*{ })", .{fmtIdent(ident)}),
             .bytes => |bytes| {
                 try w.writeAll("(*");
@@ -2151,10 +2161,10 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
 
             .mul_add => try airMulAdd(f, inst),
 
-            .add_with_overflow => try airOverflow(f, inst, "addo_"),
-            .sub_with_overflow => try airOverflow(f, inst, "subo_"),
-            .mul_with_overflow => try airOverflow(f, inst, "mulo_"),
-            .shl_with_overflow => try airOverflow(f, inst, "shlo_"),
+            .add_with_overflow => try airOverflow(f, inst, "addo_", .range),
+            .sub_with_overflow => try airOverflow(f, inst, "subo_", .range),
+            .mul_with_overflow => try airOverflow(f, inst, "mulo_", .range),
+            .shl_with_overflow => try airOverflow(f, inst, "shlo_", .bits),
 
             .min => try airMinMax(f, inst, "<"),
             .max => try airMinMax(f, inst, ">"),
@@ -2284,8 +2294,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
             .slice_ptr        => try airSliceField(f, inst, " = ", ".ptr;\n"),
             .slice_len        => try airSliceField(f, inst, " = ", ".len;\n"),
 
-            .ptr_slice_len_ptr => try airSliceField(f, inst, " = &", ".len;\n"),
-            .ptr_slice_ptr_ptr => try airSliceField(f, inst, " = &", ".ptr;\n"),
+            .ptr_slice_len_ptr => try airSliceField(f, inst, " = &", "->len;\n"),
+            .ptr_slice_ptr_ptr => try airSliceField(f, inst, " = &", "->ptr;\n"),
 
             .ptr_elem_val       => try airPtrElemVal(f, inst),
             .ptr_elem_ptr       => try airPtrElemPtr(f, inst),
@@ -2459,7 +2469,7 @@ fn airAlloc(f: *Function, inst: Air.Inst.Index) !CValue {
     const elem_type = inst_ty.elemType();
     const mutability: Mutability = if (inst_ty.isConstPtr()) .Const else .Mut;
     if (!elem_type.isFnOrHasRuntimeBitsIgnoreComptime()) {
-        return CValue{ .undefined_ptr = inst_ty };
+        return CValue{ .undef = inst_ty };
     }
 
     const target = f.object.dg.module.getTarget();
@@ -2474,9 +2484,13 @@ fn airRetPtr(f: *Function, inst: Air.Inst.Index) !CValue {
     const writer = f.object.writer();
     const inst_ty = f.air.typeOfIndex(inst);
 
+    const elem_ty = inst_ty.elemType();
+    if (!elem_ty.hasRuntimeBitsIgnoreComptime()) {
+        return CValue{ .undef = inst_ty };
+    }
+
     // First line: the variable used as data storage.
-    const elem_type = inst_ty.elemType();
-    const local = try f.allocLocal(elem_type, .Mut);
+    const local = try f.allocLocal(elem_ty, .Mut);
     try writer.writeAll(";\n");
 
     return CValue{ .local_ref = local.local };
@@ -2507,8 +2521,7 @@ fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue {
     if (is_array) {
         // Insert a memcpy to initialize this array. The source operand is always a pointer
         // and thus we only need to know size/type information from the local type/dest.
-        try writer.writeByte(';');
-        try f.object.indent_writer.insertNewline();
+        try writer.writeAll(";\n");
         try writer.writeAll("memcpy(");
         try f.writeCValue(writer, local);
         try writer.writeAll(", ");
@@ -2535,7 +2548,8 @@ fn airRet(f: *Function, inst: Air.Inst.Index) !CValue {
         try writer.writeAll(";\n");
     } else if (ret_ty.isError()) {
         try writer.writeAll("return 0;");
-    } else {
+    } else if (f.object.dg.decl.ty.fnCallingConvention() != .Naked) {
+        // Not even allowed to return void in a naked function.
         try writer.writeAll("return;\n");
     }
     return CValue.none;
@@ -2847,7 +2861,7 @@ fn airSatOp(f: *Function, inst: Air.Inst.Index, fn_op: [*:0]const u8) !CValue {
     return ret;
 }
 
-fn airOverflow(f: *Function, inst: Air.Inst.Index, op_abbrev: [*:0]const u8) !CValue {
+fn airOverflow(f: *Function, inst: Air.Inst.Index, op_abbrev: [*:0]const u8, kind: enum { range, bits }) !CValue {
     if (f.liveness.isUnused(inst))
         return CValue.none;
 
@@ -2879,23 +2893,29 @@ fn airOverflow(f: *Function, inst: Air.Inst.Index, op_abbrev: [*:0]const u8) !CV
     try w.writeAll(", &");
     try f.writeCValue(w, ret);
     try w.writeAll(".field_0, ");
-    {
-        var arena = std.heap.ArenaAllocator.init(f.object.dg.module.gpa);
-        defer arena.deinit();
-
-        const expected_contents = union { u: Value.Payload.U64, i: Value.Payload.I64 };
-        var stack align(@alignOf(expected_contents)) =
-            std.heap.stackFallback(@sizeOf(expected_contents), arena.allocator());
-
-        if (int_info.signedness == .signed) {
-            const min_val = try scalar_ty.minInt(stack.get(), target);
-            try w.print("{}, ", .{try f.fmtIntLiteral(scalar_ty, min_val)});
-        }
+    switch (kind) {
+        .range => {
+            var arena = std.heap.ArenaAllocator.init(f.object.dg.module.gpa);
+            defer arena.deinit();
+
+            const expected_contents = union { u: Value.Payload.U64, i: Value.Payload.I64 };
+            var stack align(@alignOf(expected_contents)) =
+                std.heap.stackFallback(@sizeOf(expected_contents), arena.allocator());
+
+            if (int_info.signedness == .signed) {
+                const min_val = try scalar_ty.minInt(stack.get(), target);
+                try w.print("{}, ", .{try f.fmtIntLiteral(scalar_ty, min_val)});
+            }
 
-        const max_val = try scalar_ty.maxInt(stack.get(), target);
-        try w.print("{});", .{try f.fmtIntLiteral(scalar_ty, max_val)});
+            const max_val = try scalar_ty.maxInt(stack.get(), target);
+            try w.print("{});\n", .{try f.fmtIntLiteral(scalar_ty, max_val)});
+        },
+        .bits => {
+            var bits_pl = Value.Payload.U64{ .base = .{ .tag = .int_u64 }, .data = int_info.bits };
+            const bits_val = Value.initPayload(&bits_pl.base);
+            try w.print("{x});\n", .{try f.fmtIntLiteral(Type.u8, bits_val)});
+        },
     }
-    try f.object.indent_writer.insertNewline();
     return ret;
 }
 
@@ -3075,6 +3095,9 @@ fn airCall(
     inst: Air.Inst.Index,
     modifier: std.builtin.CallOptions.Modifier,
 ) !CValue {
+    // Not even allowed to call panic in a naked function.
+    if (f.object.dg.decl.ty.fnCallingConvention() == .Naked) return .none;
+
     switch (modifier) {
         .auto => {},
         .always_tail => return f.fail("TODO: C backend: call with always_tail attribute", .{}),
@@ -3094,10 +3117,10 @@ fn airCall(
     const writer = f.object.writer();
 
     const result_local: CValue = r: {
-        if (f.liveness.isUnused(inst)) {
-            if (loweredFnRetTyHasBits(fn_ty)) {
-                try writer.print("(void)", .{});
-            }
+        if (!loweredFnRetTyHasBits(fn_ty)) {
+            break :r .none;
+        } else if (f.liveness.isUnused(inst)) {
+            try writer.print("(void)", .{});
             break :r .none;
         } else {
             const local = try f.allocLocal(fn_ty.fnReturnType(), .Const);
@@ -3107,6 +3130,7 @@ fn airCall(
     };
 
     var is_extern = false;
+    var name: [*:0]const u8 = "";
     callee: {
         known: {
             const fn_decl = fn_decl: {
@@ -3121,6 +3145,7 @@ fn airCall(
                     else => break :known,
                 };
             };
+            name = f.object.dg.module.declPtr(fn_decl).name;
             try f.object.dg.renderDeclName(writer, fn_decl);
             break :callee;
         }
@@ -3137,7 +3162,9 @@ fn airCall(
         if (args_written != 0) {
             try writer.writeAll(", ");
         }
-        if (is_extern and ty.isCPtr() and ty.childType().tag() == .u8) {
+        if ((is_extern or std.mem.eql(u8, std.mem.span(name), "main")) and
+            ty.isCPtr() and ty.childType().tag() == .u8)
+        {
             // Corresponds with hack in renderType .Pointer case.
             try writer.writeAll("(char");
             if (ty.isConstPtr()) try writer.writeAll(" const");
@@ -3286,15 +3313,27 @@ fn lowerTry(
         }
     }
 
-    const local = try f.allocLocal(result_ty, .Const);
-    if (operand_is_ptr or isByRef(payload_ty)) {
-        try writer.writeAll(" = &");
+    const is_array = payload_ty.zigTypeTag() == .Array;
+    const local = try f.allocLocal(result_ty, if (is_array) .Mut else .Const);
+    if (is_array) {
+        try writer.writeAll(";\n");
+        try writer.writeAll("memcpy(");
+        try f.writeCValue(writer, local);
+        try writer.writeAll(", ");
         try f.writeCValue(writer, err_union);
-        try writer.writeAll("->payload;\n");
+        try writer.writeAll(".payload, sizeof(");
+        try f.writeCValue(writer, local);
+        try writer.writeAll("));\n");
     } else {
-        try writer.writeAll(" = ");
-        try f.writeCValue(writer, err_union);
-        try writer.writeAll(".payload;\n");
+        if (operand_is_ptr or isByRef(payload_ty)) {
+            try writer.writeAll(" = &");
+            try f.writeCValue(writer, err_union);
+            try writer.writeAll("->payload;\n");
+        } else {
+            try writer.writeAll(" = ");
+            try f.writeCValue(writer, err_union);
+            try writer.writeAll(".payload;\n");
+        }
     }
     return local;
 }
@@ -3361,15 +3400,21 @@ fn airBreakpoint(f: *Function) !CValue {
 
 fn airRetAddr(f: *Function, inst: Air.Inst.Index) !CValue {
     if (f.liveness.isUnused(inst)) return CValue.none;
+    const writer = f.object.writer();
     const local = try f.allocLocal(Type.usize, .Const);
-    try f.object.writer().writeAll(" = zig_return_address();\n");
+    try writer.writeAll(" = (");
+    try f.renderTypecast(writer, Type.usize);
+    try writer.writeAll(")zig_return_address();\n");
     return local;
 }
 
 fn airFrameAddress(f: *Function, inst: Air.Inst.Index) !CValue {
     if (f.liveness.isUnused(inst)) return CValue.none;
+    const writer = f.object.writer();
     const local = try f.allocLocal(Type.usize, .Const);
-    try f.object.writer().writeAll(" = zig_frame_address();\n");
+    try writer.writeAll(" = (");
+    try f.renderTypecast(writer, Type.usize);
+    try writer.writeAll(")zig_frame_address();\n");
     return local;
 }
 
@@ -3385,6 +3430,9 @@ fn airFence(f: *Function, inst: Air.Inst.Index) !CValue {
 }
 
 fn airUnreach(f: *Function) !CValue {
+    // Not even allowed to call unreachable in a naked function.
+    if (f.object.dg.decl.ty.fnCallingConvention() == .Naked) return .none;
+
     try f.object.writer().writeAll("zig_unreachable();\n");
     return CValue.none;
 }
@@ -3479,15 +3527,12 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue {
         const local = try f.allocLocal(inst_ty, .Mut);
         if (f.wantSafety()) {
             try writer.writeAll(" = ");
-            try f.object.dg.renderValue(writer, inst_ty, Value.undef, .Other);
+            try f.writeCValue(writer, .{ .undef = inst_ty });
         }
         try writer.writeAll(";\n");
         break :local local;
     } else .none;
 
-    try writer.writeAll("{\n");
-    f.object.indent_writer.pushIndent();
-
     const output_locals_begin = f.next_local_index;
     f.next_local_index += outputs.len;
     const constraints_extra_begin = extra_i;
@@ -3510,7 +3555,7 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue {
             try writer.writeAll("\")");
             if (f.wantSafety()) {
                 try writer.writeAll(" = ");
-                try f.object.dg.renderValue(writer, output_ty, Value.undef, .Other);
+                try f.writeCValue(writer, .{ .undef = output_ty });
             }
             try writer.writeAll(";\n");
         } else if (constraint.len < 2 or constraint[0] != '=') {
@@ -3605,7 +3650,7 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue {
         } else {
             const input_val = try f.resolveInst(input);
             try writer.print("{s}(", .{fmtStringLiteral(constraint)});
-            try f.writeCValue(writer, if (input_val == .constant) .{
+            try f.writeCValue(writer, if (input_val == .constant) CValue{
                 .local = input_locals_begin + index,
             } else input_val);
         }
@@ -3647,9 +3692,6 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue {
         }
     }
 
-    f.object.indent_writer.popIndent();
-    try writer.writeAll("}\n");
-
     return local;
 }
 
@@ -3728,7 +3770,7 @@ fn airOptionalPayloadPtr(f: *Function, inst: Air.Inst.Index) !CValue {
     const inst_ty = f.air.typeOfIndex(inst);
 
     if (!inst_ty.childType().hasRuntimeBitsIgnoreComptime()) {
-        return CValue{ .undefined_ptr = inst_ty };
+        return CValue{ .undef = inst_ty };
     }
 
     if (opt_ty.optionalReprIsPayload()) {
@@ -3996,7 +4038,9 @@ fn airWrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue {
     }
 
     const local = try f.allocLocal(err_un_ty, .Const);
-    try writer.writeAll(" = { .error = ");
+    try writer.writeAll(" = { .payload = ");
+    try f.writeCValue(writer, .{ .undef = payload_ty });
+    try writer.writeAll(", .error = ");
     try f.writeCValue(writer, operand);
     try writer.writeAll(" };\n");
     return local;
@@ -4059,10 +4103,25 @@ fn airWrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue {
     const operand = try f.resolveInst(ty_op.operand);
 
     const inst_ty = f.air.typeOfIndex(inst);
-    const local = try f.allocLocal(inst_ty, .Const);
-    try writer.writeAll(" = { .error = 0, .payload = ");
-    try f.writeCValue(writer, operand);
-    try writer.writeAll(" };\n");
+    const payload_ty = inst_ty.errorUnionPayload();
+    const is_array = payload_ty.zigTypeTag() == .Array;
+    const local = try f.allocLocal(inst_ty, if (is_array) .Mut else .Const);
+    try writer.writeAll(" = { .payload = ");
+    try f.writeCValue(writer, if (is_array) CValue{ .undef = payload_ty } else operand);
+    try writer.print(", .error = {} }};\n", .{
+        try f.fmtIntLiteral(inst_ty.errorUnionSet(), Value.zero),
+    });
+
+    if (is_array) {
+        try writer.writeAll("memcpy(");
+        try f.writeCValue(writer, local);
+        try writer.writeAll(".payload, ");
+        try f.writeCValue(writer, operand);
+        try writer.writeAll(", sizeof(");
+        try f.writeCValue(writer, local);
+        try writer.writeAll(".payload));\n");
+    }
+
     return local;
 }
 
@@ -4114,11 +4173,11 @@ fn airArrayToSlice(f: *Function, inst: Air.Inst.Index) !CValue {
     const array_len = f.air.typeOf(ty_op.operand).elemType().arrayLen();
 
     try writer.writeAll(" = { .ptr = ");
-    if (operand == .undefined_ptr) {
+    if (operand == .undef) {
         // Unfortunately, C does not support any equivalent to
         // &(*(void *)p)[0], although LLVM does via GetElementPtr
         var buf: Type.SlicePtrFieldTypeBuffer = undefined;
-        try f.writeCValue(writer, CValue{ .undefined_ptr = inst_ty.slicePtrFieldType(&buf) });
+        try f.writeCValue(writer, CValue{ .undef = inst_ty.slicePtrFieldType(&buf) });
     } else {
         try writer.writeAll("&(");
         try f.writeCValueDeref(writer, operand);
@@ -4528,9 +4587,12 @@ fn airAggregateInit(f: *Function, inst: Air.Inst.Index) !CValue {
     const ty_pl = f.air.instructions.items(.data)[inst].ty_pl;
     const len = @intCast(usize, inst_ty.arrayLen());
     const elements = @ptrCast([]const Air.Inst.Ref, f.air.extra[ty_pl.payload..][0..len]);
+    const mutability: Mutability = for (elements) |element| {
+        if (f.air.typeOf(element).zigTypeTag() == .Array) break .Mut;
+    } else .Const;
 
     const writer = f.object.writer();
-    const local = try f.allocLocal(inst_ty, .Const);
+    const local = try f.allocLocal(inst_ty, mutability);
     try writer.writeAll(" = {");
     switch (inst_ty.zigTypeTag()) {
         .Array => {
@@ -4547,6 +4609,7 @@ fn airAggregateInit(f: *Function, inst: Air.Inst.Index) !CValue {
                 empty = false;
             }
             if (empty) try writer.print("{}", .{try f.fmtIntLiteral(Type.u8, Value.zero)});
+            try writer.writeAll("};\n");
         },
         .Struct => {
             var empty = true;
@@ -4557,15 +4620,44 @@ fn airAggregateInit(f: *Function, inst: Air.Inst.Index) !CValue {
                 if (!inst_ty.isTupleOrAnonStruct()) {
                     try writer.print(".{ } = ", .{fmtIdent(inst_ty.structFieldName(index))});
                 }
-                try f.writeCValue(writer, try f.resolveInst(element));
+
+                const element_ty = f.air.typeOf(element);
+                try f.writeCValue(writer, switch (element_ty.zigTypeTag()) {
+                    .Array => CValue{ .undef = element_ty },
+                    else => try f.resolveInst(element),
+                });
                 empty = false;
             }
             if (empty) try writer.print("{}", .{try f.fmtIntLiteral(Type.u8, Value.zero)});
+            try writer.writeAll("};\n");
+
+            for (elements) |element, index| {
+                if (inst_ty.structFieldValueComptime(index)) |_| continue;
+
+                const element_ty = f.air.typeOf(element);
+                if (element_ty.zigTypeTag() != .Array) continue;
+
+                var field_name_storage: ?[]u8 = null;
+                defer if (field_name_storage) |storage| f.object.dg.gpa.free(storage);
+                const field_name = if (inst_ty.isTupleOrAnonStruct()) field_name: {
+                    const name = try std.fmt.allocPrint(f.object.dg.gpa, "field_{d}", .{index});
+                    field_name_storage = name;
+                    break :field_name name;
+                } else inst_ty.structFieldName(index);
+
+                try writer.writeAll(";\n");
+                try writer.writeAll("memcpy(");
+                try f.writeCValue(writer, local);
+                try writer.print(".{ }, ", .{fmtIdent(field_name)});
+                try f.writeCValue(writer, try f.resolveInst(element));
+                try writer.writeAll(", sizeof(");
+                try f.writeCValue(writer, local);
+                try writer.print(".{ }));\n", .{fmtIdent(field_name)});
+            }
         },
         .Vector => return f.fail("TODO: C backend: implement airAggregateInit for vectors", .{}),
         else => unreachable,
     }
-    try writer.writeAll("};\n");
 
     return local;
 }
@@ -4732,8 +4824,8 @@ fn airMulAdd(f: *Function, inst: Air.Inst.Index) !CValue {
 
 fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 {
     return switch (order) {
-        .Unordered => "memory_order_relaxed",
-        .Monotonic => "memory_order_consume",
+        // Note: unordered is actually even less atomic than relaxed
+        .Unordered, .Monotonic => "memory_order_relaxed",
         .Acquire => "memory_order_acquire",
         .Release => "memory_order_release",
         .AcqRel => "memory_order_acq_rel",
src/main.zig
@@ -3018,7 +3018,10 @@ fn buildOutputType(
         const c_code_path = try fs.path.join(arena, &[_][]const u8{
             c_code_directory.path orelse ".", c_code_loc.basename,
         });
-        try test_exec_args.appendSlice(&.{ self_exe_path, "run", "-lc", c_code_path });
+        try test_exec_args.append(self_exe_path);
+        try test_exec_args.append("run");
+        if (link_libc) try test_exec_args.append("-lc");
+        try test_exec_args.append(c_code_path);
     }
 
     const run_or_test = switch (arg_mode) {
test/behavior/bugs/3742.zig
@@ -38,6 +38,5 @@ test "fixed" {
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
     ArgSerializer.serializeCommand(GET.init("banana"));
 }
test/behavior/fn.zig
@@ -272,7 +272,6 @@ test "void parameters" {
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
 
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     try voidFun(1, void{}, 2, {});
 }
 fn voidFun(a: i32, b: void, c: i32, d: void) !void {
test/behavior/struct.zig
@@ -1163,7 +1163,6 @@ test "for loop over pointers to struct, getting field from struct pointer" {
 
 test "anon init through error unions and optionals" {
     if (builtin.zig_backend == .stage1) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
@@ -1234,7 +1233,6 @@ test "anon init through error union" {
 
 test "typed init through error unions and optionals" {
     if (builtin.zig_backend == .stage1) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
test/behavior/translate_c_macros.zig
@@ -100,7 +100,6 @@ test "nested comma operator" {
 test "cast functions" {
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO