Commit 24a9a42966

Andrew Kelley <superjoe30@gmail.com>
2017-05-02 23:34:21
add safe release build mode
closes #288
1 parent 7c236f6
src/all_types.hpp
@@ -1297,6 +1297,12 @@ struct TimeEvent {
     const char *name;
 };
 
+enum BuildMode {
+    BuildModeDebug,
+    BuildModeFastRelease,
+    BuildModeSafeRelease,
+};
+
 struct CodeGen {
     LLVMModuleRef module;
     ZigList<ErrorMsg*> errors;
@@ -1392,7 +1398,7 @@ struct CodeGen {
     Buf *dynamic_linker;
     Buf *ar_path;
     Buf triple_str;
-    bool is_release_build;
+    BuildMode build_mode;
     bool is_test_build;
     uint32_t target_os_index;
     uint32_t target_arch_index;
src/codegen.cpp
@@ -55,11 +55,12 @@ static PackageTableEntry *new_package(const char *root_src_dir, const char *root
     return entry;
 }
 
-CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out_type) {
+CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out_type, BuildMode build_mode) {
     CodeGen *g = allocate<CodeGen>(1);
 
     codegen_add_time_event(g, "Initialize");
 
+    g->build_mode = build_mode;
     g->out_type = out_type;
     g->import_table.init(32);
     g->builtin_fn_table.init(32);
@@ -72,7 +73,6 @@ CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out
     g->memoized_fn_eval_table.init(16);
     g->exported_symbol_names.init(8);
     g->external_prototypes.init(8);
-    g->is_release_build = false;
     g->is_test_build = false;
     g->want_h_file = (out_type == OutTypeObj || out_type == OutTypeLib);
 
@@ -154,10 +154,6 @@ void codegen_set_clang_argv(CodeGen *g, const char **args, size_t len) {
     g->clang_argv_len = len;
 }
 
-void codegen_set_is_release(CodeGen *g, bool is_release_build) {
-    g->is_release_build = is_release_build;
-}
-
 void codegen_set_omit_zigrt(CodeGen *g, bool omit_zigrt) {
     g->omit_zigrt = omit_zigrt;
 }
@@ -393,7 +389,7 @@ static LLVMValueRef fn_llvm_value(CodeGen *g, FnTableEntry *fn_table_entry) {
     }
 
     if (fn_table_entry->body_node != nullptr) {
-        bool want_fn_safety = !g->is_release_build && !fn_table_entry->def_scope->safety_off;
+        bool want_fn_safety = g->build_mode != BuildModeFastRelease && !fn_table_entry->def_scope->safety_off;
         if (want_fn_safety) {
             if (g->link_libc) {
                 addLLVMFnAttr(fn_table_entry->llvm_value, "sspstrong");
@@ -407,7 +403,7 @@ static LLVMValueRef fn_llvm_value(CodeGen *g, FnTableEntry *fn_table_entry) {
         ZigLLVMAddFunctionAttrCold(fn_table_entry->llvm_value);
     }
     addLLVMFnAttr(fn_table_entry->llvm_value, "nounwind");
-    if (!g->is_release_build && fn_table_entry->fn_inline != FnInlineAlways) {
+    if (g->build_mode == BuildModeDebug && fn_table_entry->fn_inline != FnInlineAlways) {
         ZigLLVMAddFunctionAttr(fn_table_entry->llvm_value, "no-frame-pointer-elim", "true");
         ZigLLVMAddFunctionAttr(fn_table_entry->llvm_value, "no-frame-pointer-elim-non-leaf", nullptr);
     }
@@ -440,7 +436,7 @@ static ZigLLVMDIScope *get_di_scope(CodeGen *g, Scope *scope) {
             unsigned scope_line = line_number;
             bool is_definition = fn_table_entry->body_node != nullptr;
             unsigned flags = 0;
-            bool is_optimized = g->is_release_build;
+            bool is_optimized = g->build_mode != BuildModeDebug;
             bool is_internal_linkage = (fn_table_entry->linkage == GlobalLinkageIdInternal);
             ZigLLVMDISubprogram *subprogram = ZigLLVMCreateFunction(g->dbuilder,
                 get_di_scope(g, scope->parent), buf_ptr(&fn_table_entry->symbol_name), "",
@@ -555,7 +551,7 @@ static LLVMValueRef get_handle_value(CodeGen *g, LLVMValueRef ptr, TypeTableEntr
 }
 
 static bool ir_want_debug_safety(CodeGen *g, IrInstruction *instruction) {
-    if (g->is_release_build)
+    if (g->build_mode == BuildModeFastRelease)
         return false;
 
     // TODO memoize
@@ -725,7 +721,7 @@ static LLVMValueRef get_safety_crash_err_fn(CodeGen *g) {
     LLVMSetLinkage(fn_val, LLVMInternalLinkage);
     LLVMSetFunctionCallConv(fn_val, LLVMFastCallConv);
     addLLVMFnAttr(fn_val, "nounwind");
-    if (!g->is_release_build) {
+    if (g->build_mode == BuildModeDebug) {
         ZigLLVMAddFunctionAttr(fn_val, "no-frame-pointer-elim", "true");
         ZigLLVMAddFunctionAttr(fn_val, "no-frame-pointer-elim-non-leaf", nullptr);
     }
@@ -1697,7 +1693,7 @@ static LLVMValueRef ir_render_decl_var(CodeGen *g, IrExecutable *executable,
     if (!type_has_bits(var->value->type))
         return nullptr;
 
-    if (var->ref_count == 0 && g->is_release_build)
+    if (var->ref_count == 0 && g->build_mode != BuildModeDebug)
         return nullptr;
 
     IrInstruction *init_value = decl_var_instruction->init_value;
@@ -3926,7 +3922,7 @@ static void do_code_gen(CodeGen *g) {
     os_path_join(g->cache_dir, o_basename, output_path);
     ensure_cache_dir(g);
     if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, buf_ptr(output_path),
-                LLVMObjectFile, &err_msg, !g->is_release_build))
+                LLVMObjectFile, &err_msg, g->build_mode == BuildModeDebug))
     {
         zig_panic("unable to write object file: %s", err_msg);
     }
@@ -4320,6 +4316,15 @@ static const char *bool_to_str(bool b) {
     return b ? "true" : "false";
 }
 
+static const char *build_mode_to_str(BuildMode build_mode) {
+    switch (build_mode) {
+        case BuildModeDebug: return "Mode.Debug";
+        case BuildModeSafeRelease: return "Mode.ReleaseSafe";
+        case BuildModeFastRelease: return "Mode.ReleaseFast";
+    }
+    zig_unreachable();
+}
+
 static void define_builtin_compile_vars(CodeGen *g) {
     if (g->std_package == nullptr)
         return;
@@ -4428,13 +4433,21 @@ static void define_builtin_compile_vars(CodeGen *g) {
             "    SeqCst,\n"
             "};\n\n");
     }
+    {
+        buf_appendf(contents,
+            "pub const Mode = enum {\n"
+            "    Debug,\n"
+            "    ReleaseSafe,\n"
+            "    ReleaseFast,\n"
+            "};\n\n");
+    }
     buf_appendf(contents, "pub const is_big_endian = %s;\n", bool_to_str(g->is_big_endian));
-    buf_appendf(contents, "pub const is_release = %s;\n", bool_to_str(g->is_release_build));
     buf_appendf(contents, "pub const is_test = %s;\n", bool_to_str(g->is_test_build));
     buf_appendf(contents, "pub const os = Os.%s;\n", cur_os);
     buf_appendf(contents, "pub const arch = Arch.%s;\n", cur_arch);
     buf_appendf(contents, "pub const environ = Environ.%s;\n", cur_environ);
     buf_appendf(contents, "pub const object_format = ObjectFormat.%s;\n", cur_obj_fmt);
+    buf_appendf(contents, "pub const mode = %s;\n", build_mode_to_str(g->build_mode));
 
     {
         buf_appendf(contents, "pub const link_libs = [][]const u8 {\n");
@@ -4484,8 +4497,8 @@ static void init(CodeGen *g) {
         zig_panic("unable to create target based on: %s", buf_ptr(&g->triple_str));
     }
 
-
-    LLVMCodeGenOptLevel opt_level = g->is_release_build ? LLVMCodeGenLevelAggressive : LLVMCodeGenLevelNone;
+    bool is_optimized = g->build_mode != BuildModeDebug;
+    LLVMCodeGenOptLevel opt_level = is_optimized ? LLVMCodeGenLevelAggressive : LLVMCodeGenLevelNone;
 
     LLVMRelocMode reloc_mode = g->is_static ? LLVMRelocStatic : LLVMRelocPIC;
 
@@ -4518,7 +4531,6 @@ static void init(CodeGen *g) {
 
 
     Buf *producer = buf_sprintf("zig %s", ZIG_VERSION_STRING);
-    bool is_optimized = g->is_release_build;
     const char *flags = "";
     unsigned runtime_version = 0;
     ZigLLVMDIFile *compile_unit_file = ZigLLVMCreateFile(g->dbuilder, buf_ptr(g->root_out_name),
src/codegen.hpp
@@ -14,10 +14,9 @@
 
 #include <stdio.h>
 
-CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out_type);
+CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out_type, BuildMode build_mode);
 
 void codegen_set_clang_argv(CodeGen *codegen, const char **args, size_t len);
-void codegen_set_is_release(CodeGen *codegen, bool is_release);
 void codegen_set_is_test(CodeGen *codegen, bool is_test);
 void codegen_set_each_lib_rpath(CodeGen *codegen, bool each_lib_rpath);
 
src/ir.cpp
@@ -4649,7 +4649,6 @@ static IrInstruction *ir_gen_var_decl(IrBuilder *irb, Scope *scope, AstNode *nod
     if (init_value == irb->codegen->invalid_instruction)
         return init_value;
 
-
     IrInstruction *result = ir_build_var_decl(irb, scope, node, var, type_instruction, init_value);
     var->decl_instruction = result;
     return result;
src/link.cpp
@@ -37,7 +37,7 @@ static Buf *build_o(CodeGen *parent_gen, const char *oname) {
     os_path_join(parent_gen->zig_std_special_dir, source_basename, full_path);
 
     ZigTarget *child_target = parent_gen->is_native_target ? nullptr : &parent_gen->zig_target;
-    CodeGen *child_gen = codegen_create(full_path, child_target, OutTypeObj);
+    CodeGen *child_gen = codegen_create(full_path, child_target, OutTypeObj, parent_gen->build_mode);
     child_gen->link_libc = parent_gen->link_libc;
 
     child_gen->link_libs.resize(parent_gen->link_libs.length);
@@ -50,8 +50,6 @@ static Buf *build_o(CodeGen *parent_gen, const char *oname) {
 
     codegen_set_cache_dir(child_gen, parent_gen->cache_dir);
 
-    codegen_set_is_release(child_gen, parent_gen->is_release_build);
-
     codegen_set_strip(child_gen, parent_gen->strip_debug_symbols);
     codegen_set_is_static(child_gen, parent_gen->is_static);
 
src/main.cpp
@@ -38,7 +38,8 @@ static int usage(const char *arg0) {
         "  --output-h [file]            override generated header file path\n"
         "  --pkg-begin [name] [path]    make package available to import and push current pkg\n"
         "  --pkg-end                    pop current pkg\n"
-        "  --release                    build with optimizations on and debug protection off\n"
+        "  --release-fast               build with optimizations on and safety off\n"
+        "  --release-safe               build with optimizations on and safety on\n"
         "  --static                     output will be statically linked\n"
         "  --strip                      exclude debug symbols\n"
         "  --target-arch [name]         specify target architecture\n"
@@ -161,7 +162,6 @@ int main(int argc, char **argv) {
     const char *in_file = nullptr;
     const char *out_file = nullptr;
     const char *out_file_h = nullptr;
-    bool is_release_build = false;
     bool strip = false;
     bool is_static = false;
     OutType out_type = OutTypeUnknown;
@@ -201,6 +201,7 @@ int main(int argc, char **argv) {
     bool timing_info = false;
     const char *cache_dir = nullptr;
     CliPkg *cur_pkg = allocate<CliPkg>(1);
+    BuildMode build_mode = BuildModeDebug;
 
     if (argc >= 2 && strcmp(argv[1], "build") == 0) {
         const char *zig_exe_path = arg0;
@@ -237,7 +238,7 @@ int main(int argc, char **argv) {
             }
         }
 
-        CodeGen *g = codegen_create(build_runner_path, nullptr, OutTypeExe);
+        CodeGen *g = codegen_create(build_runner_path, nullptr, OutTypeExe, BuildModeDebug);
         codegen_set_out_name(g, buf_create_from_str("build"));
         codegen_set_verbose(g, verbose);
 
@@ -319,8 +320,10 @@ int main(int argc, char **argv) {
         char *arg = argv[i];
 
         if (arg[0] == '-') {
-            if (strcmp(arg, "--release") == 0) {
-                is_release_build = true;
+            if (strcmp(arg, "--release-fast") == 0) {
+                build_mode = BuildModeFastRelease;
+            } else if (strcmp(arg, "--release-safe") == 0) {
+                build_mode = BuildModeSafeRelease;
             } else if (strcmp(arg, "--strip") == 0) {
                 strip = true;
             } else if (strcmp(arg, "--static") == 0) {
@@ -569,10 +572,9 @@ int main(int argc, char **argv) {
                     buf_create_from_str((cache_dir == nullptr) ? default_zig_cache_name : cache_dir),
                     full_cache_dir);
 
-            CodeGen *g = codegen_create(zig_root_source_file, target, out_type);
+            CodeGen *g = codegen_create(zig_root_source_file, target, out_type, build_mode);
             codegen_set_out_name(g, buf_out_name);
             codegen_set_lib_version(g, ver_major, ver_minor, ver_patch);
-            codegen_set_is_release(g, is_release_build);
             codegen_set_is_test(g, cmd == CmdTest);
             codegen_set_linker_script(g, linker_script);
             codegen_set_cache_dir(g, full_cache_dir);
std/special/build_file_template.zig
@@ -1,10 +1,20 @@
 const Builder = @import("std").build.Builder;
+const Mode = @import("builtin").Mode;
 
 pub fn build(b: &Builder) {
-    const release = b.option(bool, "release", "optimizations on and safety off") ?? false;
+    const release_safe = b.option(bool, "--release-safe", "optimizations on and safety on") ?? false;
+    const release_fast = b.option(bool, "--release-fast", "optimizations on and safety off") ?? false;
+
+    const build_mode = if (release_safe) {
+        Mode.ReleaseSafe
+    } else if (release_fast) {
+        Mode.ReleaseFast
+    } else {
+        Mode.Debug
+    };
 
     const exe = b.addExecutable("YOUR_NAME_HERE", "src/main.zig");
-    exe.setRelease(release);
+    exe.setBuildMode(build_mode);
 
     b.default_step.dependOn(&exe.step);
 }
std/special/builtin.zig
@@ -32,7 +32,7 @@ export fn memcpy(noalias dest: ?&u8, noalias src: ?&const u8, n: usize) {
 }
 
 export fn __stack_chk_fail() {
-    if (builtin.is_release) {
+    if (builtin.mode == builtin.Mode.ReleaseFast) {
         @setGlobalLinkage(__stack_chk_fail, builtin.GlobalLinkage.Internal);
         unreachable;
     }
std/build.zig
@@ -405,6 +405,23 @@ pub const Builder = struct {
         return &step_info.step;
     }
 
+    pub fn standardReleaseOptions(self: &Builder) -> builtin.Mode {
+        const release_safe = self.option(bool, "release-safe", "optimizations on and safety on") ?? false;
+        const release_fast = self.option(bool, "release-fast", "optimizations on and safety off") ?? false;
+
+        if (release_safe and !release_fast) {
+            return builtin.Mode.ReleaseSafe;
+        } else if (release_fast and !release_safe) {
+            return builtin.Mode.ReleaseFast;
+        } else if (!release_fast and !release_safe) {
+            return builtin.Mode.Debug;
+        } else {
+            %%io.stderr.printf("Both -Drelease-safe and -Drelease-fast specified");
+            self.markInvalidUserInput();
+            return builtin.Mode.Debug;
+        }
+    }
+
     pub fn addUserInputOption(self: &Builder, name: []const u8, value: []const u8) -> bool {
         test (%%self.user_input_options.put(name, UserInputOption {
             .name = name,
@@ -667,7 +684,7 @@ pub const LibExeObjStep = struct {
     linker_script: ?[]const u8,
     link_libs: BufSet,
     verbose: bool,
-    release: bool,
+    build_mode: builtin.Mode,
     static: bool,
     output_path: ?[]const u8,
     output_h_path: ?[]const u8,
@@ -724,7 +741,7 @@ pub const LibExeObjStep = struct {
         var self = LibExeObjStep {
             .builder = builder,
             .verbose = false,
-            .release = false,
+            .build_mode = builtin.Mode.Debug,
             .static = static,
             .kind = kind,
             .root_src = root_src,
@@ -794,8 +811,8 @@ pub const LibExeObjStep = struct {
         self.verbose = value;
     }
 
-    pub fn setRelease(self: &LibExeObjStep, value: bool) {
-        self.release = value;
+    pub fn setBuildMode(self: &LibExeObjStep, mode: builtin.Mode) {
+        self.build_mode = mode;
     }
 
     pub fn setOutputPath(self: &LibExeObjStep, value: []const u8) {
@@ -886,8 +903,10 @@ pub const LibExeObjStep = struct {
             %%zig_args.append("--verbose");
         }
 
-        if (self.release) {
-            %%zig_args.append("--release");
+        switch (self.build_mode) {
+            builtin.Mode.Debug => {},
+            builtin.Mode.ReleaseSafe => %%zig_args.append("--release-safe"),
+            builtin.Mode.ReleaseFast => %%zig_args.append("--release-fast"),
         }
 
         %%zig_args.append("--cache-dir");
@@ -980,7 +999,7 @@ pub const TestStep = struct {
     step: Step,
     builder: &Builder,
     root_src: []const u8,
-    release: bool,
+    build_mode: builtin.Mode,
     verbose: bool,
     link_libs: BufSet,
     name_prefix: []const u8,
@@ -992,7 +1011,7 @@ pub const TestStep = struct {
             .step = Step.init(step_name, builder.allocator, make),
             .builder = builder,
             .root_src = root_src,
-            .release = false,
+            .build_mode = builtin.Mode.Debug,
             .verbose = false,
             .name_prefix = "",
             .filter = null,
@@ -1004,8 +1023,8 @@ pub const TestStep = struct {
         self.verbose = value;
     }
 
-    pub fn setRelease(self: &TestStep, value: bool) {
-        self.release = value;
+    pub fn setBuildMode(self: &TestStep, mode: builtin.Mode) {
+        self.build_mode = mode;
     }
 
     pub fn linkSystemLibrary(self: &TestStep, name: []const u8) {
@@ -1034,8 +1053,10 @@ pub const TestStep = struct {
             %%zig_args.append("--verbose");
         }
 
-        if (self.release) {
-            %%zig_args.append("--release");
+        switch (self.build_mode) {
+            builtin.Mode.Debug => {},
+            builtin.Mode.ReleaseSafe => %%zig_args.append("--release-safe"),
+            builtin.Mode.ReleaseFast => %%zig_args.append("--release-fast"),
         }
 
         test (self.filter) |filter| {
@@ -1095,6 +1116,8 @@ pub const CLibExeObjStep = struct {
     name_only_filename: []const u8,
     object_src: []const u8,
     kind: Kind,
+    build_mode: builtin.Mode,
+    strip: bool,
 
     const Kind = enum {
         Exe,
@@ -1147,6 +1170,8 @@ pub const CLibExeObjStep = struct {
             .major_only_filename = undefined,
             .name_only_filename = undefined,
             .object_src = undefined,
+            .build_mode = builtin.Mode.Debug,
+            .strip = false,
         };
         clib.computeOutFileNames();
         return clib;
@@ -1233,13 +1258,8 @@ pub const CLibExeObjStep = struct {
         %%self.include_dirs.append(path);
     }
 
-    pub fn addCompileFlagsForRelease(self: &CLibExeObjStep, release: bool) {
-        if (release) {
-            %%self.cflags.append("-g");
-            %%self.cflags.append("-O2");
-        } else {
-            %%self.cflags.append("-g");
-        }
+    pub fn setBuildMode(self: &CLibExeObjStep, build_mode: builtin.Mode) {
+        self.build_mode = build_mode;
     }
 
     pub fn addCompileFlags(self: &CLibExeObjStep, flags: []const []const u8) {
@@ -1248,6 +1268,34 @@ pub const CLibExeObjStep = struct {
         }
     }
 
+    fn appendCompileFlags(self: &CLibExeObjStep, args: &List([]const u8)) {
+        if (!self.strip) {
+            %%args.append("-g");
+        }
+        switch (self.build_mode) {
+            builtin.Mode.Debug => {},
+            builtin.Mode.ReleaseSafe => {
+                %%args.append("-O2");
+                %%args.append("-D_FORTIFY_SOURCE=2");
+                %%args.append("-fstack-protector-strong");
+                %%args.append("--param");
+                %%args.append("ssp-buffer-size=4");
+            },
+            builtin.Mode.ReleaseFast => {
+                %%args.append("-O2");
+            },
+        }
+
+        for (self.include_dirs.toSliceConst()) |dir| {
+            %%args.append("-I");
+            %%args.append(self.builder.pathFromRoot(dir));
+        }
+
+        for (self.cflags.toSliceConst()) |cflag| {
+            %%args.append(cflag);
+        }
+    }
+
     fn make(step: &Step) -> %void {
         const self = @fieldParentPtr(CLibExeObjStep, "step", step);
         const cc = os.getEnv("CC") ?? "cc";
@@ -1265,14 +1313,7 @@ pub const CLibExeObjStep = struct {
                 %%cc_args.append("-o");
                 %%cc_args.append(output_path);
 
-                for (self.cflags.toSliceConst()) |cflag| {
-                    %%cc_args.append(cflag);
-                }
-
-                for (self.include_dirs.toSliceConst()) |dir| {
-                    %%cc_args.append("-I");
-                    %%cc_args.append(builder.pathFromRoot(dir));
-                }
+                self.appendCompileFlags(&cc_args);
 
                 %return builder.spawnChild(cc, cc_args.toSliceConst());
             },
@@ -1295,14 +1336,7 @@ pub const CLibExeObjStep = struct {
                     %%cc_args.append("-o");
                     %%cc_args.append(cache_o_file);
 
-                    for (self.cflags.toSliceConst()) |cflag| {
-                        %%cc_args.append(cflag);
-                    }
-
-                    for (self.include_dirs.toSliceConst()) |dir| {
-                        %%cc_args.append("-I");
-                        %%cc_args.append(builder.pathFromRoot(dir));
-                    }
+                    self.appendCompileFlags(&cc_args);
 
                     %return builder.spawnChild(cc, cc_args.toSliceConst());
 
std/hash_map.zig
@@ -5,7 +5,7 @@ const mem = @import("mem.zig");
 const Allocator = mem.Allocator;
 const builtin = @import("builtin");
 
-const want_modification_safety = !builtin.is_release;
+const want_modification_safety = builtin.mode != builtin.Mode.ReleaseFast;
 const debug_u32 = if (want_modification_safety) u32 else void;
 
 pub fn HashMap(comptime K: type, comptime V: type,
test/tests.zig
@@ -9,6 +9,7 @@ const io = std.io;
 const mem = std.mem;
 const fmt = std.fmt;
 const List = std.list.List;
+const Mode = @import("builtin").Mode;
 
 const compare_output = @import("compare_output.zig");
 const build_examples = @import("build_examples.zig");
@@ -107,14 +108,13 @@ pub fn addPkgTests(b: &build.Builder, test_filter: ?[]const u8, root_src: []cons
     name:[] const u8, desc: []const u8) -> &build.Step
 {
     const step = b.step(b.fmt("test-{}", name), desc);
-    for ([]bool{false, true}) |release| {
+    for ([]Mode{Mode.Debug, Mode.ReleaseFast}) |mode| {
         for ([]bool{false, true}) |link_libc| {
             const these_tests = b.addTest(root_src);
-            these_tests.setNamePrefix(b.fmt("{}-{}-{} ", name,
-                if (release) "release" else "debug",
+            these_tests.setNamePrefix(b.fmt("{}-{}-{} ", name, @enumTagName(mode),
                 if (link_libc) "c" else "bare"));
             these_tests.setFilter(test_filter);
-            these_tests.setRelease(release);
+            these_tests.setBuildMode(mode);
             if (link_libc) {
                 these_tests.linkSystemLibrary("c");
             }
@@ -372,9 +372,9 @@ pub const CompareOutputContext = struct {
                 self.step.dependOn(&run_and_cmp_output.step);
             },
             Special.None => {
-                for ([]bool{false, true}) |release| {
+                for ([]Mode{Mode.Debug, Mode.ReleaseFast}) |mode| {
                     const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "{} {} ({})",
-                        "compare-output", case.name, if (release) "release" else "debug");
+                        "compare-output", case.name, @enumTagName(mode));
                     test (self.test_filter) |filter| {
                         if (mem.indexOf(u8, annotated_case_name, filter) == null)
                             continue;
@@ -382,7 +382,7 @@ pub const CompareOutputContext = struct {
 
                     const exe = b.addExecutable("test", root_src);
                     exe.setOutputPath(exe_path);
-                    exe.setRelease(release);
+                    exe.setBuildMode(mode);
                     if (case.link_libc) {
                         exe.linkSystemLibrary("c");
                     }
@@ -465,10 +465,10 @@ pub const CompileErrorContext = struct {
         name: []const u8,
         test_index: usize,
         case: &const TestCase,
-        release: bool,
+        build_mode: Mode,
 
         pub fn create(context: &CompileErrorContext, name: []const u8,
-            case: &const TestCase, release: bool) -> &CompileCmpOutputStep
+            case: &const TestCase, build_mode: Mode) -> &CompileCmpOutputStep
         {
             const allocator = context.b.allocator;
             const ptr = %%allocator.create(CompileCmpOutputStep);
@@ -478,7 +478,7 @@ pub const CompileErrorContext = struct {
                 .name = name,
                 .test_index = context.test_index,
                 .case = case,
-                .release = release,
+                .build_mode = build_mode,
             };
             context.test_index += 1;
             return ptr;
@@ -501,8 +501,10 @@ pub const CompileErrorContext = struct {
             %%zig_args.append("--output");
             %%zig_args.append(b.pathFromRoot(obj_path));
 
-            if (self.release) {
-                %%zig_args.append("--release");
+            switch (self.build_mode) {
+                Mode.Debug => {},
+                Mode.ReleaseSafe => %%zig_args.append("--release-safe"),
+                Mode.ReleaseFast => %%zig_args.append("--release-fast"),
             }
 
             %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
@@ -620,15 +622,15 @@ pub const CompileErrorContext = struct {
     pub fn addCase(self: &CompileErrorContext, case: &const TestCase) {
         const b = self.b;
 
-        for ([]bool{false, true}) |release| {
+        for ([]Mode{Mode.Debug, Mode.ReleaseFast}) |mode| {
             const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "compile-error {} ({})",
-                case.name, if (release) "release" else "debug");
+                case.name, @enumTagName(mode));
             test (self.test_filter) |filter| {
                 if (mem.indexOf(u8, annotated_case_name, filter) == null)
                     continue;
             }
 
-            const compile_and_cmp_errors = CompileCmpOutputStep.create(self, annotated_case_name, case, release);
+            const compile_and_cmp_errors = CompileCmpOutputStep.create(self, annotated_case_name, case, mode);
             self.step.dependOn(&compile_and_cmp_errors.step);
 
             for (case.sources.toSliceConst()) |src_file| {
@@ -657,7 +659,7 @@ pub const BuildExamplesContext = struct {
     pub fn addBuildFile(self: &BuildExamplesContext, build_file: []const u8) {
         const b = self.b;
 
-        const annotated_case_name = b.fmt("build {} (debug)", build_file);
+        const annotated_case_name = b.fmt("build {} (Debug)", build_file);
         test (self.test_filter) |filter| {
             if (mem.indexOf(u8, annotated_case_name, filter) == null)
                 return;
@@ -686,16 +688,16 @@ pub const BuildExamplesContext = struct {
     pub fn addAllArgs(self: &BuildExamplesContext, root_src: []const u8, link_libc: bool) {
         const b = self.b;
 
-        for ([]bool{false, true}) |release| {
+        for ([]Mode{Mode.Debug, Mode.ReleaseFast}) |mode| {
             const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "build {} ({})",
-                root_src, if (release) "release" else "debug");
+                root_src, @enumTagName(mode));
             test (self.test_filter) |filter| {
                 if (mem.indexOf(u8, annotated_case_name, filter) == null)
                     continue;
             }
 
             const exe = b.addExecutable("test", root_src);
-            exe.setRelease(release);
+            exe.setBuildMode(mode);
             if (link_libc) {
                 exe.linkSystemLibrary("c");
             }