Commit 47336abae3

Andrew Kelley <superjoe30@gmail.com>
2017-04-17 08:58:42
improvements to zig build system and unwrap error safety
* zig build system: create standard dynamic library sym links * unwrapping an error results in a panic message that contains the error name * rename error.SysResources to error.SystemResources * add std.os.symLink * add std.os.deleteFile
1 parent d16ce67
src/all_types.hpp
@@ -1211,7 +1211,6 @@ enum PanicMsgId {
     PanicMsgIdExactDivisionRemainder,
     PanicMsgIdSliceWidenRemainder,
     PanicMsgIdUnwrapMaybeFail,
-    PanicMsgIdUnwrapErrFail,
     PanicMsgIdInvalidErrorCode,
 
     PanicMsgIdCount,
@@ -1445,6 +1444,8 @@ struct CodeGen {
     ZigList<AstNode *> error_decls;
     bool generate_error_name_table;
     LLVMValueRef err_name_table;
+    size_t largest_err_name_len;
+    LLVMValueRef safety_crash_err_fn;
 
     IrInstruction *invalid_instruction;
     ConstExprValue const_void_val;
src/codegen.cpp
@@ -255,6 +255,7 @@ void codegen_set_linker_script(CodeGen *g, const char *linker_script) {
 static void render_const_val(CodeGen *g, ConstExprValue *const_val);
 static void render_const_val_global(CodeGen *g, ConstExprValue *const_val, const char *name);
 static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val);
+static void generate_error_name_table(CodeGen *g);
 
 static void addLLVMAttr(LLVMValueRef val, LLVMAttributeIndex attr_index, const char *attr_name) {
     unsigned kind_id = LLVMGetEnumAttributeKindForName(attr_name, strlen(attr_name));
@@ -545,6 +546,34 @@ static bool ir_want_debug_safety(CodeGen *g, IrInstruction *instruction) {
     return true;
 }
 
+static bool is_array_of_at_least_n_bytes(CodeGen *g, TypeTableEntry *type_entry, uint32_t n) {
+    if (type_entry->id != TypeTableEntryIdArray)
+        return false;
+
+    TypeTableEntry *child_type = type_entry->data.array.child_type;
+    if (child_type->id != TypeTableEntryIdInt)
+        return false;
+
+    if (child_type != g->builtin_types.entry_u8)
+        return false;
+
+    if (type_entry->data.array.len < n)
+        return false;
+
+    return true;
+}
+
+static uint32_t get_type_alignment(CodeGen *g, TypeTableEntry *type_entry) {
+    uint32_t alignment = ZigLLVMGetPrefTypeAlignment(g->target_data_ref, type_entry->type_ref);
+    uint32_t dbl_ptr_bytes = g->pointer_size_bytes * 2;
+    if (is_array_of_at_least_n_bytes(g, type_entry, dbl_ptr_bytes)) {
+        return (alignment < dbl_ptr_bytes) ? dbl_ptr_bytes : alignment;
+    } else {
+        return alignment;
+    }
+}
+
+
 static Buf *panic_msg_buf(PanicMsgId msg_id) {
     switch (msg_id) {
         case PanicMsgIdCount:
@@ -569,8 +598,6 @@ static Buf *panic_msg_buf(PanicMsgId msg_id) {
             return buf_create_from_str("slice widening size mismatch");
         case PanicMsgIdUnwrapMaybeFail:
             return buf_create_from_str("attempt to unwrap null");
-        case PanicMsgIdUnwrapErrFail:
-            return buf_create_from_str("attempt to unwrap error");
         case PanicMsgIdUnreachable:
             return buf_create_from_str("reached unreachable code");
         case PanicMsgIdInvalidErrorCode:
@@ -595,28 +622,128 @@ static LLVMValueRef get_panic_msg_ptr_val(CodeGen *g, PanicMsgId msg_id) {
     return val->llvm_global;
 }
 
-static void gen_panic(CodeGen *g, LLVMValueRef msg_arg) {
+static void gen_panic_raw(CodeGen *g, LLVMValueRef msg_ptr, LLVMValueRef msg_len) {
     FnTableEntry *panic_fn = get_extern_panic_fn(g);
     LLVMValueRef fn_val = fn_llvm_value(g, panic_fn);
+    LLVMValueRef args[] = { msg_ptr, msg_len };
+    ZigLLVMBuildCall(g->builder, fn_val, args, 2, panic_fn->type_entry->data.fn.calling_convention, false, "");
+    LLVMBuildUnreachable(g->builder);
+}
 
+static void gen_panic(CodeGen *g, LLVMValueRef msg_arg) {
     TypeTableEntry *str_type = get_slice_type(g, g->builtin_types.entry_u8, true);
     size_t ptr_index = str_type->data.structure.fields[slice_ptr_index].gen_index;
     size_t len_index = str_type->data.structure.fields[slice_len_index].gen_index;
     LLVMValueRef ptr_ptr = LLVMBuildStructGEP(g->builder, msg_arg, (unsigned)ptr_index, "");
     LLVMValueRef len_ptr = LLVMBuildStructGEP(g->builder, msg_arg, (unsigned)len_index, "");
 
-    LLVMValueRef args[] = {
-        LLVMBuildLoad(g->builder, ptr_ptr, ""),
-        LLVMBuildLoad(g->builder, len_ptr, ""),
-    };
-    ZigLLVMBuildCall(g->builder, fn_val, args, 2, panic_fn->type_entry->data.fn.calling_convention, false, "");
-    LLVMBuildUnreachable(g->builder);
+    LLVMValueRef msg_ptr = LLVMBuildLoad(g->builder, ptr_ptr, "");
+    LLVMValueRef msg_len = LLVMBuildLoad(g->builder, len_ptr, "");
+    gen_panic_raw(g, msg_ptr, msg_len);
 }
 
 static void gen_debug_safety_crash(CodeGen *g, PanicMsgId msg_id) {
     gen_panic(g, get_panic_msg_ptr_val(g, msg_id));
 }
 
+static LLVMValueRef get_safety_crash_err_fn(CodeGen *g) {
+    if (g->safety_crash_err_fn != nullptr)
+        return g->safety_crash_err_fn;
+
+    static const char *unwrap_err_msg_text = "attempt to unwrap error: ";
+
+    g->generate_error_name_table = true;
+    generate_error_name_table(g);
+
+    size_t unwrap_err_msg_text_len = strlen(unwrap_err_msg_text);
+    size_t err_buf_len = strlen(unwrap_err_msg_text) + g->largest_err_name_len;
+    LLVMValueRef *err_buf_vals = allocate<LLVMValueRef>(err_buf_len);
+    size_t i = 0;
+    for (; i < unwrap_err_msg_text_len; i += 1) {
+        err_buf_vals[i] = LLVMConstInt(LLVMInt8Type(), unwrap_err_msg_text[i], false);
+    }
+    for (; i < err_buf_len; i += 1) {
+        err_buf_vals[i] = LLVMGetUndef(LLVMInt8Type());
+    }
+    LLVMValueRef init_value = LLVMConstArray(LLVMInt8Type(), err_buf_vals, err_buf_len);
+    Buf *global_name = get_mangled_name(g, buf_create_from_str("__zig_panic_buf"), false);
+    LLVMValueRef global_value = LLVMAddGlobal(g->module, LLVMTypeOf(init_value), buf_ptr(global_name));
+    LLVMSetInitializer(global_value, init_value);
+    LLVMSetLinkage(global_value, LLVMInternalLinkage);
+    LLVMSetGlobalConstant(global_value, false);
+    LLVMSetUnnamedAddr(global_value, true);
+    LLVMSetAlignment(global_value, get_type_alignment(g, g->builtin_types.entry_u8));
+
+    TypeTableEntry *usize = g->builtin_types.entry_usize;
+    LLVMValueRef full_buf_ptr_indices[] = {
+        LLVMConstNull(usize->type_ref),
+        LLVMConstNull(usize->type_ref),
+    };
+    LLVMValueRef full_buf_ptr = LLVMConstInBoundsGEP(global_value, full_buf_ptr_indices, 2);
+
+    LLVMValueRef offset_ptr_indices[] = {
+        LLVMConstNull(usize->type_ref),
+        LLVMConstInt(usize->type_ref, unwrap_err_msg_text_len, false),
+    };
+    LLVMValueRef offset_buf_ptr = LLVMConstInBoundsGEP(global_value, offset_ptr_indices, 2);
+
+    Buf *fn_name = get_mangled_name(g, buf_create_from_str("__zig_fail_unwrap"), false);
+    LLVMTypeRef fn_type_ref = LLVMFunctionType(LLVMVoidType(), &g->err_tag_type->type_ref, 1, false);
+    LLVMValueRef fn_val = LLVMAddFunction(g->module, buf_ptr(fn_name), fn_type_ref);
+    addLLVMFnAttr(fn_val, "noreturn");
+    addLLVMFnAttr(fn_val, "cold");
+    LLVMSetLinkage(fn_val, LLVMInternalLinkage);
+    LLVMSetFunctionCallConv(fn_val, LLVMFastCallConv);
+
+    LLVMBasicBlockRef entry_block = LLVMAppendBasicBlock(fn_val, "Entry");
+    LLVMBasicBlockRef prev_block = LLVMGetInsertBlock(g->builder);
+    LLVMValueRef prev_debug_location = LLVMGetCurrentDebugLocation(g->builder);
+    LLVMPositionBuilderAtEnd(g->builder, entry_block);
+    ZigLLVMClearCurrentDebugLocation(g->builder);
+
+    LLVMValueRef err_val = LLVMGetParam(fn_val, 0);
+
+    LLVMValueRef err_table_indices[] = {
+        LLVMConstNull(g->builtin_types.entry_usize->type_ref),
+        err_val,
+    };
+    LLVMValueRef err_name_val = LLVMBuildInBoundsGEP(g->builder, g->err_name_table, err_table_indices, 2, "");
+
+    LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder, err_name_val, slice_ptr_index, "");
+    LLVMValueRef err_name_ptr = LLVMBuildLoad(g->builder, ptr_field_ptr, "");
+
+    LLVMValueRef len_field_ptr = LLVMBuildStructGEP(g->builder, err_name_val, slice_len_index, "");
+    LLVMValueRef err_name_len = LLVMBuildLoad(g->builder, len_field_ptr, "");
+
+    LLVMValueRef params[] = {
+        offset_buf_ptr, // dest pointer
+        err_name_ptr, // source pointer
+        err_name_len, // size bytes
+        LLVMConstInt(LLVMInt32Type(), 1, false), // align bytes
+        LLVMConstNull(LLVMInt1Type()), // is volatile
+    };
+
+    LLVMBuildCall(g->builder, g->memcpy_fn_val, params, 5, "");
+
+    LLVMValueRef const_prefix_len = LLVMConstInt(LLVMTypeOf(err_name_len), strlen(unwrap_err_msg_text), false);
+    LLVMValueRef full_buf_len = LLVMBuildNUWAdd(g->builder, const_prefix_len, err_name_len, "");
+
+    gen_panic_raw(g, full_buf_ptr, full_buf_len);
+
+    LLVMPositionBuilderAtEnd(g->builder, prev_block);
+    LLVMSetCurrentDebugLocation(g->builder, prev_debug_location);
+
+    g->safety_crash_err_fn = fn_val;
+    return fn_val;
+}
+
+static void gen_debug_safety_crash_for_err(CodeGen *g, LLVMValueRef err_val) {
+    LLVMValueRef safety_crash_err_fn = get_safety_crash_err_fn(g);
+    LLVMBuildCall(g->builder, safety_crash_err_fn, &err_val, 1, "");
+    LLVMBuildUnreachable(g->builder);
+
+}
+
 static void add_bounds_check(CodeGen *g, LLVMValueRef target_val,
         LLVMIntPredicate lower_pred, LLVMValueRef lower_value,
         LLVMIntPredicate upper_pred, LLVMValueRef upper_value)
@@ -790,33 +917,6 @@ static LLVMRealPredicate cmp_op_to_real_predicate(IrBinOp cmp_op) {
     }
 }
 
-static bool is_array_of_at_least_n_bytes(CodeGen *g, TypeTableEntry *type_entry, uint32_t n) {
-    if (type_entry->id != TypeTableEntryIdArray)
-        return false;
-
-    TypeTableEntry *child_type = type_entry->data.array.child_type;
-    if (child_type->id != TypeTableEntryIdInt)
-        return false;
-
-    if (child_type != g->builtin_types.entry_u8)
-        return false;
-
-    if (type_entry->data.array.len < n)
-        return false;
-
-    return true;
-}
-
-static uint32_t get_type_alignment(CodeGen *g, TypeTableEntry *type_entry) {
-    uint32_t alignment = ZigLLVMGetPrefTypeAlignment(g->target_data_ref, type_entry->type_ref);
-    uint32_t dbl_ptr_bytes = g->pointer_size_bytes * 2;
-    if (is_array_of_at_least_n_bytes(g, type_entry, dbl_ptr_bytes)) {
-        return (alignment < dbl_ptr_bytes) ? dbl_ptr_bytes : alignment;
-    } else {
-        return alignment;
-    }
-}
-
 static LLVMValueRef gen_struct_memcpy(CodeGen *g, LLVMValueRef src, LLVMValueRef dest,
         TypeTableEntry *type_entry)
 {
@@ -2522,7 +2622,7 @@ static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutable *execu
         LLVMBuildCondBr(g->builder, cond_val, ok_block, err_block);
 
         LLVMPositionBuilderAtEnd(g->builder, err_block);
-        gen_debug_safety_crash(g, PanicMsgIdUnwrapErrFail);
+        gen_debug_safety_crash_for_err(g, err_val);
 
         LLVMPositionBuilderAtEnd(g->builder, ok_block);
     }
@@ -3384,7 +3484,7 @@ static LLVMValueRef gen_test_fn_val(CodeGen *g, FnTableEntry *fn_entry) {
 }
 
 static void generate_error_name_table(CodeGen *g) {
-    if (!g->generate_error_name_table || g->error_decls.length == 1) {
+    if (g->err_name_table != nullptr || !g->generate_error_name_table || g->error_decls.length == 1) {
         return;
     }
 
@@ -3400,6 +3500,8 @@ static void generate_error_name_table(CodeGen *g) {
         assert(error_decl_node->type == NodeTypeErrorValueDecl);
         Buf *name = error_decl_node->data.error_value_decl.name;
 
+        g->largest_err_name_len = max(g->largest_err_name_len, buf_len(name));
+
         LLVMValueRef str_init = LLVMConstString(buf_ptr(name), (unsigned)buf_len(name), true);
         LLVMValueRef str_global = LLVMAddGlobal(g->module, LLVMTypeOf(str_init), "");
         LLVMSetInitializer(str_global, str_init);
@@ -3417,7 +3519,7 @@ static void generate_error_name_table(CodeGen *g) {
     LLVMValueRef err_name_table_init = LLVMConstArray(str_type->type_ref, values, (unsigned)g->error_decls.length);
 
     g->err_name_table = LLVMAddGlobal(g->module, LLVMTypeOf(err_name_table_init),
-            buf_ptr(get_mangled_name(g, buf_create_from_str("err_name_table"), false)));
+            buf_ptr(get_mangled_name(g, buf_create_from_str("__zig_err_name_table"), false)));
     LLVMSetInitializer(g->err_name_table, err_name_table_init);
     LLVMSetLinkage(g->err_name_table, LLVMPrivateLinkage);
     LLVMSetGlobalConstant(g->err_name_table, true);
src/main.cpp
@@ -248,8 +248,6 @@ int main(int argc, char **argv) {
                 fprintf(stderr, " %s", args.at(i));
             }
             fprintf(stderr, "\n");
-        } else {
-            os_delete_file(buf_create_from_str("./build"));
         }
         return (term.how == TerminationIdClean) ? term.code : -1;
     }
std/os/child_process.zig
@@ -142,7 +142,7 @@ pub const ChildProcess = struct {
         const pid_err = posix.getErrno(pid);
         if (pid_err > 0) {
             return switch (pid_err) {
-                errno.EAGAIN, errno.ENOMEM, errno.ENOSYS => error.SysResources,
+                errno.EAGAIN, errno.ENOMEM, errno.ENOSYS => error.SystemResources,
                 else => error.Unexpected,
             };
         }
@@ -210,7 +210,7 @@ fn makePipe() -> %[2]i32 {
     const err = posix.getErrno(posix.pipe(&fds));
     if (err > 0) {
         return switch (err) {
-            errno.EMFILE, errno.ENFILE => error.SysResources,
+            errno.EMFILE, errno.ENFILE => error.SystemResources,
             else => error.Unexpected,
         }
     }
@@ -242,7 +242,7 @@ fn writeIntFd(fd: i32, value: ErrInt) -> %void {
             switch (err) {
                 errno.EINTR => continue,
                 errno.EINVAL => unreachable,
-                else => return error.SysResources,
+                else => return error.SystemResources,
             }
         }
         index += amt_written;
@@ -260,7 +260,7 @@ fn readIntFd(fd: i32) -> %ErrInt {
             switch (err) {
                 errno.EINTR => continue,
                 errno.EINVAL => unreachable,
-                else => return error.SysResources,
+                else => return error.SystemResources,
             }
         }
         index += amt_written;
std/os/index.zig
@@ -25,13 +25,16 @@ const BufMap = @import("../buf_map.zig").BufMap;
 const cstr = @import("../cstr.zig");
 
 error Unexpected;
-error SysResources;
+error SystemResources;
 error AccessDenied;
 error InvalidExe;
 error FileSystem;
 error IsDir;
 error FileNotFound;
 error FileBusy;
+error LinkPathAlreadyExists;
+error SymLinkLoop;
+error ReadOnlyFileSystem;
 
 /// Fills `buf` with random bytes. If linking against libc, this calls the
 /// appropriate OS-specific library call. Otherwise it uses the zig standard
@@ -174,7 +177,7 @@ pub fn posixOpen(path: []const u8, flags: usize, perm: usize, allocator: ?&Alloc
                 errno.ENFILE => error.SystemFdQuotaExceeded,
                 errno.ENODEV => error.NoDevice,
                 errno.ENOENT => error.PathNotFound,
-                errno.ENOMEM => error.NoMem,
+                errno.ENOMEM => error.SystemResources,
                 errno.ENOSPC => error.NoSpaceLeft,
                 errno.ENOTDIR => error.NotDir,
                 errno.EPERM => error.BadPerm,
@@ -191,7 +194,7 @@ pub fn posixDup2(old_fd: i32, new_fd: i32) -> %void {
         if (err > 0) {
             return switch (err) {
                 errno.EBUSY, errno.EINTR => continue,
-                errno.EMFILE => error.SysResources,
+                errno.EMFILE => error.SystemResources,
                 errno.EINVAL => unreachable,
                 else => error.Unexpected,
             };
@@ -305,7 +308,7 @@ fn posixExecveErrnoToErr(err: usize) -> error {
     assert(err > 0);
     return switch (err) {
         errno.EFAULT => unreachable,
-        errno.E2BIG, errno.EMFILE, errno.ENAMETOOLONG, errno.ENFILE, errno.ENOMEM => error.SysResources,
+        errno.E2BIG, errno.EMFILE, errno.ENAMETOOLONG, errno.ENFILE, errno.ENOMEM => error.SystemResources,
         errno.EACCES, errno.EPERM => error.AccessDenied,
         errno.EINVAL, errno.ENOEXEC => error.InvalidExe,
         errno.EIO, errno.ELOOP => error.FileSystem,
@@ -381,3 +384,59 @@ pub fn getCwd(allocator: &Allocator) -> %[]u8 {
         return buf;
     }
 }
+
+pub fn symLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) -> %void {
+    const full_buf = %return allocator.alloc(u8, existing_path.len + new_path.len + 2);
+    defer allocator.free(full_buf);
+
+    const existing_buf = full_buf;
+    mem.copy(u8, existing_buf, existing_path);
+    existing_buf[existing_path.len] = 0;
+
+    const new_buf = full_buf[existing_path.len + 1...];
+    mem.copy(u8, new_buf, new_path);
+    new_buf[new_path.len] = 0;
+
+    const err = posix.getErrno(posix.symlink(existing_buf.ptr, new_buf.ptr));
+    if (err > 0) {
+        return switch (err) {
+            errno.EFAULT, errno.EINVAL => unreachable,
+            errno.EACCES, errno.EPERM => error.AccessDenied,
+            errno.EDQUOT => error.DiskQuota,
+            errno.EEXIST => error.LinkPathAlreadyExists,
+            errno.EIO => error.FileSystem,
+            errno.ELOOP => error.SymLinkLoop,
+            errno.ENAMETOOLONG => error.NameTooLong,
+            errno.ENOENT, errno.ENOTDIR => error.FileNotFound,
+            errno.ENOMEM => error.SystemResources,
+            errno.ENOSPC => error.NoSpaceLeft,
+            errno.EROFS => error.ReadOnlyFileSystem,
+            else => error.Unexpected,
+        };
+    }
+}
+
+pub fn deleteFile(allocator: &Allocator, path: []const u8) -> %void {
+    const buf = %return allocator.alloc(u8, path.len + 1);
+    defer allocator.free(buf);
+
+    mem.copy(u8, buf, path);
+    buf[path.len] = 0;
+
+    const err = posix.getErrno(posix.unlink(buf.ptr));
+    if (err > 0) {
+        return switch (err) {
+            errno.EACCES, errno.EPERM => error.AccessDenied,
+            errno.EBUSY => error.FileBusy,
+            errno.EFAULT, errno.EINVAL => unreachable,
+            errno.EIO => error.FileSystem,
+            errno.EISDIR => error.IsDir,
+            errno.ELOOP => error.SymLinkLoop,
+            errno.ENAMETOOLONG => error.NameTooLong,
+            errno.ENOENT, errno.ENOTDIR => error.FileNotFound,
+            errno.ENOMEM => error.SystemResources,
+            errno.EROFS => error.ReadOnlyFileSystem,
+            else => error.Unexpected,
+        };
+    }
+}
std/os/linux.zig
@@ -287,6 +287,10 @@ pub fn read(fd: i32, buf: &u8, count: usize) -> usize {
     arch.syscall3(arch.SYS_read, usize(fd), usize(buf), count)
 }
 
+pub fn symlink(existing: &const u8, new: &const u8) -> usize {
+    arch.syscall2(arch.SYS_symlink, usize(existing), usize(new))
+}
+
 pub fn pread(fd: i32, buf: &u8, count: usize, offset: usize) -> usize {
     arch.syscall4(arch.SYS_pread, usize(fd), usize(buf), count, offset)
 }
@@ -340,6 +344,10 @@ pub fn kill(pid: i32, sig: i32) -> usize {
     arch.syscall2(arch.SYS_kill, usize(pid), usize(sig))
 }
 
+pub fn unlink(path: &const u8) -> usize {
+    arch.syscall1(arch.SYS_unlink, usize(path))
+}
+
 pub fn waitpid(pid: i32, status: &i32, options: i32) -> usize {
     arch.syscall4(arch.SYS_wait4, usize(pid), usize(status), usize(options), 0)
 }
std/build.zig
@@ -395,6 +395,33 @@ pub const Builder = struct {
 
         return self.invalid_user_input;
     }
+
+    fn spawnChild(self: &Builder, exe_path: []const u8, args: []const []const u8) {
+        if (self.verbose) {
+            %%io.stderr.printf("{}", exe_path);
+            for (args) |arg| {
+                %%io.stderr.printf(" {}", arg);
+            }
+            %%io.stderr.printf("\n");
+        }
+
+        var child = os.ChildProcess.spawn(exe_path, args, &self.env_map,
+            StdIo.Ignore, StdIo.Inherit, StdIo.Inherit, self.allocator)
+            %% |err| debug.panic("Unable to spawn {}: {}\n", exe_path, @errorName(err));
+
+        const term = %%child.wait();
+        switch (term) {
+            Term.Clean => |code| {
+                if (code != 0) {
+                    debug.panic("Process {} exited with error code {}\n", exe_path, code);
+                }
+            },
+            else => {
+                debug.panic("Process {} terminated unexpectedly\n", exe_path);
+            },
+        };
+
+    }
 };
 
 const Version = struct {
@@ -568,14 +595,7 @@ const Exe = struct {
             %return zig_args.append(lib_path);
         }
 
-        if (builder.verbose) {
-            printInvocation(builder.zig_exe, zig_args);
-        }
-        // TODO issue #301
-        var child = os.ChildProcess.spawn(builder.zig_exe, zig_args.toSliceConst(), &builder.env_map,
-            StdIo.Ignore, StdIo.Inherit, StdIo.Inherit, builder.allocator)
-            %% |err| debug.panic("Unable to spawn zig compiler: {}\n", @errorName(err));
-        %return waitForCleanExit(&child);
+        builder.spawnChild(builder.zig_exe, zig_args.toSliceConst());
     }
 };
 
@@ -700,14 +720,7 @@ const CLibrary = struct {
                 %%cc_args.append(dir);
             }
 
-            if (builder.verbose) {
-                printInvocation(cc, cc_args);
-            }
-
-            var child = os.ChildProcess.spawn(cc, cc_args.toSliceConst(), &builder.env_map,
-                StdIo.Ignore, StdIo.Inherit, StdIo.Inherit, builder.allocator)
-                %% |err| debug.panic("Unable to spawn compiler: {}\n", @errorName(err));
-            %return waitForCleanExit(&child);
+            builder.spawnChild(cc, cc_args.toSliceConst());
 
             %%self.object_files.append(o_file);
         }
@@ -732,14 +745,18 @@ const CLibrary = struct {
                 %%cc_args.append(object_file);
             }
 
-            if (builder.verbose) {
-                printInvocation(cc, cc_args);
-            }
+            builder.spawnChild(cc, cc_args.toSliceConst());
 
-            var child = os.ChildProcess.spawn(cc, cc_args.toSliceConst(), &builder.env_map,
-                StdIo.Ignore, StdIo.Inherit, StdIo.Inherit, builder.allocator)
-                %% |err| debug.panic("Unable to spawn compiler: {}\n", @errorName(err));
-            %return waitForCleanExit(&child);
+            // sym link for libfoo.so.1 to libfoo.so.1.2.3
+            const major_only = %%fmt.allocPrint(builder.allocator, "lib{}.so.{d}", self.name, self.version.major);
+            defer builder.allocator.free(major_only);
+            _ = os.deleteFile(builder.allocator, major_only);
+            %%os.symLink(builder.allocator, self.out_filename, major_only);
+            // sym link for libfoo.so to libfoo.so.1
+            const name_only = %%fmt.allocPrint(builder.allocator, "lib{}.so", self.name);
+            defer builder.allocator.free(name_only);
+            _ = os.deleteFile(builder.allocator, name_only);
+            %%os.symLink(builder.allocator, major_only, name_only);
         }
     }
 
@@ -848,14 +865,7 @@ const CExecutable = struct {
                 %%cc_args.append(dir);
             }
 
-            if (builder.verbose) {
-                printInvocation(cc, cc_args);
-            }
-
-            var child = os.ChildProcess.spawn(cc, cc_args.toSliceConst(), &builder.env_map,
-                StdIo.Ignore, StdIo.Inherit, StdIo.Inherit, builder.allocator)
-                %% |err| debug.panic("Unable to spawn compiler: {}\n", @errorName(err));
-            %return waitForCleanExit(&child);
+            builder.spawnChild(cc, cc_args.toSliceConst());
 
             %%self.object_files.append(o_file);
         }
@@ -879,14 +889,7 @@ const CExecutable = struct {
             %%cc_args.append(full_path_lib);
         }
 
-        if (builder.verbose) {
-            printInvocation(cc, cc_args);
-        }
-
-        var child = os.ChildProcess.spawn(cc, cc_args.toSliceConst(), &builder.env_map,
-            StdIo.Ignore, StdIo.Inherit, StdIo.Inherit, builder.allocator)
-            %% |err| debug.panic("Unable to spawn compiler: {}\n", @errorName(err));
-        %return waitForCleanExit(&child);
+        builder.spawnChild(cc, cc_args.toSliceConst());
     }
 
     pub fn setTarget(self: &CExecutable, target_arch: Arch, target_os: Os, target_environ: Environ) {