From db74832e40f98960e5dc3e46c8196c59033ee0f9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 19 Feb 2019 12:07:56 -0500 Subject: [PATCH] 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 --- src/all_types.hpp | 24 ++++++++++---- src/codegen.cpp | 81 +++++++++++++++++++++++++++++++++++++++++++++++ src/link.cpp | 1 + src/main.cpp | 11 +++++++ src/target.cpp | 20 ++++++++++-- src/target.hpp | 2 ++ 6 files changed, 130 insertions(+), 9 deletions(-) diff --git a/src/all_types.hpp b/src/all_types.hpp index 0c81f1f53..5bac44f9d 100644 --- a/src/all_types.hpp +++ b/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 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; diff --git a/src/codegen.cpp b/src/codegen.cpp index f0bce2b03..86923a0bd 100644 --- a/src/codegen.cpp +++ b/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); diff --git a/src/link.cpp b/src/link.cpp index a3a68e24d..2878a0035 100644 --- a/src/link.cpp +++ b/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)); diff --git a/src/main.cpp b/src/main.cpp index 441341c12..dd35cee5a 100644 --- a/src/main.cpp +++ b/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) { diff --git a/src/target.cpp b/src/target.cpp index 5352a2182..3e733da0e 100644 --- a/src/target.cpp +++ b/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(); +} diff --git a/src/target.hpp b/src/target.hpp index 99d1cadf5..292ea8f94 100644 --- a/src/target.hpp +++ b/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