Commit db74832e40

Andrew Kelley <andrew@ziglang.org>
2019-02-19 18:07:56
valgrind client requests for undefined values
with this change, when you assign undefined, zig emits a few assembly instructions to tell valgrind that the memory is undefined it's on by default for debug builds, and disabled otherwise. only support for linux, darwin, solaris, mingw on x86_64 is currently implemented. --disable-valgrind turns it off even in debug mode. --enable-valgrind turns it on even in release modes. It's always disabled for compiler_rt.a and builtin.a. Adds `@import("builtin").valgrind_support` which lets code know at comptime whether valgrind client requests are enabled. See #1989
1 parent c9fb524
src/all_types.hpp
@@ -1345,15 +1345,11 @@ struct ZigFn {
     // in the case of async functions this is the implicit return type according to the
     // zig source code, not according to zig ir
     ZigType *src_implicit_return_type;
-    bool is_test;
-    FnInline fn_inline;
-    FnAnalState anal_state;
     IrExecutable ir_executable;
     IrExecutable analyzed_executable;
     size_t prealloc_bbc;
     AstNode **param_source_nodes;
     Buf **param_names;
-    uint32_t align_bytes;
 
     AstNode *fn_no_inline_set_node;
     AstNode *fn_static_eval_set_node;
@@ -1363,13 +1359,22 @@ struct ZigFn {
 
     Buf *section_name;
     AstNode *set_alignstack_node;
-    uint32_t alignstack_value;
 
     AstNode *set_cold_node;
-    bool is_cold;
 
     ZigList<FnExport> export_list;
+
+    LLVMValueRef valgrind_client_request_array;
+
+    FnInline fn_inline;
+    FnAnalState anal_state;
+
+    uint32_t align_bytes;
+    uint32_t alignstack_value;
+
     bool calls_or_awaits_errorable_fn;
+    bool is_cold;
+    bool is_test;
 };
 
 uint32_t fn_table_entry_hash(ZigFn*);
@@ -1612,6 +1617,12 @@ struct LinkLib {
     bool provided_explicitly;
 };
 
+enum ValgrindSupport {
+    ValgrindSupportAuto,
+    ValgrindSupportDisabled,
+    ValgrindSupportEnabled,
+};
+
 // When adding fields, check if they should be added to the hash computation in build_with_cache
 struct CodeGen {
     //////////////////////////// Runtime State
@@ -1813,6 +1824,7 @@ struct CodeGen {
     OutType out_type;
     ZigTarget zig_target;
     TargetSubsystem subsystem;
+    ValgrindSupport valgrind_support;
     bool is_static;
     bool strip_debug_symbols;
     bool is_test_build;
src/codegen.cpp
@@ -3341,6 +3341,77 @@ static bool value_is_all_undef(ConstExprValue *const_val) {
     zig_unreachable();
 }
 
+static LLVMValueRef gen_valgrind_client_request(CodeGen *g, LLVMValueRef default_value, LLVMValueRef request,
+        LLVMValueRef a1, LLVMValueRef a2, LLVMValueRef a3, LLVMValueRef a4, LLVMValueRef a5)
+{
+    if (!target_has_valgrind_support(&g->zig_target)) {
+        return default_value;
+    }
+    LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->type_ref;
+    bool asm_has_side_effects = true;
+    bool asm_is_alignstack = false;
+    if (g->zig_target.arch.arch == ZigLLVM_x86_64) {
+        if (g->zig_target.os == OsLinux || target_is_darwin(&g->zig_target) || g->zig_target.os == OsSolaris ||
+            (g->zig_target.os == OsWindows && g->zig_target.env_type != ZigLLVM_MSVC))
+        {
+            if (g->cur_fn->valgrind_client_request_array == nullptr) {
+                LLVMBasicBlockRef prev_block = LLVMGetInsertBlock(g->builder);
+                LLVMBasicBlockRef entry_block = LLVMGetEntryBasicBlock(g->cur_fn->llvm_value);
+                LLVMValueRef first_inst = LLVMGetFirstInstruction(entry_block);
+                LLVMPositionBuilderBefore(g->builder, first_inst);
+                LLVMTypeRef array_type_ref = LLVMArrayType(usize_type_ref, 6);
+                g->cur_fn->valgrind_client_request_array = LLVMBuildAlloca(g->builder, array_type_ref, "");
+                LLVMPositionBuilderAtEnd(g->builder, prev_block);
+            }
+            LLVMValueRef array_ptr = g->cur_fn->valgrind_client_request_array;
+            LLVMValueRef array_elements[] = {request, a1, a2, a3, a4, a5};
+            LLVMValueRef zero = LLVMConstInt(usize_type_ref, 0, false);
+            for (unsigned i = 0; i < 6; i += 1) {
+                LLVMValueRef indexes[] = {
+                    zero,
+                    LLVMConstInt(usize_type_ref, i, false),
+                };
+                LLVMValueRef elem_ptr = LLVMBuildInBoundsGEP(g->builder, array_ptr, indexes, 2, "");
+                LLVMBuildStore(g->builder, array_elements[i], elem_ptr);
+            }
+
+            Buf *asm_template = buf_create_from_str(
+                "rolq $$3,  %rdi ; rolq $$13, %rdi\n"
+                "rolq $$61, %rdi ; rolq $$51, %rdi\n"
+                "xchgq %rbx,%rbx\n"
+            );
+            Buf *asm_constraints = buf_create_from_str(
+                "={rdx},{rax},0,~{cc},~{memory}"
+            );
+            unsigned input_and_output_count = 2;
+            LLVMValueRef array_ptr_as_usize = LLVMBuildPtrToInt(g->builder, array_ptr, usize_type_ref, "");
+            LLVMValueRef param_values[] = { array_ptr_as_usize, default_value };
+            LLVMTypeRef param_types[] = {usize_type_ref, usize_type_ref};
+            LLVMTypeRef function_type = LLVMFunctionType(usize_type_ref, param_types,
+                    input_and_output_count, false);
+            LLVMValueRef asm_fn = LLVMGetInlineAsm(function_type, buf_ptr(asm_template), buf_len(asm_template),
+                    buf_ptr(asm_constraints), buf_len(asm_constraints), asm_has_side_effects, asm_is_alignstack,
+                    LLVMInlineAsmDialectATT);
+            return LLVMBuildCall(g->builder, asm_fn, param_values, input_and_output_count, "");
+        }
+    }
+    zig_unreachable();
+}
+
+static bool want_valgrind_support(CodeGen *g) {
+    if (!target_has_valgrind_support(&g->zig_target))
+        return false;
+    switch (g->valgrind_support) {
+        case ValgrindSupportDisabled:
+            return false;
+        case ValgrindSupportEnabled:
+            return true;
+        case ValgrindSupportAuto:
+            return g->build_mode == BuildModeDebug;
+    }
+    zig_unreachable();
+}
+
 static void gen_undef_init(CodeGen *g, uint32_t ptr_align_bytes, ZigType *value_type, LLVMValueRef ptr) {
     assert(type_has_bits(value_type));
     uint64_t size_bytes = LLVMStoreSizeOfType(g->target_data_ref, value_type->type_ref);
@@ -3353,6 +3424,14 @@ static void gen_undef_init(CodeGen *g, uint32_t ptr_align_bytes, ZigType *value_
     ZigType *usize = g->builtin_types.entry_usize;
     LLVMValueRef byte_count = LLVMConstInt(usize->type_ref, size_bytes, false);
     ZigLLVMBuildMemSet(g->builder, dest_ptr, fill_char, byte_count, ptr_align_bytes, false);
+    // then tell valgrind that the memory is undefined even though we just memset it
+    if (want_valgrind_support(g)) {
+        static const uint32_t VG_USERREQ__MAKE_MEM_UNDEFINED = 1296236545;
+        LLVMValueRef zero = LLVMConstInt(usize->type_ref, 0, false);
+        LLVMValueRef req = LLVMConstInt(usize->type_ref, VG_USERREQ__MAKE_MEM_UNDEFINED, false);
+        LLVMValueRef ptr_as_usize = LLVMBuildPtrToInt(g->builder, dest_ptr, usize->type_ref, "");
+        gen_valgrind_client_request(g, zero, req, ptr_as_usize, byte_count, zero, zero, zero);
+    }
 }
 
 static LLVMValueRef ir_render_store_ptr(CodeGen *g, IrExecutable *executable, IrInstructionStorePtr *instruction) {
@@ -7525,6 +7604,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
     buf_appendf(contents, "pub const mode = %s;\n", build_mode_to_str(g->build_mode));
     buf_appendf(contents, "pub const link_libc = %s;\n", bool_to_str(g->libc_link_lib != nullptr));
     buf_appendf(contents, "pub const have_error_return_tracing = %s;\n", bool_to_str(g->have_err_ret_tracing));
+    buf_appendf(contents, "pub const valgrind_support = %s;\n", bool_to_str(want_valgrind_support(g)));
 
     buf_appendf(contents, "pub const __zig_test_fn_slice = {}; // overwritten later\n");
 
@@ -8489,6 +8569,7 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {
     cache_bool(ch, g->linker_rdynamic);
     cache_bool(ch, g->each_lib_rpath);
     cache_bool(ch, g->disable_pic);
+    cache_bool(ch, g->valgrind_support);
     cache_buf_opt(ch, g->mmacosx_version_min);
     cache_buf_opt(ch, g->mios_version_min);
     cache_usize(ch, g->version_major);
src/link.cpp
@@ -55,6 +55,7 @@ static Buf *build_a_raw(CodeGen *parent_gen, const char *aname, Buf *full_path)
     codegen_set_strip(child_gen, parent_gen->strip_debug_symbols);
     codegen_set_is_static(child_gen, true);
     child_gen->disable_pic = parent_gen->disable_pic;
+    child_gen->valgrind_support = ValgrindSupportDisabled;
 
     codegen_set_out_name(child_gen, buf_create_from_str(aname));
 
src/main.cpp
@@ -49,6 +49,8 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
         "  --cache [auto|off|on]        build in global cache, print out paths to stdout\n"
         "  --color [auto|off|on]        enable or disable colored error messages\n"
         "  --disable-pic                disable Position Independent Code for libraries\n"
+        "  --disable-valgrind           omit valgrind client requests in debug builds\n"
+        "  --enable-valgrind            include valgrind client requests release builds\n"
         "  --emit [asm|bin|llvm-ir]     emit a specific file format as compilation output\n"
         "  -ftime-report                print timing diagnostics\n"
         "  --libc-include-dir [path]    directory where libc stdlib.h resides\n"
@@ -396,6 +398,7 @@ int main(int argc, char **argv) {
     TargetSubsystem subsystem = TargetSubsystemAuto;
     bool is_single_threaded = false;
     Buf *override_std_dir = nullptr;
+    ValgrindSupport valgrind_support = ValgrindSupportAuto;
 
     if (argc >= 2 && strcmp(argv[1], "build") == 0) {
         Buf zig_exe_path_buf = BUF_INIT;
@@ -433,6 +436,7 @@ int main(int argc, char **argv) {
 
         CodeGen *g = codegen_create(build_runner_path, nullptr, OutTypeExe, BuildModeDebug, get_zig_lib_dir(),
                 override_std_dir);
+        g->valgrind_support = valgrind_support;
         g->enable_time_report = timing_info;
         buf_init_from_str(&g->cache_dir, cache_dir ? cache_dir : default_zig_cache_name);
         codegen_set_out_name(g, buf_create_from_str("build"));
@@ -520,6 +524,7 @@ int main(int argc, char **argv) {
         os_path_join(get_zig_special_dir(), buf_create_from_str("fmt_runner.zig"), fmt_runner_path);
         CodeGen *g = codegen_create(fmt_runner_path, nullptr, OutTypeExe, BuildModeDebug, get_zig_lib_dir(),
                 nullptr);
+        g->valgrind_support = valgrind_support;
         g->is_single_threaded = true;
         codegen_set_out_name(g, buf_create_from_str("fmt"));
         g->enable_cache = true;
@@ -577,6 +582,10 @@ int main(int argc, char **argv) {
                 timing_info = true;
             } else if (strcmp(arg, "--disable-pic") == 0) {
                 disable_pic = true;
+            } else if (strcmp(arg, "--enable-valgrind") == 0) {
+                valgrind_support = ValgrindSupportEnabled;
+            } else if (strcmp(arg, "--disable-valgrind") == 0) {
+                valgrind_support = ValgrindSupportDisabled;
             } else if (strcmp(arg, "--system-linker-hack") == 0) {
                 system_linker_hack = true;
             } else if (strcmp(arg, "--single-threaded") == 0) {
@@ -849,6 +858,7 @@ int main(int argc, char **argv) {
     switch (cmd) {
     case CmdBuiltin: {
         CodeGen *g = codegen_create(nullptr, target, out_type, build_mode, get_zig_lib_dir(), override_std_dir);
+        g->valgrind_support = valgrind_support;
         g->is_single_threaded = is_single_threaded;
         Buf *builtin_source = codegen_generate_builtin_source(g);
         if (fwrite(buf_ptr(builtin_source), 1, buf_len(builtin_source), stdout) != buf_len(builtin_source)) {
@@ -909,6 +919,7 @@ int main(int argc, char **argv) {
             }
             CodeGen *g = codegen_create(zig_root_source_file, target, out_type, build_mode, get_zig_lib_dir(),
                     override_std_dir);
+            g->valgrind_support = valgrind_support;
             g->subsystem = subsystem;
 
             if (disable_pic) {
src/target.cpp
@@ -544,7 +544,7 @@ void get_target_triple(Buf *triple, const ZigTarget *target) {
     }
 }
 
-static bool is_os_darwin(ZigTarget *target) {
+bool target_is_darwin(const ZigTarget *target) {
     switch (target->os) {
         case OsMacOSX:
         case OsIOS:
@@ -566,7 +566,7 @@ void resolve_target_object_format(ZigTarget *target) {
         case ZigLLVM_thumb:
         case ZigLLVM_x86:
         case ZigLLVM_x86_64:
-            if (is_os_darwin(target)) {
+            if (target_is_darwin(target)) {
                 target->oformat = ZigLLVM_MachO;
             } else if (target->os == OsWindows) {
                 target->oformat = ZigLLVM_COFF;
@@ -626,7 +626,7 @@ void resolve_target_object_format(ZigTarget *target) {
 
         case ZigLLVM_ppc:
         case ZigLLVM_ppc64:
-            if (is_os_darwin(target)) {
+            if (target_is_darwin(target)) {
                 target->oformat = ZigLLVM_MachO;
             } else {
                 target->oformat= ZigLLVM_ELF;
@@ -1084,3 +1084,17 @@ bool target_is_arm(const ZigTarget *target) {
     }
     zig_unreachable();
 }
+
+// Valgrind supports more, but Zig does not support them yet.
+bool target_has_valgrind_support(const ZigTarget *target) {
+    switch (target->arch.arch) {
+        case ZigLLVM_UnknownArch:
+            zig_unreachable();
+        case ZigLLVM_x86_64:
+            return (target->os == OsLinux || target_is_darwin(target) || target->os == OsSolaris ||
+                (target->os == OsWindows && target->env_type != ZigLLVM_MSVC));
+        default:
+            return false;
+    }
+    zig_unreachable();
+}
src/target.hpp
@@ -136,5 +136,7 @@ ZigLLVM_OSType get_llvm_os_type(Os os_type);
 
 bool target_is_arm(const ZigTarget *target);
 bool target_allows_addr_zero(const ZigTarget *target);
+bool target_has_valgrind_support(const ZigTarget *target);
+bool target_is_darwin(const ZigTarget *target);
 
 #endif