From c6160fa3a5c2f55871c2ab2377531c8a4e34a76a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 7 Mar 2022 15:43:20 -0700 Subject: [PATCH 01/11] LLVM: add compile unit to debug info This commit also adds a bunch of bindings for debug info. --- src/Compilation.zig | 2 +- src/codegen/llvm.zig | 66 +++++++- src/codegen/llvm/bindings.zig | 300 +++++++++++++++++++++++++++++++++- src/link.zig | 2 +- 4 files changed, 362 insertions(+), 8 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index b2ac9b249c3d..fa2e4ca68b6a 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -898,7 +898,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { // We put the `Compilation` itself in the arena. Freeing the arena will free the module. // It's initialized later after we prepare the initialization options. const comp = try arena.create(Compilation); - const root_name = try arena.dupe(u8, options.root_name); + const root_name = try arena.dupeZ(u8, options.root_name); const ofmt = options.object_format orelse options.target.getObjectFormat(); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index c837d9a00d48..c6ada88aa411 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -5,12 +5,14 @@ const Allocator = std.mem.Allocator; const log = std.log.scoped(.codegen); const math = std.math; const native_endian = builtin.cpu.arch.endian(); +const DW = std.dwarf; const llvm = @import("llvm/bindings.zig"); const link = @import("../link.zig"); const Compilation = @import("../Compilation.zig"); const build_options = @import("build_options"); const Module = @import("../Module.zig"); +const Package = @import("../Package.zig"); const TypedValue = @import("../TypedValue.zig"); const Air = @import("../Air.zig"); const Liveness = @import("../Liveness.zig"); @@ -159,6 +161,7 @@ pub fn targetTriple(allocator: Allocator, target: std.Target) ![:0]u8 { pub const Object = struct { llvm_module: *const llvm.Module, + dibuilder: ?*llvm.DIBuilder, context: *const llvm.Context, target_machine: *const llvm.TargetMachine, target_data: *const llvm.TargetData, @@ -180,8 +183,9 @@ pub const Object = struct { /// The backing memory for `type_map`. Periodically garbage collected after flush(). /// The code for doing the periodical GC is not yet implemented. type_map_arena: std.heap.ArenaAllocator, - /// The LLVM global table which holds the names corresponding to Zig errors. Note that the values - /// are not added until flushModule, when all errors in the compilation are known. + /// The LLVM global table which holds the names corresponding to Zig errors. + /// Note that the values are not added until flushModule, when all errors in + /// the compilation are known. error_name_table: ?*const llvm.Value, pub const TypeMap = std.HashMapUnmanaged( @@ -204,9 +208,7 @@ pub const Object = struct { initializeLLVMTarget(options.target.cpu.arch); - const root_nameZ = try gpa.dupeZ(u8, options.root_name); - defer gpa.free(root_nameZ); - const llvm_module = llvm.Module.createWithName(root_nameZ.ptr, context); + const llvm_module = llvm.Module.createWithName(options.root_name.ptr, context); errdefer llvm_module.dispose(); const llvm_target_triple = try targetTriple(gpa, options.target); @@ -221,6 +223,58 @@ pub const Object = struct { return error.InvalidLlvmTriple; } + llvm_module.setTarget(llvm_target_triple.ptr); + var opt_dbuilder: ?*llvm.DIBuilder = null; + errdefer if (opt_dbuilder) |dibuilder| dibuilder.dispose(); + + if (!options.strip) { + switch (options.object_format) { + .coff => llvm_module.addModuleCodeViewFlag(), + else => llvm_module.addModuleDebugInfoFlag(), + } + const dibuilder = llvm_module.createDIBuilder(true); + opt_dbuilder = dibuilder; + + // Don't use the version string here; LLVM misparses it when it + // includes the git revision. + const producer = try std.fmt.allocPrintZ(gpa, "zig {d}.{d}.{d}", .{ + build_options.semver.major, + build_options.semver.minor, + build_options.semver.patch, + }); + defer gpa.free(producer); + + // For macOS stack traces, we want to avoid having to parse the compilation unit debug + // info. As long as each debug info file has a path independent of the compilation unit + // directory (DW_AT_comp_dir), then we never have to look at the compilation unit debug + // info. If we provide an absolute path to LLVM here for the compilation unit debug + // info, LLVM will emit DWARF info that depends on DW_AT_comp_dir. To avoid this, we + // pass "." for the compilation unit directory. This forces each debug file to have a + // directory rather than be relative to DW_AT_comp_dir. According to DWARF 5, debug + // files will no longer reference DW_AT_comp_dir, for the purpose of being able to + // support the common practice of stripping all but the line number sections from an + // executable. + const compile_unit_dir = d: { + if (options.target.isDarwin()) break :d "."; + const mod = options.module orelse break :d "."; + break :d mod.root_pkg.root_src_directory.path orelse "."; + }; + const compile_unit_dir_z = try gpa.dupeZ(u8, compile_unit_dir); + defer gpa.free(compile_unit_dir_z); + + _ = dibuilder.createCompileUnit( + DW.LANG.C99, + dibuilder.createFile(options.root_name, compile_unit_dir_z), + producer, + options.optimize_mode != .Debug, + "", // flags + 0, // runtime version + "", // split name + 0, // dwo id + true, // emit debug info + ); + } + const opt_level: llvm.CodeGenOptLevel = if (options.optimize_mode == .Debug) .None else @@ -266,6 +320,7 @@ pub const Object = struct { return Object{ .llvm_module = llvm_module, + .dibuilder = opt_dbuilder, .context = context, .target_machine = target_machine, .target_data = target_data, @@ -277,6 +332,7 @@ pub const Object = struct { } pub fn deinit(self: *Object, gpa: Allocator) void { + if (self.dibuilder) |dib| dib.dispose(); self.target_data.dispose(); self.target_machine.dispose(); self.llvm_module.dispose(); diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index fd095ae5114f..35fd95feef6b 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -184,6 +184,9 @@ pub const Value = opaque { pub const setFunctionCallConv = LLVMSetFunctionCallConv; extern fn LLVMSetFunctionCallConv(Fn: *const Value, CC: CallConv) void; + pub const fnSetSubprogram = ZigLLVMFnSetSubprogram; + extern fn ZigLLVMFnSetSubprogram(f: *const Value, subprogram: *DISubprogram) void; + pub const setValueName = LLVMSetValueName; extern fn LLVMSetValueName(Val: *const Value, Name: [*:0]const u8) void; @@ -354,6 +357,18 @@ pub const Module = opaque { Name: [*:0]const u8, NameLen: usize, ) ?*const Value; + + pub const setTarget = LLVMSetTarget; + extern fn LLVMSetTarget(M: *const Module, Triple: [*:0]const u8) void; + + pub const addModuleDebugInfoFlag = ZigLLVMAddModuleDebugInfoFlag; + extern fn ZigLLVMAddModuleDebugInfoFlag(module: *const Module) void; + + pub const addModuleCodeViewFlag = ZigLLVMAddModuleCodeViewFlag; + extern fn ZigLLVMAddModuleCodeViewFlag(module: *const Module) void; + + pub const createDIBuilder = ZigLLVMCreateDIBuilder; + extern fn ZigLLVMCreateDIBuilder(module: *const Module, allow_unresolved: bool) *DIBuilder; }; pub const lookupIntrinsicID = LLVMLookupIntrinsicID; @@ -1203,7 +1218,7 @@ pub const WriteImportLibrary = ZigLLVMWriteImportLibrary; extern fn ZigLLVMWriteImportLibrary( def_path: [*:0]const u8, arch: ArchType, - output_lib_path: [*c]const u8, + output_lib_path: [*:0]const u8, kill_at: bool, ) bool; @@ -1400,3 +1415,286 @@ pub const address_space = struct { pub const constant_buffer_15: c_uint = 23; }; }; + +pub const DIEnumerator = opaque {}; +pub const DILocalVariable = opaque {}; +pub const DIGlobalVariable = opaque {}; +pub const DILocation = opaque {}; + +pub const DIType = opaque { + pub const toScope = ZigLLVMTypeToScope; + extern fn ZigLLVMTypeToScope(ty: *DIType) *DIScope; +}; +pub const DIFile = opaque { + pub const toScope = ZigLLVMFileToScope; + extern fn ZigLLVMFileToScope(difile: *DIFile) *DIScope; +}; +pub const DILexicalBlock = opaque { + pub const toScope = ZigLLVMLexicalBlockToScope; + extern fn ZigLLVMLexicalBlockToScope(lexical_block: *DILexicalBlock) *DIScope; +}; +pub const DICompileUnit = opaque { + pub const toScope = ZigLLVMCompileUnitToScope; + extern fn ZigLLVMCompileUnitToScope(compile_unit: *DICompileUnit) *DIScope; +}; +pub const DISubprogram = opaque { + pub const toScope = ZigLLVMSubprogramToScope; + extern fn ZigLLVMSubprogramToScope(subprogram: *DISubprogram) *DIScope; +}; + +pub const getDebugLoc = ZigLLVMGetDebugLoc; +extern fn ZigLLVMGetDebugLoc(line: c_uint, col: c_uint, scope: *DIScope) *DILocation; + +pub const DIBuilder = opaque { + pub const dispose = ZigLLVMDisposeDIBuilder; + extern fn ZigLLVMDisposeDIBuilder(dib: *DIBuilder) void; + + pub const finalize = ZigLLVMDIBuilderFinalize; + extern fn ZigLLVMDIBuilderFinalize(dib: *DIBuilder) void; + + pub const createPointerType = ZigLLVMCreateDebugPointerType; + extern fn ZigLLVMCreateDebugPointerType( + dib: *DIBuilder, + pointee_type: *DIType, + size_in_bits: u64, + align_in_bits: u64, + name: [*:0]const u8, + ) *DIType; + + pub const createBasicType = ZigLLVMCreateDebugBasicType; + extern fn ZigLLVMCreateDebugBasicType( + dib: *DIBuilder, + name: [*:0]const u8, + size_in_bits: u64, + encoding: c_uint, + ) *DIType; + + pub const createArrayType = ZigLLVMCreateDebugArrayType; + extern fn ZigLLVMCreateDebugArrayType( + dib: *DIBuilder, + size_in_bits: u64, + align_in_bits: u64, + elem_type: *DIType, + elem_count: c_int, + ) *DIType; + + pub const createEnumerator = ZigLLVMCreateDebugEnumerator; + extern fn ZigLLVMCreateDebugEnumerator( + dib: *DIBuilder, + name: [*:0]const u8, + val: i64, + ) *DIEnumerator; + + pub const createEnumerationType = ZigLLVMCreateDebugEnumerationType; + extern fn ZigLLVMCreateDebugEnumerationType( + dib: *DIBuilder, + scope: *DIScope, + name: [*:0]const u8, + file: *DIFile, + line_number: c_uint, + size_in_bits: u64, + align_in_bits: u64, + enumerator_array: [*]const *DIEnumerator, + enumerator_array_len: c_int, + underlying_type: *DIType, + unique_id: [*:0]const u8, + ) *DIType; + + pub const createStructType = ZigLLVMCreateDebugStructType; + extern fn ZigLLVMCreateDebugStructType( + dib: *DIBuilder, + scope: *DIScope, + name: [*:0]const u8, + file: *DIFile, + line_number: c_uint, + size_in_bits: u64, + align_in_bits: u64, + flags: c_uint, + derived_from: *DIType, + types_array: [*]const *DIType, + types_array_len: c_int, + run_time_lang: c_uint, + vtable_holder: *DIType, + unique_id: [*:0]const u8, + ) *DIType; + + pub const createUnionType = ZigLLVMCreateDebugUnionType; + extern fn ZigLLVMCreateDebugUnionType( + dib: *DIBuilder, + scope: *DIScope, + name: [*:0]const u8, + file: *DIFile, + line_number: c_uint, + size_in_bits: u64, + align_in_bits: u64, + flags: c_uint, + types_array: [*]const *DIType, + types_array_len: c_int, + run_time_lang: c_uint, + unique_id: [*:0]const u8, + ) *DIType; + + pub const createMemberType = ZigLLVMCreateDebugMemberType; + extern fn ZigLLVMCreateDebugMemberType( + dib: *DIBuilder, + scope: *DIScope, + name: [*:0]const u8, + file: *DIFile, + line: c_uint, + size_in_bits: u64, + align_in_bits: u64, + offset_in_bits: u64, + flags: c_uint, + ty: *DIType, + ) *DIType; + + pub const createReplaceableCompositeType = ZigLLVMCreateReplaceableCompositeType; + extern fn ZigLLVMCreateReplaceableCompositeType( + dib: *DIBuilder, + tag: c_uint, + name: [*:0]const u8, + scope: *DIScope, + file: *DIFile, + line: c_uint, + ) *DIType; + + pub const createForwardDeclType = ZigLLVMCreateDebugForwardDeclType; + extern fn ZigLLVMCreateDebugForwardDeclType( + dib: *DIBuilder, + tag: c_uint, + name: [*:0]const u8, + scope: *DIScope, + file: *DIFile, + line: c_uint, + ) *DIType; + + pub const replaceTemporary = ZigLLVMReplaceTemporary; + extern fn ZigLLVMReplaceTemporary(dib: *DIBuilder, ty: *DIType, replacement: *DIType) void; + + pub const replaceDebugArrays = ZigLLVMReplaceDebugArrays; + extern fn ZigLLVMReplaceDebugArrays( + dib: *DIBuilder, + ty: *DIType, + types_array: [*]const *DIType, + types_array_len: c_int, + ) void; + + pub const createSubroutineType = ZigLLVMCreateSubroutineType; + extern fn ZigLLVMCreateSubroutineType( + dib: *DIBuilder, + types_array: [*]const *DIType, + types_array_len: c_int, + flags: c_uint, + ) *DIType; + + pub const createAutoVariable = ZigLLVMCreateAutoVariable; + extern fn ZigLLVMCreateAutoVariable( + dib: *DIBuilder, + scope: *DIScope, + name: [*:0]const u8, + file: *DIFile, + line_no: c_uint, + ty: *DIType, + always_preserve: bool, + flags: c_uint, + ) *DILocalVariable; + + pub const createGlobalVariable = ZigLLVMCreateGlobalVariable; + extern fn ZigLLVMCreateGlobalVariable( + dib: *DIBuilder, + scope: *DIScope, + name: [*:0]const u8, + linkage_name: [*:0]const u8, + file: *DIFile, + line_no: c_uint, + di_type: *DIType, + is_local_to_unit: bool, + ) *DIGlobalVariable; + + pub const createParameterVariable = ZigLLVMCreateParameterVariable; + extern fn ZigLLVMCreateParameterVariable( + dib: *DIBuilder, + scope: *DIScope, + name: [*:0]const u8, + file: *DIFile, + line_no: c_uint, + ty: *DIType, + always_preserve: bool, + flags: c_uint, + arg_no: c_uint, + ) *DILocalVariable; + + pub const createLexicalBlock = ZigLLVMCreateLexicalBlock; + extern fn ZigLLVMCreateLexicalBlock( + dib: *DIBuilder, + scope: *DIScope, + file: *DIFile, + line: c_uint, + col: c_uint, + ) *DILexicalBlock; + + pub const createCompileUnit = ZigLLVMCreateCompileUnit; + extern fn ZigLLVMCreateCompileUnit( + dib: *DIBuilder, + lang: c_uint, + difile: *DIFile, + producer: [*:0]const u8, + is_optimized: bool, + flags: [*:0]const u8, + runtime_version: c_uint, + split_name: [*:0]const u8, + dwo_id: u64, + emit_debug_info: bool, + ) *DICompileUnit; + + pub const createFile = ZigLLVMCreateFile; + extern fn ZigLLVMCreateFile( + dib: *DIBuilder, + filename: [*:0]const u8, + directory: [*:0]const u8, + ) *DIFile; + + pub const createFunction = ZigLLVMCreateFunction; + extern fn ZigLLVMCreateFunction( + dib: *DIBuilder, + scope: *DIScope, + name: [*:0]const u8, + linkage_name: [*:0]const u8, + file: *DIFile, + lineno: c_uint, + fn_di_type: *DIType, + is_local_to_unit: bool, + is_definition: bool, + scope_line: c_uint, + flags: c_uint, + is_optimized: bool, + decl_subprogram: *DISubprogram, + ) *DISubprogram; + + pub const createVectorType = ZigLLVMDIBuilderCreateVectorType; + extern fn ZigLLVMDIBuilderCreateVectorType( + dib: *DIBuilder, + SizeInBits: u64, + AlignInBits: u32, + Ty: *DIType, + elem_count: u32, + ) *DIType; + + pub const insertDeclareAtEnd = ZigLLVMInsertDeclareAtEnd; + extern fn ZigLLVMInsertDeclareAtEnd( + dib: *DIBuilder, + storage: *const Value, + var_info: *DILocalVariable, + debug_loc: *DILocation, + basic_block_ref: *const BasicBlock, + ) *const Value; + + pub const insertDeclare = ZigLLVMInsertDeclare; + extern fn ZigLLVMInsertDeclare( + dib: *DIBuilder, + storage: *const Value, + var_info: *DILocalVariable, + debug_loc: *DILocation, + insert_before_instr: *const Value, + ) *const Value; +}; diff --git a/src/link.zig b/src/link.zig index 577a78d89345..34e2bb8ed838 100644 --- a/src/link.zig +++ b/src/link.zig @@ -72,7 +72,7 @@ pub const Options = struct { object_format: std.Target.ObjectFormat, optimize_mode: std.builtin.Mode, machine_code_model: std.builtin.CodeModel, - root_name: []const u8, + root_name: [:0]const u8, /// Not every Compilation compiles .zig code! For example you could do `zig build-exe foo.o`. module: ?*Module, dynamic_linker: ?[]const u8, From 627209253c65363483fbdae9effad51f1aed8c98 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 7 Mar 2022 19:53:38 -0700 Subject: [PATCH 02/11] langref: fix a stray anyopaque that should be a void --- doc/langref.html.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index f4960198388e..3ab279ff12c5 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -1317,7 +1317,7 @@ const @"identifier with spaces in it" = 0xff; const @"1SmallStep4Man" = 112358; const c = @import("std").c; -pub extern "c" fn @"error"() anyopaque; +pub extern "c" fn @"error"() void; pub extern "c" fn @"fstat$INODE64"(fd: c.fd_t, buf: *c.Stat) c_int; const Color = enum { From 0d24bc7da0b85c6b178b918f963fde94d8e10ee7 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 7 Mar 2022 19:54:35 -0700 Subject: [PATCH 03/11] LLVM: add DISubprogram and DIType lowering; handle dbg_stmt --- lib/std/dwarf.zig | 15 + src/codegen/llvm.zig | 551 ++++++++++++++++++++++++++++++++-- src/codegen/llvm/bindings.zig | 55 +++- 3 files changed, 589 insertions(+), 32 deletions(-) diff --git a/lib/std/dwarf.zig b/lib/std/dwarf.zig index eb204d15eef0..12ec357849d9 100644 --- a/lib/std/dwarf.zig +++ b/lib/std/dwarf.zig @@ -226,6 +226,21 @@ pub const LNCT = struct { pub const hi_user = 0x3fff; }; +pub const CC = enum(u8) { + normal = 0x1, + program = 0x2, + nocall = 0x3, + + pass_by_reference = 0x4, + pass_by_value = 0x5, + + lo_user = 0x40, + hi_user = 0xff, + + GNU_renesas_sh = 0x40, + GNU_borland_fastcall_i386 = 0x41, +}; + const PcRange = struct { start: u64, end: u64, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index c6ada88aa411..30b87ceed9b6 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -161,7 +161,12 @@ pub fn targetTriple(allocator: Allocator, target: std.Target) ![:0]u8 { pub const Object = struct { llvm_module: *const llvm.Module, - dibuilder: ?*llvm.DIBuilder, + di_builder: ?*llvm.DIBuilder, + /// One of these mappings: + /// - *Module.File => *DIFile + /// - *Module.Decl => *DISubprogram + di_map: std.AutoHashMapUnmanaged(*const anyopaque, *llvm.DIScope), + di_compile_unit: ?*llvm.DICompileUnit, context: *const llvm.Context, target_machine: *const llvm.TargetMachine, target_data: *const llvm.TargetData, @@ -224,16 +229,18 @@ pub const Object = struct { } llvm_module.setTarget(llvm_target_triple.ptr); - var opt_dbuilder: ?*llvm.DIBuilder = null; - errdefer if (opt_dbuilder) |dibuilder| dibuilder.dispose(); + var opt_di_builder: ?*llvm.DIBuilder = null; + errdefer if (opt_di_builder) |di_builder| di_builder.dispose(); + + var di_compile_unit: ?*llvm.DICompileUnit = null; if (!options.strip) { switch (options.object_format) { .coff => llvm_module.addModuleCodeViewFlag(), else => llvm_module.addModuleDebugInfoFlag(), } - const dibuilder = llvm_module.createDIBuilder(true); - opt_dbuilder = dibuilder; + const di_builder = llvm_module.createDIBuilder(true); + opt_di_builder = di_builder; // Don't use the version string here; LLVM misparses it when it // includes the git revision. @@ -262,9 +269,9 @@ pub const Object = struct { const compile_unit_dir_z = try gpa.dupeZ(u8, compile_unit_dir); defer gpa.free(compile_unit_dir_z); - _ = dibuilder.createCompileUnit( + di_compile_unit = di_builder.createCompileUnit( DW.LANG.C99, - dibuilder.createFile(options.root_name, compile_unit_dir_z), + di_builder.createFile(options.root_name, compile_unit_dir_z), producer, options.optimize_mode != .Debug, "", // flags @@ -320,7 +327,9 @@ pub const Object = struct { return Object{ .llvm_module = llvm_module, - .dibuilder = opt_dbuilder, + .di_map = .{}, + .di_builder = opt_di_builder, + .di_compile_unit = di_compile_unit, .context = context, .target_machine = target_machine, .target_data = target_data, @@ -332,7 +341,10 @@ pub const Object = struct { } pub fn deinit(self: *Object, gpa: Allocator) void { - if (self.dibuilder) |dib| dib.dispose(); + if (self.di_builder) |dib| { + dib.dispose(); + self.di_map.deinit(gpa); + } self.target_data.dispose(); self.target_machine.dispose(); self.llvm_module.dispose(); @@ -413,6 +425,9 @@ pub const Object = struct { pub fn flushModule(self: *Object, comp: *Compilation) !void { try self.genErrorNameTable(comp); + + if (self.di_builder) |dib| dib.finalize(); + if (comp.verbose_llvm_ir) { self.llvm_module.dump(); } @@ -526,8 +541,9 @@ pub const Object = struct { const target = dg.module.getTarget(); const sret = firstParamSRet(fn_info, target); const ret_ptr = if (sret) llvm_func.getParam(0) else null; + const gpa = dg.gpa; - var args = std.ArrayList(*const llvm.Value).init(dg.gpa); + var args = std.ArrayList(*const llvm.Value).init(gpa); defer args.deinit(); const param_offset: c_uint = @boolToInt(ret_ptr != null); @@ -538,8 +554,53 @@ pub const Object = struct { try args.append(llvm_func.getParam(llvm_arg_i)); } + var di_file: ?*llvm.DIFile = null; + var di_scope: ?*llvm.DIScope = null; + + if (dg.object.di_builder) |dib| { + di_file = s: { + const file = decl.src_namespace.file_scope; + const gop = try dg.object.di_map.getOrPut(gpa, file); + if (!gop.found_existing) { + const dir_path = file.pkg.root_src_directory.path orelse "."; + const sub_file_path_z = try gpa.dupeZ(u8, file.sub_file_path); + defer gpa.free(sub_file_path_z); + const dir_path_z = try gpa.dupeZ(u8, dir_path); + defer gpa.free(dir_path_z); + gop.value_ptr.* = dib.createFile(sub_file_path_z, dir_path_z).toScope(); + } + break :s @ptrCast(*llvm.DIFile, gop.value_ptr.*); + }; + + const line_number = decl.src_line + 1; + const is_internal_linkage = decl.val.tag() != .extern_fn and + !dg.module.decl_exports.contains(decl); + const noret_bit: c_uint = if (fn_info.return_type.isNoReturn()) + llvm.DIFlags.NoReturn + else + 0; + const subprogram = dib.createFunction( + di_file.?.toScope(), + decl.name, + llvm_func.getValueName(), + di_file.?, + line_number, + try dg.lowerDebugType(decl.ty), + is_internal_linkage, + true, // is definition + line_number + func.lbrace_line, // scope line + llvm.DIFlags.StaticMember | noret_bit, + dg.module.comp.bin_file.options.optimize_mode != .Debug, + null, // decl_subprogram + ); + + llvm_func.fnSetSubprogram(subprogram); + + di_scope = subprogram.toScope(); + } + var fg: FuncGen = .{ - .gpa = dg.gpa, + .gpa = gpa, .air = air, .liveness = liveness, .context = dg.context, @@ -552,6 +613,8 @@ pub const Object = struct { .llvm_func = llvm_func, .blocks = .{}, .single_threaded = module.comp.bin_file.options.single_threaded, + .di_scope = di_scope, + .di_file = di_file, }; defer fg.deinit(); @@ -1827,6 +1890,418 @@ pub const DeclGen = struct { } } + fn lowerDebugType(dg: *DeclGen, ty: Type) Allocator.Error!*llvm.DIType { + const gpa = dg.gpa; + const target = dg.module.getTarget(); + const dib = dg.object.di_builder.?; + switch (ty.zigTypeTag()) { + .Void, .NoReturn => return dib.createBasicType("void", 0, DW.ATE.signed), + .Int => { + const info = ty.intInfo(target); + assert(info.bits != 0); + const name = try ty.nameAlloc(gpa); // TODO this is a leak + const dwarf_encoding: c_uint = switch (info.signedness) { + .signed => DW.ATE.signed, + .unsigned => DW.ATE.unsigned, + }; + return dib.createBasicType(name, info.bits, dwarf_encoding); + }, + .Enum => { + @panic("TODO debug info type for enums"); + //var buffer: Type.Payload.Bits = undefined; + //const int_ty = ty.intTagType(&buffer); + //const bit_count = int_ty.intInfo(target).bits; + //assert(bit_count != 0); + //return dg.context.intType(bit_count); + }, + .Float => { + const bits = ty.floatBits(target); + const name = try ty.nameAlloc(gpa); // TODO this is a leak + return dib.createBasicType(name, bits, DW.ATE.float); + }, + .Bool => return dib.createBasicType("bool", 1, DW.ATE.boolean), + .Pointer => { + if (ty.isSlice()) { + @panic("TODO debug info type for slices"); + //var buf: Type.SlicePtrFieldTypeBuffer = undefined; + //const ptr_type = ty.slicePtrFieldType(&buf); + + //const fields: [2]*const llvm.Type = .{ + // try dg.llvmType(ptr_type), + // try dg.llvmType(Type.usize), + //}; + //return dg.context.structType(&fields, fields.len, .False); + } + + const ptr_info = ty.ptrInfo().data; + const elem_di_ty = try lowerDebugType(dg, ptr_info.pointee_type); + const name = try ty.nameAlloc(gpa); // TODO this is a leak + return dib.createPointerType( + elem_di_ty, + target.cpu.arch.ptrBitWidth(), + ty.ptrAlignment(target) * 8, + name, + ); + }, + .Opaque => { + @panic("TODO debug info type for opaque"); + }, + .Array => { + const elem_di_ty = try lowerDebugType(dg, ty.childType()); + return dib.createArrayType( + ty.abiSize(target) * 8, + ty.abiAlignment(target) * 8, + elem_di_ty, + @intCast(c_int, ty.arrayLen()), + ); + }, + .Vector => { + @panic("TODO debug info type for vector"); + }, + .Optional => { + @panic("TODO debug info type for optional"); + //var buf: Type.Payload.ElemType = undefined; + //const child_type = ty.optionalChild(&buf); + //if (!child_type.hasRuntimeBits()) { + // return dg.context.intType(1); + //} + //const payload_llvm_ty = try dg.llvmType(child_type); + //if (ty.isPtrLikeOptional()) { + // return payload_llvm_ty; + //} else if (!child_type.hasRuntimeBits()) { + // return dg.context.intType(1); + //} + + //const fields: [2]*const llvm.Type = .{ + // payload_llvm_ty, dg.context.intType(1), + //}; + //return dg.context.structType(&fields, fields.len, .False); + }, + .ErrorUnion => { + const err_set_ty = ty.errorUnionSet(); + const err_set_di_ty = try dg.lowerDebugType(err_set_ty); + const payload_ty = ty.errorUnionPayload(); + if (!payload_ty.hasRuntimeBits()) { + return err_set_di_ty; + } + const payload_di_ty = try dg.lowerDebugType(payload_ty); + + const name = try ty.nameAlloc(gpa); // TODO this is a leak + const di_file: ?*llvm.DIFile = null; + const line = 0; + const compile_unit_scope = dg.object.di_compile_unit.?.toScope(); + const fwd_decl = dib.createReplaceableCompositeType( + DW.TAG.structure_type, + name.ptr, + compile_unit_scope, + di_file, + line, + ); + + const err_set_size = err_set_ty.abiSize(target); + const err_set_align = err_set_ty.abiAlignment(target); + const payload_size = payload_ty.abiSize(target); + const payload_align = payload_ty.abiAlignment(target); + + var offset: u64 = 0; + offset += err_set_size; + offset = std.mem.alignForwardGeneric(u64, offset, payload_align); + const payload_offset = offset; + + const fields: [2]*llvm.DIType = .{ + dib.createMemberType( + fwd_decl.toScope(), + "tag", + di_file, + line, + err_set_size * 8, // size in bits + err_set_align * 8, // align in bits + 0, // offset in bits + 0, // flags + err_set_di_ty, + ), + dib.createMemberType( + fwd_decl.toScope(), + "value", + di_file, + line, + payload_size * 8, // size in bits + payload_align * 8, // align in bits + payload_offset * 8, // offset in bits + 0, // flags + payload_di_ty, + ), + }; + + const replacement_di_type = dib.createStructType( + compile_unit_scope, + name.ptr, + di_file, + line, + ty.abiSize(target) * 8, // size in bits + ty.abiAlignment(target) * 8, // align in bits + 0, // flags + null, // derived from + &fields, + fields.len, + 0, // run time lang + null, // vtable holder + "", // unique id + ); + dib.replaceTemporary(fwd_decl, replacement_di_type); + + return replacement_di_type; + }, + .ErrorSet => { + // TODO make this a proper enum with all the error codes in it. + // will need to consider how to take incremental compilation into account. + return dib.createBasicType("anyerror", 16, DW.ATE.unsigned); + }, + .Struct => { + @panic("TODO debug info type for struct"); + //const gop = try dg.object.type_map.getOrPut(gpa, ty); + //if (gop.found_existing) return gop.value_ptr.*; + + //// The Type memory is ephemeral; since we want to store a longer-lived + //// reference, we need to copy it here. + //gop.key_ptr.* = try ty.copy(dg.object.type_map_arena.allocator()); + + //if (ty.isTupleOrAnonStruct()) { + // const tuple = ty.tupleFields(); + // const llvm_struct_ty = dg.context.structCreateNamed(""); + // gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls + + // var llvm_field_types: std.ArrayListUnmanaged(*const llvm.Type) = .{}; + // defer llvm_field_types.deinit(gpa); + + // try llvm_field_types.ensureUnusedCapacity(gpa, tuple.types.len); + + // comptime assert(struct_layout_version == 2); + // var offset: u64 = 0; + // var big_align: u32 = 0; + + // for (tuple.types) |field_ty, i| { + // const field_val = tuple.values[i]; + // if (field_val.tag() != .unreachable_value) continue; + + // const field_align = field_ty.abiAlignment(target); + // big_align = @maximum(big_align, field_align); + // const prev_offset = offset; + // offset = std.mem.alignForwardGeneric(u64, offset, field_align); + + // const padding_len = offset - prev_offset; + // if (padding_len > 0) { + // const llvm_array_ty = dg.context.intType(8).arrayType(@intCast(c_uint, padding_len)); + // try llvm_field_types.append(gpa, llvm_array_ty); + // } + // const field_llvm_ty = try dg.llvmType(field_ty); + // try llvm_field_types.append(gpa, field_llvm_ty); + + // offset += field_ty.abiSize(target); + // } + // { + // const prev_offset = offset; + // offset = std.mem.alignForwardGeneric(u64, offset, big_align); + // const padding_len = offset - prev_offset; + // if (padding_len > 0) { + // const llvm_array_ty = dg.context.intType(8).arrayType(@intCast(c_uint, padding_len)); + // try llvm_field_types.append(gpa, llvm_array_ty); + // } + // } + + // llvm_struct_ty.structSetBody( + // llvm_field_types.items.ptr, + // @intCast(c_uint, llvm_field_types.items.len), + // .False, + // ); + + // return llvm_struct_ty; + //} + + //const struct_obj = ty.castTag(.@"struct").?.data; + + //if (struct_obj.layout == .Packed) { + // var buf: Type.Payload.Bits = undefined; + // const int_ty = struct_obj.packedIntegerType(target, &buf); + // const int_llvm_ty = try dg.llvmType(int_ty); + // gop.value_ptr.* = int_llvm_ty; + // return int_llvm_ty; + //} + + //const name = try struct_obj.getFullyQualifiedName(gpa); + //defer gpa.free(name); + + //const llvm_struct_ty = dg.context.structCreateNamed(name); + //gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls + + //assert(struct_obj.haveFieldTypes()); + + //var llvm_field_types: std.ArrayListUnmanaged(*const llvm.Type) = .{}; + //defer llvm_field_types.deinit(gpa); + + //try llvm_field_types.ensureUnusedCapacity(gpa, struct_obj.fields.count()); + + //comptime assert(struct_layout_version == 2); + //var offset: u64 = 0; + //var big_align: u32 = 0; + + //for (struct_obj.fields.values()) |field| { + // if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; + + // const field_align = field.normalAlignment(target); + // big_align = @maximum(big_align, field_align); + // const prev_offset = offset; + // offset = std.mem.alignForwardGeneric(u64, offset, field_align); + + // const padding_len = offset - prev_offset; + // if (padding_len > 0) { + // const llvm_array_ty = dg.context.intType(8).arrayType(@intCast(c_uint, padding_len)); + // try llvm_field_types.append(gpa, llvm_array_ty); + // } + // const field_llvm_ty = try dg.llvmType(field.ty); + // try llvm_field_types.append(gpa, field_llvm_ty); + + // offset += field.ty.abiSize(target); + //} + //{ + // const prev_offset = offset; + // offset = std.mem.alignForwardGeneric(u64, offset, big_align); + // const padding_len = offset - prev_offset; + // if (padding_len > 0) { + // const llvm_array_ty = dg.context.intType(8).arrayType(@intCast(c_uint, padding_len)); + // try llvm_field_types.append(gpa, llvm_array_ty); + // } + //} + + //llvm_struct_ty.structSetBody( + // llvm_field_types.items.ptr, + // @intCast(c_uint, llvm_field_types.items.len), + // .False, + //); + + //return llvm_struct_ty; + }, + .Union => { + @panic("TODO debug info type for union"); + //const gop = try dg.object.type_map.getOrPut(gpa, ty); + //if (gop.found_existing) return gop.value_ptr.*; + + //// The Type memory is ephemeral; since we want to store a longer-lived + //// reference, we need to copy it here. + //gop.key_ptr.* = try ty.copy(dg.object.type_map_arena.allocator()); + + //const layout = ty.unionGetLayout(target); + //const union_obj = ty.cast(Type.Payload.Union).?.data; + + //if (layout.payload_size == 0) { + // const enum_tag_llvm_ty = try dg.llvmType(union_obj.tag_ty); + // gop.value_ptr.* = enum_tag_llvm_ty; + // return enum_tag_llvm_ty; + //} + + //const name = try union_obj.getFullyQualifiedName(gpa); + //defer gpa.free(name); + + //const llvm_union_ty = dg.context.structCreateNamed(name); + //gop.value_ptr.* = llvm_union_ty; // must be done before any recursive calls + + //const aligned_field = union_obj.fields.values()[layout.most_aligned_field]; + //const llvm_aligned_field_ty = try dg.llvmType(aligned_field.ty); + + //const llvm_payload_ty = ty: { + // if (layout.most_aligned_field_size == layout.payload_size) { + // break :ty llvm_aligned_field_ty; + // } + // const padding_len = @intCast(c_uint, layout.payload_size - layout.most_aligned_field_size); + // const fields: [2]*const llvm.Type = .{ + // llvm_aligned_field_ty, + // dg.context.intType(8).arrayType(padding_len), + // }; + // break :ty dg.context.structType(&fields, fields.len, .True); + //}; + + //if (layout.tag_size == 0) { + // var llvm_fields: [1]*const llvm.Type = .{llvm_payload_ty}; + // llvm_union_ty.structSetBody(&llvm_fields, llvm_fields.len, .False); + // return llvm_union_ty; + //} + //const enum_tag_llvm_ty = try dg.llvmType(union_obj.tag_ty); + + //// Put the tag before or after the payload depending on which one's + //// alignment is greater. + //var llvm_fields: [3]*const llvm.Type = undefined; + //var llvm_fields_len: c_uint = 2; + + //if (layout.tag_align >= layout.payload_align) { + // llvm_fields = .{ enum_tag_llvm_ty, llvm_payload_ty, undefined }; + //} else { + // llvm_fields = .{ llvm_payload_ty, enum_tag_llvm_ty, undefined }; + //} + + //// Insert padding to make the LLVM struct ABI size match the Zig union ABI size. + //if (layout.padding != 0) { + // llvm_fields[2] = dg.context.intType(8).arrayType(layout.padding); + // llvm_fields_len = 3; + //} + + //llvm_union_ty.structSetBody(&llvm_fields, llvm_fields_len, .False); + //return llvm_union_ty; + }, + .Fn => { + const fn_info = ty.fnInfo(); + const sret = firstParamSRet(fn_info, target); + + var param_di_types = std.ArrayList(*llvm.DIType).init(dg.gpa); + defer param_di_types.deinit(); + + // Return type goes first. + const di_ret_ty = if (sret) Type.void else fn_info.return_type; + try param_di_types.append(try dg.lowerDebugType(di_ret_ty)); + + if (sret) { + var ptr_ty_payload: Type.Payload.ElemType = .{ + .base = .{ .tag = .single_mut_pointer }, + .data = fn_info.return_type, + }; + const ptr_ty = Type.initPayload(&ptr_ty_payload.base); + try param_di_types.append(try dg.lowerDebugType(ptr_ty)); + } + + for (fn_info.param_types) |param_ty| { + if (!param_ty.hasRuntimeBits()) continue; + + if (isByRef(param_ty)) { + var ptr_ty_payload: Type.Payload.ElemType = .{ + .base = .{ .tag = .single_mut_pointer }, + .data = param_ty, + }; + const ptr_ty = Type.initPayload(&ptr_ty_payload.base); + try param_di_types.append(try dg.lowerDebugType(ptr_ty)); + } else { + try param_di_types.append(try dg.lowerDebugType(param_ty)); + } + } + + return dib.createSubroutineType( + param_di_types.items.ptr, + @intCast(c_int, param_di_types.items.len), + 0, + ); + }, + .ComptimeInt => unreachable, + .ComptimeFloat => unreachable, + .Type => unreachable, + .Undefined => unreachable, + .Null => unreachable, + .EnumLiteral => unreachable, + + .BoundFn => @panic("TODO remove BoundFn from the language"), + + .Frame => @panic("TODO implement lowerDebugType for Frame types"), + .AnyFrame => @panic("TODO implement lowerDebugType for AnyFrame types"), + } + } + const ParentPtr = struct { ty: Type, llvm_ptr: *const llvm.Value, @@ -2141,6 +2616,8 @@ pub const FuncGen = struct { liveness: Liveness, context: *const llvm.Context, builder: *const llvm.Builder, + di_scope: ?*llvm.DIScope, + di_file: ?*llvm.DIFile, /// This stores the LLVM values used in a function, such that they can be referred to /// in other instructions. This table is cleared before every function is generated. @@ -2156,7 +2633,7 @@ pub const FuncGen = struct { /// it omits 0-bit types. If the function uses sret as the first parameter, /// this slice does not include it. args: []const *const llvm.Value, - arg_index: usize, + arg_index: c_uint, llvm_func: *const llvm.Value, @@ -2386,10 +2863,7 @@ pub const FuncGen = struct { .constant => unreachable, .const_ty => unreachable, .unreach => self.airUnreach(inst), - .dbg_stmt => blk: { - // TODO: implement debug info - break :blk null; - }, + .dbg_stmt => self.airDbgStmt(inst), // zig fmt: on }; if (opt_value) |val| { @@ -3099,6 +3573,17 @@ pub const FuncGen = struct { return null; } + fn airDbgStmt(self: *FuncGen, inst: Air.Inst.Index) ?*const llvm.Value { + const di_scope = self.di_scope orelse return null; + const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt; + self.builder.setCurrentDebugLocation( + @intCast(c_int, dbg_stmt.line + 1), + @intCast(c_int, dbg_stmt.column + 1), + di_scope, + ); + return null; + } + fn airAssembly(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { // Eventually, the Zig compiler needs to be reworked to have inline assembly go // through the same parsing code regardless of backend, and have LLVM-flavored @@ -4299,15 +4784,31 @@ pub const FuncGen = struct { self.arg_index += 1; const inst_ty = self.air.typeOfIndex(inst); - if (isByRef(inst_ty)) { - // TODO declare debug variable - return arg_val; - } else { - const ptr_val = self.buildAlloca(try self.dg.llvmType(inst_ty)); - _ = self.builder.buildStore(arg_val, ptr_val); - // TODO declare debug variable - return arg_val; + const result = r: { + if (isByRef(inst_ty)) { + break :r arg_val; + } else { + const ptr_val = self.buildAlloca(try self.dg.llvmType(inst_ty)); + _ = self.builder.buildStore(arg_val, ptr_val); + break :r arg_val; + } + }; + + if (self.dg.object.di_builder) |dib| { + const func = self.dg.decl.getFunction().?; + _ = dib.createParameterVariable( + self.di_scope.?, + func.getParamName(self.arg_index - 1).ptr, // TODO test 0 bit args + self.di_file.?, + func.owner_decl.src_line + func.lbrace_line + 1, + try self.dg.lowerDebugType(inst_ty), + true, // always preserve + 0, // flags + self.arg_index, // includes +1 because 0 is return type + ); } + + return result; } fn airAlloc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { @@ -4830,7 +5331,7 @@ pub const FuncGen = struct { const prev_debug_location = self.builder.getCurrentDebugLocation2(); defer { self.builder.positionBuilderAtEnd(prev_block); - if (!self.dg.module.comp.bin_file.options.strip) { + if (self.di_scope != null) { self.builder.setCurrentDebugLocation2(prev_debug_location); } } diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 35fd95feef6b..f20fe86a38c2 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -193,6 +193,9 @@ pub const Value = opaque { pub const setValueName2 = LLVMSetValueName2; extern fn LLVMSetValueName2(Val: *const Value, Name: [*]const u8, NameLen: usize) void; + pub const getValueName = LLVMGetValueName; + extern fn LLVMGetValueName(Val: *const Value) [*:0]const u8; + pub const takeName = ZigLLVMTakeName; extern fn ZigLLVMTakeName(new_owner: *const Value, victim: *const Value) void; @@ -836,7 +839,7 @@ pub const Builder = opaque { pub const buildExactSDiv = LLVMBuildExactSDiv; extern fn LLVMBuildExactSDiv(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; - pub const zigSetCurrentDebugLocation = ZigLLVMSetCurrentDebugLocation; + pub const setCurrentDebugLocation = ZigLLVMSetCurrentDebugLocation; extern fn ZigLLVMSetCurrentDebugLocation(builder: *const Builder, line: c_int, column: c_int, scope: *DIScope) void; pub const clearCurrentDebugLocation = ZigLLVMClearCurrentDebugLocation; @@ -1505,16 +1508,16 @@ pub const DIBuilder = opaque { dib: *DIBuilder, scope: *DIScope, name: [*:0]const u8, - file: *DIFile, + file: ?*DIFile, line_number: c_uint, size_in_bits: u64, align_in_bits: u64, flags: c_uint, - derived_from: *DIType, + derived_from: ?*DIType, types_array: [*]const *DIType, types_array_len: c_int, run_time_lang: c_uint, - vtable_holder: *DIType, + vtable_holder: ?*DIType, unique_id: [*:0]const u8, ) *DIType; @@ -1539,7 +1542,7 @@ pub const DIBuilder = opaque { dib: *DIBuilder, scope: *DIScope, name: [*:0]const u8, - file: *DIFile, + file: ?*DIFile, line: c_uint, size_in_bits: u64, align_in_bits: u64, @@ -1554,7 +1557,7 @@ pub const DIBuilder = opaque { tag: c_uint, name: [*:0]const u8, scope: *DIScope, - file: *DIFile, + file: ?*DIFile, line: c_uint, ) *DIType; @@ -1668,7 +1671,7 @@ pub const DIBuilder = opaque { scope_line: c_uint, flags: c_uint, is_optimized: bool, - decl_subprogram: *DISubprogram, + decl_subprogram: ?*DISubprogram, ) *DISubprogram; pub const createVectorType = ZigLLVMDIBuilderCreateVectorType; @@ -1698,3 +1701,41 @@ pub const DIBuilder = opaque { insert_before_instr: *const Value, ) *const Value; }; + +pub const DIFlags = opaque { + pub const Zero = 0; + pub const Private = 1; + pub const Protected = 2; + pub const Public = 3; + + pub const FwdDecl = 1 << 2; + pub const AppleBlock = 1 << 3; + pub const BlockByrefStruct = 1 << 4; + pub const Virtual = 1 << 5; + pub const Artificial = 1 << 6; + pub const Explicit = 1 << 7; + pub const Prototyped = 1 << 8; + pub const ObjcClassComplete = 1 << 9; + pub const ObjectPointer = 1 << 10; + pub const Vector = 1 << 11; + pub const StaticMember = 1 << 12; + pub const LValueReference = 1 << 13; + pub const RValueReference = 1 << 14; + pub const Reserved = 1 << 15; + + pub const SingleInheritance = 1 << 16; + pub const MultipleInheritance = 2 << 16; + pub const VirtualInheritance = 3 << 16; + + pub const IntroducedVirtual = 1 << 18; + pub const BitField = 1 << 19; + pub const NoReturn = 1 << 20; + pub const TypePassByValue = 1 << 22; + pub const TypePassByReference = 1 << 23; + pub const EnumClass = 1 << 24; + pub const Thunk = 1 << 25; + pub const NonTrivial = 1 << 26; + pub const BigEndian = 1 << 27; + pub const LittleEndian = 1 << 28; + pub const AllCallsDescribed = 1 << 29; +}; From 40c0bdd3850c0df22a98ad9293d36091d76210c4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 7 Mar 2022 21:10:23 -0700 Subject: [PATCH 04/11] LLVM: memoize debug types and add enum debug types --- src/codegen/llvm.zig | 132 ++++++++++++++++++++++++++++++++++------- src/stage1/analyze.cpp | 3 +- 2 files changed, 113 insertions(+), 22 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 30b87ceed9b6..310293462066 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -188,6 +188,7 @@ pub const Object = struct { /// The backing memory for `type_map`. Periodically garbage collected after flush(). /// The code for doing the periodical GC is not yet implemented. type_map_arena: std.heap.ArenaAllocator, + di_type_map: DITypeMap, /// The LLVM global table which holds the names corresponding to Zig errors. /// Note that the values are not added until flushModule, when all errors in /// the compilation are known. @@ -200,6 +201,13 @@ pub const Object = struct { std.hash_map.default_max_load_percentage, ); + pub const DITypeMap = std.HashMapUnmanaged( + Type, + *llvm.DIType, + Type.HashContext64, + std.hash_map.default_max_load_percentage, + ); + pub fn create(gpa: Allocator, options: link.Options) !*Object { const obj = try gpa.create(Object); errdefer gpa.destroy(obj); @@ -336,6 +344,7 @@ pub const Object = struct { .decl_map = .{}, .type_map = .{}, .type_map_arena = std.heap.ArenaAllocator.init(gpa), + .di_type_map = .{}, .error_name_table = null, }; } @@ -344,6 +353,7 @@ pub const Object = struct { if (self.di_builder) |dib| { dib.dispose(); self.di_map.deinit(gpa); + self.di_type_map.deinit(gpa); } self.target_data.dispose(); self.target_machine.dispose(); @@ -558,19 +568,7 @@ pub const Object = struct { var di_scope: ?*llvm.DIScope = null; if (dg.object.di_builder) |dib| { - di_file = s: { - const file = decl.src_namespace.file_scope; - const gop = try dg.object.di_map.getOrPut(gpa, file); - if (!gop.found_existing) { - const dir_path = file.pkg.root_src_directory.path orelse "."; - const sub_file_path_z = try gpa.dupeZ(u8, file.sub_file_path); - defer gpa.free(sub_file_path_z); - const dir_path_z = try gpa.dupeZ(u8, dir_path); - defer gpa.free(dir_path_z); - gop.value_ptr.* = dib.createFile(sub_file_path_z, dir_path_z).toScope(); - } - break :s @ptrCast(*llvm.DIFile, gop.value_ptr.*); - }; + di_file = try dg.object.getDIFile(gpa, decl.src_namespace.file_scope); const line_number = decl.src_line + 1; const is_internal_linkage = decl.val.tag() != .extern_fn and @@ -718,6 +716,22 @@ pub const Object = struct { const llvm_value = self.decl_map.get(decl) orelse return; llvm_value.deleteGlobal(); } + + fn getDIFile(o: *Object, gpa: Allocator, file: *const Module.File) !*llvm.DIFile { + const gop = try o.di_map.getOrPut(gpa, file); + errdefer assert(o.di_map.remove(file)); + if (gop.found_existing) { + return @ptrCast(*llvm.DIFile, gop.value_ptr.*); + } + const dir_path = file.pkg.root_src_directory.path orelse "."; + const sub_file_path_z = try gpa.dupeZ(u8, file.sub_file_path); + defer gpa.free(sub_file_path_z); + const dir_path_z = try gpa.dupeZ(u8, dir_path); + defer gpa.free(dir_path_z); + const di_file = o.di_builder.?.createFile(sub_file_path_z, dir_path_z); + gop.value_ptr.* = di_file.toScope(); + return di_file; + } }; pub const DeclGen = struct { @@ -1891,9 +1905,18 @@ pub const DeclGen = struct { } fn lowerDebugType(dg: *DeclGen, ty: Type) Allocator.Error!*llvm.DIType { - const gpa = dg.gpa; + const gop = try dg.object.di_type_map.getOrPut(dg.gpa, ty); + errdefer assert(dg.object.di_type_map.remove(ty)); + if (!gop.found_existing) { + gop.value_ptr.* = try lowerDebugTypeRaw(dg, ty); + } + return gop.value_ptr.*; + } + + fn lowerDebugTypeRaw(dg: *DeclGen, ty: Type) Allocator.Error!*llvm.DIType { const target = dg.module.getTarget(); const dib = dg.object.di_builder.?; + const gpa = dg.gpa; switch (ty.zigTypeTag()) { .Void, .NoReturn => return dib.createBasicType("void", 0, DW.ATE.signed), .Int => { @@ -1907,12 +1930,54 @@ pub const DeclGen = struct { return dib.createBasicType(name, info.bits, dwarf_encoding); }, .Enum => { - @panic("TODO debug info type for enums"); - //var buffer: Type.Payload.Bits = undefined; - //const int_ty = ty.intTagType(&buffer); - //const bit_count = int_ty.intInfo(target).bits; - //assert(bit_count != 0); - //return dg.context.intType(bit_count); + const owner_decl = ty.getOwnerDecl(); + + if (!ty.hasRuntimeBits()) { + return dg.makeEmptyNamespaceDIType(owner_decl); + } + + const field_names = ty.enumFields().keys(); + + const enumerators = try gpa.alloc(*llvm.DIEnumerator, field_names.len); + defer gpa.free(enumerators); + + var buf_field_index: Value.Payload.U32 = .{ + .base = .{ .tag = .enum_field_index }, + .data = undefined, + }; + const field_index_val = Value.initPayload(&buf_field_index.base); + + for (field_names) |field_name, i| { + const field_name_z = try gpa.dupeZ(u8, field_name); + defer gpa.free(field_name_z); + + buf_field_index.data = @intCast(u32, i); + var buf_u64: Value.Payload.U64 = undefined; + const field_int_val = field_index_val.enumToInt(ty, &buf_u64); + // See https://github.com/ziglang/zig/issues/645 + const field_int = field_int_val.toSignedInt(); + enumerators[i] = dib.createEnumerator(field_name_z, field_int); + } + + const di_file = try dg.object.getDIFile(gpa, owner_decl.src_namespace.file_scope); + const di_scope = try dg.namespaceToDebugScope(owner_decl.src_namespace); + + const name = try ty.nameAlloc(gpa); // TODO this is a leak + var buffer: Type.Payload.Bits = undefined; + const int_ty = ty.intTagType(&buffer); + + return dib.createEnumerationType( + di_scope, + name, + di_file, + owner_decl.src_node + 1, + ty.abiSize(target) * 8, + ty.abiAlignment(target) * 8, + enumerators.ptr, + @intCast(c_int, enumerators.len), + try lowerDebugType(dg, int_ty), + "", + ); }, .Float => { const bits = ty.floatBits(target); @@ -2302,6 +2367,33 @@ pub const DeclGen = struct { } } + fn namespaceToDebugScope(dg: *DeclGen, namespace: *const Module.Namespace) !*llvm.DIScope { + const di_type = try dg.lowerDebugType(namespace.ty); + return di_type.toScope(); + } + + /// This is to be used instead of void for debug info types, to avoid tripping + /// Assertion `!isa(Scope) && "shouldn't make a namespace scope for a type"' + /// when targeting CodeView (Windows). + fn makeEmptyNamespaceDIType(dg: *DeclGen, decl: *const Module.Decl) !*llvm.DIType { + const fields: [0]*llvm.DIType = .{}; + return dg.object.di_builder.?.createStructType( + try dg.namespaceToDebugScope(decl.src_namespace), + decl.name, // TODO use fully qualified name + try dg.object.getDIFile(dg.gpa, decl.src_namespace.file_scope), + decl.src_line + 1, + 0, // size in bits + 0, // align in bits + 0, // flags + null, // derived from + undefined, // TODO should be able to pass &fields, + fields.len, + 0, // run time lang + null, // vtable holder + "", // unique id + ); + } + const ParentPtr = struct { ty: Type, llvm_ptr: *const llvm.Value, diff --git a/src/stage1/analyze.cpp b/src/stage1/analyze.cpp index ff925f265fab..a2db15c62280 100644 --- a/src/stage1/analyze.cpp +++ b/src/stage1/analyze.cpp @@ -9073,8 +9073,7 @@ static void resolve_llvm_types_enum(CodeGen *g, ZigType *enum_type, ResolveStatu for (uint32_t i = 0; i < field_count; i += 1) { TypeEnumField *enum_field = &enum_type->data.enumeration.fields[i]; - // TODO send patch to LLVM to support APInt in createEnumerator instead of int64_t - // http://lists.llvm.org/pipermail/llvm-dev/2017-December/119456.html + // https://github.com/ziglang/zig/issues/645 di_enumerators[i] = ZigLLVMCreateDebugEnumerator(g->dbuilder, buf_ptr(enum_field->name), bigint_as_signed(&enum_field->value)); } From 3654ec87707be408cc3bfa9ae7e59f95c4fdf7b3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 7 Mar 2022 22:14:22 -0700 Subject: [PATCH 05/11] LLVM: add debug type lowering for ptr, slice, opaque, optional also fix issue with memoization and recursiveness. --- src/codegen/llvm.zig | 311 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 252 insertions(+), 59 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 310293462066..62b38d217358 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1075,15 +1075,13 @@ pub const DeclGen = struct { }, .Optional => { var buf: Type.Payload.ElemType = undefined; - const child_type = t.optionalChild(&buf); - if (!child_type.hasRuntimeBits()) { + const child_ty = t.optionalChild(&buf); + if (!child_ty.hasRuntimeBits()) { return dg.context.intType(1); } - const payload_llvm_ty = try dg.llvmType(child_type); + const payload_llvm_ty = try dg.llvmType(child_ty); if (t.isPtrLikeOptional()) { return payload_llvm_ty; - } else if (!child_type.hasRuntimeBits()) { - return dg.context.intType(1); } const fields: [2]*const llvm.Type = .{ @@ -1905,20 +1903,19 @@ pub const DeclGen = struct { } fn lowerDebugType(dg: *DeclGen, ty: Type) Allocator.Error!*llvm.DIType { - const gop = try dg.object.di_type_map.getOrPut(dg.gpa, ty); + const gpa = dg.gpa; + // Be careful not to reference this `gop` variable after any recursive calls + // to `lowerDebugType`. + const gop = try dg.object.di_type_map.getOrPut(gpa, ty); + if (gop.found_existing) return gop.value_ptr.*; errdefer assert(dg.object.di_type_map.remove(ty)); - if (!gop.found_existing) { - gop.value_ptr.* = try lowerDebugTypeRaw(dg, ty); - } - return gop.value_ptr.*; - } - - fn lowerDebugTypeRaw(dg: *DeclGen, ty: Type) Allocator.Error!*llvm.DIType { const target = dg.module.getTarget(); const dib = dg.object.di_builder.?; - const gpa = dg.gpa; switch (ty.zigTypeTag()) { - .Void, .NoReturn => return dib.createBasicType("void", 0, DW.ATE.signed), + .Void, .NoReturn => { + gop.value_ptr.* = dib.createBasicType("void", 0, DW.ATE.signed); + return gop.value_ptr.*; + }, .Int => { const info = ty.intInfo(target); assert(info.bits != 0); @@ -1927,13 +1924,18 @@ pub const DeclGen = struct { .signed => DW.ATE.signed, .unsigned => DW.ATE.unsigned, }; - return dib.createBasicType(name, info.bits, dwarf_encoding); + gop.value_ptr.* = dib.createBasicType(name, info.bits, dwarf_encoding); + return gop.value_ptr.*; }, .Enum => { const owner_decl = ty.getOwnerDecl(); if (!ty.hasRuntimeBits()) { - return dg.makeEmptyNamespaceDIType(owner_decl); + const enum_di_ty = try dg.makeEmptyNamespaceDIType(owner_decl); + // The recursive call to `lowerDebugType` via `makeEmptyNamespaceDIType` + // means we can't use `gop` anymore. + try dg.object.di_type_map.put(gpa, ty, enum_di_ty); + return enum_di_ty; } const field_names = ty.enumFields().keys(); @@ -1966,7 +1968,7 @@ pub const DeclGen = struct { var buffer: Type.Payload.Bits = undefined; const int_ty = ty.intTagType(&buffer); - return dib.createEnumerationType( + const enum_di_ty = dib.createEnumerationType( di_scope, name, di_file, @@ -1978,79 +1980,264 @@ pub const DeclGen = struct { try lowerDebugType(dg, int_ty), "", ); + // The recursive call to `lowerDebugType` means we can't use `gop` anymore. + try dg.object.di_type_map.put(gpa, ty, enum_di_ty); + return enum_di_ty; }, .Float => { const bits = ty.floatBits(target); const name = try ty.nameAlloc(gpa); // TODO this is a leak - return dib.createBasicType(name, bits, DW.ATE.float); + gop.value_ptr.* = dib.createBasicType(name, bits, DW.ATE.float); + return gop.value_ptr.*; + }, + .Bool => { + gop.value_ptr.* = dib.createBasicType("bool", 1, DW.ATE.boolean); + return gop.value_ptr.*; }, - .Bool => return dib.createBasicType("bool", 1, DW.ATE.boolean), .Pointer => { + // Normalize everything that the debug info does not represent. + const ptr_info = ty.ptrInfo().data; + + if (ptr_info.sentinel != null or + ptr_info.@"addrspace" != .generic or + ptr_info.bit_offset != 0 or + ptr_info.host_size != 0 or + ptr_info.@"allowzero" or + !ptr_info.mutable or + ptr_info.@"volatile" or + ptr_info.size == .Many or ptr_info.size == .C) + { + var payload: Type.Payload.Pointer = .{ + .data = .{ + .pointee_type = ptr_info.pointee_type, + .sentinel = null, + .@"align" = ptr_info.@"align", + .@"addrspace" = .generic, + .bit_offset = 0, + .host_size = 0, + .@"allowzero" = false, + .mutable = true, + .@"volatile" = false, + .size = switch (ptr_info.size) { + .Many, .C, .One => .One, + .Slice => .Slice, + }, + }, + }; + const bland_ptr_ty = Type.initPayload(&payload.base); + const ptr_di_ty = try dg.lowerDebugType(bland_ptr_ty); + // The recursive call to `lowerDebugType` means we can't use `gop` anymore. + try dg.object.di_type_map.put(gpa, ty, ptr_di_ty); + return ptr_di_ty; + } + if (ty.isSlice()) { - @panic("TODO debug info type for slices"); - //var buf: Type.SlicePtrFieldTypeBuffer = undefined; - //const ptr_type = ty.slicePtrFieldType(&buf); - - //const fields: [2]*const llvm.Type = .{ - // try dg.llvmType(ptr_type), - // try dg.llvmType(Type.usize), - //}; - //return dg.context.structType(&fields, fields.len, .False); + var buf: Type.SlicePtrFieldTypeBuffer = undefined; + const ptr_ty = ty.slicePtrFieldType(&buf); + const len_ty = Type.usize; + + const name = try ty.nameAlloc(gpa); // TODO this is a leak + const di_file: ?*llvm.DIFile = null; + const line = 0; + const compile_unit_scope = dg.object.di_compile_unit.?.toScope(); + const fwd_decl = dib.createReplaceableCompositeType( + DW.TAG.structure_type, + name.ptr, + compile_unit_scope, + di_file, + line, + ); + gop.value_ptr.* = fwd_decl; + + const ptr_size = ptr_ty.abiSize(target); + const ptr_align = ptr_ty.abiAlignment(target); + const len_size = len_ty.abiSize(target); + const len_align = len_ty.abiAlignment(target); + + var offset: u64 = 0; + offset += ptr_size; + offset = std.mem.alignForwardGeneric(u64, offset, len_align); + const len_offset = offset; + + const fields: [2]*llvm.DIType = .{ + dib.createMemberType( + fwd_decl.toScope(), + "ptr", + di_file, + line, + ptr_size * 8, // size in bits + ptr_align * 8, // align in bits + 0, // offset in bits + 0, // flags + try dg.lowerDebugType(ptr_ty), + ), + dib.createMemberType( + fwd_decl.toScope(), + "len", + di_file, + line, + len_size * 8, // size in bits + len_align * 8, // align in bits + len_offset * 8, // offset in bits + 0, // flags + try dg.lowerDebugType(len_ty), + ), + }; + + const replacement_di_type = dib.createStructType( + compile_unit_scope, + name.ptr, + di_file, + line, + ty.abiSize(target) * 8, // size in bits + ty.abiAlignment(target) * 8, // align in bits + 0, // flags + null, // derived from + &fields, + fields.len, + 0, // run time lang + null, // vtable holder + "", // unique id + ); + dib.replaceTemporary(fwd_decl, replacement_di_type); + // The recursive call to `lowerDebugType` means we can't use `gop` anymore. + try dg.object.di_type_map.put(gpa, ty, replacement_di_type); + return replacement_di_type; } - const ptr_info = ty.ptrInfo().data; const elem_di_ty = try lowerDebugType(dg, ptr_info.pointee_type); const name = try ty.nameAlloc(gpa); // TODO this is a leak - return dib.createPointerType( + const ptr_di_ty = dib.createPointerType( elem_di_ty, target.cpu.arch.ptrBitWidth(), ty.ptrAlignment(target) * 8, name, ); + // The recursive call to `lowerDebugType` means we can't use `gop` anymore. + try dg.object.di_type_map.put(gpa, ty, ptr_di_ty); + return ptr_di_ty; }, .Opaque => { - @panic("TODO debug info type for opaque"); + const name = try ty.nameAlloc(gpa); // TODO this is a leak + const owner_decl = ty.getOwnerDecl(); + const opaque_di_ty = dib.createForwardDeclType( + DW.TAG.structure_type, + name, + try dg.namespaceToDebugScope(owner_decl.src_namespace), + try dg.object.getDIFile(gpa, owner_decl.src_namespace.file_scope), + owner_decl.src_node + 1, + ); + // The recursive call to `lowerDebugType` va `namespaceToDebugScope` + // means we can't use `gop` anymore. + try dg.object.di_type_map.put(gpa, ty, opaque_di_ty); + return opaque_di_ty; }, .Array => { - const elem_di_ty = try lowerDebugType(dg, ty.childType()); - return dib.createArrayType( + const array_di_ty = dib.createArrayType( ty.abiSize(target) * 8, ty.abiAlignment(target) * 8, - elem_di_ty, + try lowerDebugType(dg, ty.childType()), @intCast(c_int, ty.arrayLen()), ); + // The recursive call to `lowerDebugType` means we can't use `gop` anymore. + try dg.object.di_type_map.put(gpa, ty, array_di_ty); + return array_di_ty; }, .Vector => { @panic("TODO debug info type for vector"); }, .Optional => { - @panic("TODO debug info type for optional"); - //var buf: Type.Payload.ElemType = undefined; - //const child_type = ty.optionalChild(&buf); - //if (!child_type.hasRuntimeBits()) { - // return dg.context.intType(1); - //} - //const payload_llvm_ty = try dg.llvmType(child_type); - //if (ty.isPtrLikeOptional()) { - // return payload_llvm_ty; - //} else if (!child_type.hasRuntimeBits()) { - // return dg.context.intType(1); - //} + const name = try ty.nameAlloc(gpa); // TODO this is a leak + var buf: Type.Payload.ElemType = undefined; + const child_ty = ty.optionalChild(&buf); + if (!child_ty.hasRuntimeBits()) { + gop.value_ptr.* = dib.createBasicType(name, 1, DW.ATE.boolean); + return gop.value_ptr.*; + } + if (ty.isPtrLikeOptional()) { + const ptr_di_ty = try dg.lowerDebugType(child_ty); + // The recursive call to `lowerDebugType` means we can't use `gop` anymore. + try dg.object.di_type_map.put(gpa, ty, ptr_di_ty); + return ptr_di_ty; + } - //const fields: [2]*const llvm.Type = .{ - // payload_llvm_ty, dg.context.intType(1), - //}; - //return dg.context.structType(&fields, fields.len, .False); + const di_file: ?*llvm.DIFile = null; + const line = 0; + const compile_unit_scope = dg.object.di_compile_unit.?.toScope(); + const fwd_decl = dib.createReplaceableCompositeType( + DW.TAG.structure_type, + name.ptr, + compile_unit_scope, + di_file, + line, + ); + gop.value_ptr.* = fwd_decl; + + const non_null_ty = Type.bool; + const payload_size = child_ty.abiSize(target); + const payload_align = child_ty.abiAlignment(target); + const non_null_size = non_null_ty.abiSize(target); + const non_null_align = non_null_ty.abiAlignment(target); + + var offset: u64 = 0; + offset += payload_size; + offset = std.mem.alignForwardGeneric(u64, offset, non_null_align); + const non_null_offset = offset; + + const fields: [2]*llvm.DIType = .{ + dib.createMemberType( + fwd_decl.toScope(), + "data", + di_file, + line, + payload_size * 8, // size in bits + payload_align * 8, // align in bits + 0, // offset in bits + 0, // flags + try dg.lowerDebugType(child_ty), + ), + dib.createMemberType( + fwd_decl.toScope(), + "some", + di_file, + line, + non_null_size * 8, // size in bits + non_null_align * 8, // align in bits + non_null_offset * 8, // offset in bits + 0, // flags + try dg.lowerDebugType(non_null_ty), + ), + }; + + const replacement_di_type = dib.createStructType( + compile_unit_scope, + name.ptr, + di_file, + line, + ty.abiSize(target) * 8, // size in bits + ty.abiAlignment(target) * 8, // align in bits + 0, // flags + null, // derived from + &fields, + fields.len, + 0, // run time lang + null, // vtable holder + "", // unique id + ); + dib.replaceTemporary(fwd_decl, replacement_di_type); + // The recursive call to `lowerDebugType` means we can't use `gop` anymore. + try dg.object.di_type_map.put(gpa, ty, replacement_di_type); + return replacement_di_type; }, .ErrorUnion => { const err_set_ty = ty.errorUnionSet(); - const err_set_di_ty = try dg.lowerDebugType(err_set_ty); const payload_ty = ty.errorUnionPayload(); if (!payload_ty.hasRuntimeBits()) { + const err_set_di_ty = try dg.lowerDebugType(err_set_ty); + // The recursive call to `lowerDebugType` means we can't use `gop` anymore. + try dg.object.di_type_map.put(gpa, ty, err_set_di_ty); return err_set_di_ty; } - const payload_di_ty = try dg.lowerDebugType(payload_ty); - const name = try ty.nameAlloc(gpa); // TODO this is a leak const di_file: ?*llvm.DIFile = null; const line = 0; @@ -2062,6 +2249,7 @@ pub const DeclGen = struct { di_file, line, ); + gop.value_ptr.* = fwd_decl; const err_set_size = err_set_ty.abiSize(target); const err_set_align = err_set_ty.abiAlignment(target); @@ -2083,7 +2271,7 @@ pub const DeclGen = struct { err_set_align * 8, // align in bits 0, // offset in bits 0, // flags - err_set_di_ty, + try dg.lowerDebugType(err_set_ty), ), dib.createMemberType( fwd_decl.toScope(), @@ -2094,7 +2282,7 @@ pub const DeclGen = struct { payload_align * 8, // align in bits payload_offset * 8, // offset in bits 0, // flags - payload_di_ty, + try dg.lowerDebugType(payload_ty), ), }; @@ -2114,13 +2302,15 @@ pub const DeclGen = struct { "", // unique id ); dib.replaceTemporary(fwd_decl, replacement_di_type); - + // The recursive call to `lowerDebugType` means we can't use `gop` anymore. + try dg.object.di_type_map.put(gpa, ty, replacement_di_type); return replacement_di_type; }, .ErrorSet => { // TODO make this a proper enum with all the error codes in it. // will need to consider how to take incremental compilation into account. - return dib.createBasicType("anyerror", 16, DW.ATE.unsigned); + gop.value_ptr.* = dib.createBasicType("anyerror", 16, DW.ATE.unsigned); + return gop.value_ptr.*; }, .Struct => { @panic("TODO debug info type for struct"); @@ -2347,11 +2537,14 @@ pub const DeclGen = struct { } } - return dib.createSubroutineType( + const fn_di_ty = dib.createSubroutineType( param_di_types.items.ptr, @intCast(c_int, param_di_types.items.len), 0, ); + // The recursive call to `lowerDebugType` means we can't use `gop` anymore. + try dg.object.di_type_map.put(gpa, ty, fn_di_ty); + return fn_di_ty; }, .ComptimeInt => unreachable, .ComptimeFloat => unreachable, From ba566eed76bbfd57c6f4b410435067f3a6d7fd5d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 7 Mar 2022 23:03:45 -0700 Subject: [PATCH 06/11] LLVM: fix not handling dbg_stmt relative line Also make `namespaceToDebugScope` behave correctly for file-level structs. Instead of being inside their own scope, they use the file scope. --- src/codegen/llvm.zig | 50 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 62b38d217358..6c386e7abfbd 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -2313,7 +2313,29 @@ pub const DeclGen = struct { return gop.value_ptr.*; }, .Struct => { + const owner_decl = ty.getOwnerDecl(); + + const name = try ty.nameAlloc(gpa); // TODO this is a leak + const fwd_decl = dib.createReplaceableCompositeType( + DW.TAG.structure_type, + name.ptr, + dg.object.di_compile_unit.?.toScope(), + null, // file + 0, // line + ); + gop.value_ptr.* = fwd_decl; + + const TODO_implement_this = true; // TODO + if (TODO_implement_this or !ty.hasRuntimeBits()) { + const struct_di_ty = try dg.makeEmptyNamespaceDIType(owner_decl); + dib.replaceTemporary(fwd_decl, struct_di_ty); + // The recursive call to `lowerDebugType` via `makeEmptyNamespaceDIType` + // means we can't use `gop` anymore. + try dg.object.di_type_map.put(gpa, ty, struct_di_ty); + return struct_di_ty; + } @panic("TODO debug info type for struct"); + //const gop = try dg.object.type_map.getOrPut(gpa, ty); //if (gop.found_existing) return gop.value_ptr.*; @@ -2437,6 +2459,28 @@ pub const DeclGen = struct { //return llvm_struct_ty; }, .Union => { + const owner_decl = ty.getOwnerDecl(); + + const name = try ty.nameAlloc(gpa); // TODO this is a leak + const fwd_decl = dib.createReplaceableCompositeType( + DW.TAG.structure_type, + name.ptr, + dg.object.di_compile_unit.?.toScope(), + null, // file + 0, // line + ); + gop.value_ptr.* = fwd_decl; + + const TODO_implement_this = true; // TODO + if (TODO_implement_this or !ty.hasRuntimeBits()) { + const union_di_ty = try dg.makeEmptyNamespaceDIType(owner_decl); + dib.replaceTemporary(fwd_decl, union_di_ty); + // The recursive call to `lowerDebugType` via `makeEmptyNamespaceDIType` + // means we can't use `gop` anymore. + try dg.object.di_type_map.put(gpa, ty, union_di_ty); + return union_di_ty; + } + @panic("TODO debug info type for union"); //const gop = try dg.object.type_map.getOrPut(gpa, ty); //if (gop.found_existing) return gop.value_ptr.*; @@ -2561,6 +2605,10 @@ pub const DeclGen = struct { } fn namespaceToDebugScope(dg: *DeclGen, namespace: *const Module.Namespace) !*llvm.DIScope { + if (namespace.parent == null) { + const di_file = try dg.object.getDIFile(dg.gpa, namespace.file_scope); + return di_file.toScope(); + } const di_type = try dg.lowerDebugType(namespace.ty); return di_type.toScope(); } @@ -3862,7 +3910,7 @@ pub const FuncGen = struct { const di_scope = self.di_scope orelse return null; const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt; self.builder.setCurrentDebugLocation( - @intCast(c_int, dbg_stmt.line + 1), + @intCast(c_int, self.dg.decl.src_line + dbg_stmt.line + 1), @intCast(c_int, dbg_stmt.column + 1), di_scope, ); From 7cea8f063f710d5fb2fb10723c3e6485b3e2b31d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 8 Mar 2022 12:48:00 -0700 Subject: [PATCH 07/11] LLVM: add debug info for parameters --- src/codegen/llvm.zig | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 6c386e7abfbd..ee1cdd61413f 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -5117,31 +5117,48 @@ pub const FuncGen = struct { self.arg_index += 1; const inst_ty = self.air.typeOfIndex(inst); - const result = r: { + const result: struct { ptr: *const llvm.Value, operand: *const llvm.Value } = r: { if (isByRef(inst_ty)) { - break :r arg_val; + break :r .{ .ptr = arg_val, .operand = arg_val }; } else { const ptr_val = self.buildAlloca(try self.dg.llvmType(inst_ty)); _ = self.builder.buildStore(arg_val, ptr_val); - break :r arg_val; + break :r .{ .ptr = ptr_val, .operand = arg_val }; } }; if (self.dg.object.di_builder) |dib| { + const src_index = self.getSrcArgIndex(self.arg_index - 1); const func = self.dg.decl.getFunction().?; - _ = dib.createParameterVariable( + const lbrace_line = func.owner_decl.src_line + func.lbrace_line + 1; + const lbrace_col = func.lbrace_column + 1; + const di_local_var = dib.createParameterVariable( self.di_scope.?, - func.getParamName(self.arg_index - 1).ptr, // TODO test 0 bit args + func.getParamName(src_index).ptr, // TODO test 0 bit args self.di_file.?, - func.owner_decl.src_line + func.lbrace_line + 1, + lbrace_line, try self.dg.lowerDebugType(inst_ty), true, // always preserve 0, // flags self.arg_index, // includes +1 because 0 is return type ); + + const debug_loc = llvm.getDebugLoc(lbrace_line, lbrace_col, self.di_scope.?); + const insert_block = self.builder.getInsertBlock(); + _ = dib.insertDeclareAtEnd(result.ptr, di_local_var, debug_loc, insert_block); } - return result; + return result.operand; + } + + fn getSrcArgIndex(self: *FuncGen, runtime_index: u32) u32 { + const fn_info = self.dg.decl.ty.fnInfo(); + var i: u32 = 0; + for (fn_info.param_types) |param_ty, src_index| { + if (!param_ty.hasRuntimeBits()) continue; + if (i == runtime_index) return @intCast(u32, src_index); + i += 1; + } else unreachable; } fn airAlloc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { From 1046ead0cc409287b88554a90c2f80718e1af46d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 8 Mar 2022 13:01:58 -0700 Subject: [PATCH 08/11] LLVM: no longer store args into alloca instructions Previously, we did this so that we could insert a debug variable declaration intrinsic on the alloca. But there is a dbg.value intrinsic for declaring variables that are values. --- src/codegen/llvm.zig | 18 ++++++------------ src/codegen/llvm/bindings.zig | 9 +++++++++ src/zig_llvm.cpp | 13 +++++++++++++ src/zig_llvm.h | 19 +++++++++++++------ 4 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index ee1cdd61413f..2466eea8a9cc 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -5117,16 +5117,6 @@ pub const FuncGen = struct { self.arg_index += 1; const inst_ty = self.air.typeOfIndex(inst); - const result: struct { ptr: *const llvm.Value, operand: *const llvm.Value } = r: { - if (isByRef(inst_ty)) { - break :r .{ .ptr = arg_val, .operand = arg_val }; - } else { - const ptr_val = self.buildAlloca(try self.dg.llvmType(inst_ty)); - _ = self.builder.buildStore(arg_val, ptr_val); - break :r .{ .ptr = ptr_val, .operand = arg_val }; - } - }; - if (self.dg.object.di_builder) |dib| { const src_index = self.getSrcArgIndex(self.arg_index - 1); const func = self.dg.decl.getFunction().?; @@ -5145,10 +5135,14 @@ pub const FuncGen = struct { const debug_loc = llvm.getDebugLoc(lbrace_line, lbrace_col, self.di_scope.?); const insert_block = self.builder.getInsertBlock(); - _ = dib.insertDeclareAtEnd(result.ptr, di_local_var, debug_loc, insert_block); + if (isByRef(inst_ty)) { + _ = dib.insertDeclareAtEnd(arg_val, di_local_var, debug_loc, insert_block); + } else { + _ = dib.insertDbgValueIntrinsicAtEnd(arg_val, di_local_var, debug_loc, insert_block); + } } - return result.operand; + return arg_val; } fn getSrcArgIndex(self: *FuncGen, runtime_index: u32) u32 { diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index f20fe86a38c2..f590e46c2300 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -1700,6 +1700,15 @@ pub const DIBuilder = opaque { debug_loc: *DILocation, insert_before_instr: *const Value, ) *const Value; + + pub const insertDbgValueIntrinsicAtEnd = ZigLLVMInsertDbgValueIntrinsicAtEnd; + extern fn ZigLLVMInsertDbgValueIntrinsicAtEnd( + dib: *DIBuilder, + val: *const Value, + var_info: *DILocalVariable, + debug_loc: *DILocation, + basic_block_ref: *const BasicBlock, + ) *const Value; }; pub const DIFlags = opaque { diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp index aa07a7fed10b..21e83319d9a7 100644 --- a/src/zig_llvm.cpp +++ b/src/zig_llvm.cpp @@ -942,6 +942,19 @@ LLVMValueRef ZigLLVMInsertDeclareAtEnd(ZigLLVMDIBuilder *dibuilder, LLVMValueRef return wrap(result); } +LLVMValueRef ZigLLVMInsertDbgValueIntrinsicAtEnd(ZigLLVMDIBuilder *dib, LLVMValueRef val, + ZigLLVMDILocalVariable *var_info, ZigLLVMDILocation *debug_loc, + LLVMBasicBlockRef basic_block_ref) +{ + Instruction *result = reinterpret_cast(dib)->insertDbgValueIntrinsic( + unwrap(val), + reinterpret_cast(var_info), + reinterpret_cast(dib)->createExpression(), + reinterpret_cast(debug_loc), + static_cast(unwrap(basic_block_ref))); + return wrap(result); +} + LLVMValueRef ZigLLVMInsertDeclare(ZigLLVMDIBuilder *dibuilder, LLVMValueRef storage, ZigLLVMDILocalVariable *var_info, ZigLLVMDILocation *debug_loc, LLVMValueRef insert_before_instr) { diff --git a/src/zig_llvm.h b/src/zig_llvm.h index 2b8156d51dd7..b19ff1f9479e 100644 --- a/src/zig_llvm.h +++ b/src/zig_llvm.h @@ -273,13 +273,20 @@ ZIG_EXTERN_C void ZigLLVMFnSetSubprogram(LLVMValueRef fn, struct ZigLLVMDISubpro ZIG_EXTERN_C void ZigLLVMDIBuilderFinalize(struct ZigLLVMDIBuilder *dibuilder); -ZIG_EXTERN_C LLVMValueRef ZigLLVMInsertDeclareAtEnd(struct ZigLLVMDIBuilder *dibuilder, LLVMValueRef storage, - struct ZigLLVMDILocalVariable *var_info, struct ZigLLVMDILocation *debug_loc, - LLVMBasicBlockRef basic_block_ref); +ZIG_EXTERN_C struct ZigLLVMDILocation *ZigLLVMGetDebugLoc(unsigned line, unsigned col, + struct ZigLLVMDIScope *scope); + +ZIG_EXTERN_C LLVMValueRef ZigLLVMInsertDeclareAtEnd(struct ZigLLVMDIBuilder *dib, + LLVMValueRef storage, struct ZigLLVMDILocalVariable *var_info, + struct ZigLLVMDILocation *debug_loc, LLVMBasicBlockRef basic_block_ref); + +ZIG_EXTERN_C LLVMValueRef ZigLLVMInsertDeclare(struct ZigLLVMDIBuilder *dib, + LLVMValueRef storage, struct ZigLLVMDILocalVariable *var_info, + struct ZigLLVMDILocation *debug_loc, LLVMValueRef insert_before_instr); -ZIG_EXTERN_C LLVMValueRef ZigLLVMInsertDeclare(struct ZigLLVMDIBuilder *dibuilder, LLVMValueRef storage, - struct ZigLLVMDILocalVariable *var_info, struct ZigLLVMDILocation *debug_loc, LLVMValueRef insert_before_instr); -ZIG_EXTERN_C struct ZigLLVMDILocation *ZigLLVMGetDebugLoc(unsigned line, unsigned col, struct ZigLLVMDIScope *scope); +ZIG_EXTERN_C LLVMValueRef ZigLLVMInsertDbgValueIntrinsicAtEnd(struct ZigLLVMDIBuilder *dib, + LLVMValueRef val, struct ZigLLVMDILocalVariable *var_info, + struct ZigLLVMDILocation *debug_loc, LLVMBasicBlockRef basic_block_ref); ZIG_EXTERN_C void ZigLLVMSetFastMath(LLVMBuilderRef builder_wrapped, bool on_state); ZIG_EXTERN_C void ZigLLVMSetTailCall(LLVMValueRef Call); From fa57335ec64931b2e2b0413fc19cb0b53648cc0b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 8 Mar 2022 14:24:05 -0700 Subject: [PATCH 09/11] stage2: implement Type.getOwnerDecl for opaque types --- src/type.zig | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/type.zig b/src/type.zig index 6c284f58d4b5..150ea234a049 100644 --- a/src/type.zig +++ b/src/type.zig @@ -4683,7 +4683,10 @@ pub const Type = extern union { const union_obj = ty.cast(Payload.Union).?.data; return union_obj.owner_decl; }, - .@"opaque" => @panic("TODO"), + .@"opaque" => { + const opaque_obj = ty.cast(Payload.Opaque).?.data; + return opaque_obj.owner_decl; + }, .atomic_order, .atomic_rmw_op, .calling_convention, @@ -4695,7 +4698,8 @@ pub const Type = extern union { .export_options, .extern_options, .type_info, - => @panic("TODO resolve std.builtin types"), + => unreachable, // These need to be resolved earlier. + else => unreachable, } } From 874b51d8d4d80f224e979adba11526a5dcec61da Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 8 Mar 2022 14:24:31 -0700 Subject: [PATCH 10/11] LLVM: add debug info for opaque, vector, and tuples Also fix UAF of Type memory in the di_type_map. --- src/codegen/llvm.zig | 171 ++++++++++++++++++++++++------------------- 1 file changed, 95 insertions(+), 76 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 2466eea8a9cc..bfe520e1b633 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1909,6 +1909,9 @@ pub const DeclGen = struct { const gop = try dg.object.di_type_map.getOrPut(gpa, ty); if (gop.found_existing) return gop.value_ptr.*; errdefer assert(dg.object.di_type_map.remove(ty)); + // The Type memory is ephemeral; since we want to store a longer-lived + // reference, we need to copy it here. + gop.key_ptr.* = try ty.copy(dg.object.type_map_arena.allocator()); const target = dg.module.getTarget(); const dib = dg.object.di_builder.?; switch (ty.zigTypeTag()) { @@ -2084,7 +2087,7 @@ pub const DeclGen = struct { ), }; - const replacement_di_type = dib.createStructType( + const replacement_di_ty = dib.createStructType( compile_unit_scope, name.ptr, di_file, @@ -2099,10 +2102,10 @@ pub const DeclGen = struct { null, // vtable holder "", // unique id ); - dib.replaceTemporary(fwd_decl, replacement_di_type); + dib.replaceTemporary(fwd_decl, replacement_di_ty); // The recursive call to `lowerDebugType` means we can't use `gop` anymore. - try dg.object.di_type_map.put(gpa, ty, replacement_di_type); - return replacement_di_type; + try dg.object.di_type_map.put(gpa, ty, replacement_di_ty); + return replacement_di_ty; } const elem_di_ty = try lowerDebugType(dg, ptr_info.pointee_type); @@ -2118,6 +2121,10 @@ pub const DeclGen = struct { return ptr_di_ty; }, .Opaque => { + if (ty.tag() == .anyopaque) { + gop.value_ptr.* = dib.createBasicType("anyopaque", 0, DW.ATE.signed); + return gop.value_ptr.*; + } const name = try ty.nameAlloc(gpa); // TODO this is a leak const owner_decl = ty.getOwnerDecl(); const opaque_di_ty = dib.createForwardDeclType( @@ -2144,7 +2151,15 @@ pub const DeclGen = struct { return array_di_ty; }, .Vector => { - @panic("TODO debug info type for vector"); + const vector_di_ty = dib.createVectorType( + ty.abiSize(target) * 8, + ty.abiAlignment(target) * 8, + try lowerDebugType(dg, ty.childType()), + ty.vectorLen(), + ); + // The recursive call to `lowerDebugType` means we can't use `gop` anymore. + try dg.object.di_type_map.put(gpa, ty, vector_di_ty); + return vector_di_ty; }, .Optional => { const name = try ty.nameAlloc(gpa); // TODO this is a leak @@ -2209,7 +2224,7 @@ pub const DeclGen = struct { ), }; - const replacement_di_type = dib.createStructType( + const replacement_di_ty = dib.createStructType( compile_unit_scope, name.ptr, di_file, @@ -2224,10 +2239,10 @@ pub const DeclGen = struct { null, // vtable holder "", // unique id ); - dib.replaceTemporary(fwd_decl, replacement_di_type); + dib.replaceTemporary(fwd_decl, replacement_di_ty); // The recursive call to `lowerDebugType` means we can't use `gop` anymore. - try dg.object.di_type_map.put(gpa, ty, replacement_di_type); - return replacement_di_type; + try dg.object.di_type_map.put(gpa, ty, replacement_di_ty); + return replacement_di_ty; }, .ErrorUnion => { const err_set_ty = ty.errorUnionSet(); @@ -2286,7 +2301,7 @@ pub const DeclGen = struct { ), }; - const replacement_di_type = dib.createStructType( + const replacement_di_ty = dib.createStructType( compile_unit_scope, name.ptr, di_file, @@ -2301,10 +2316,10 @@ pub const DeclGen = struct { null, // vtable holder "", // unique id ); - dib.replaceTemporary(fwd_decl, replacement_di_type); + dib.replaceTemporary(fwd_decl, replacement_di_ty); // The recursive call to `lowerDebugType` means we can't use `gop` anymore. - try dg.object.di_type_map.put(gpa, ty, replacement_di_type); - return replacement_di_type; + try dg.object.di_type_map.put(gpa, ty, replacement_di_ty); + return replacement_di_ty; }, .ErrorSet => { // TODO make this a proper enum with all the error codes in it. @@ -2313,20 +2328,80 @@ pub const DeclGen = struct { return gop.value_ptr.*; }, .Struct => { - const owner_decl = ty.getOwnerDecl(); - + const compile_unit_scope = dg.object.di_compile_unit.?.toScope(); const name = try ty.nameAlloc(gpa); // TODO this is a leak const fwd_decl = dib.createReplaceableCompositeType( DW.TAG.structure_type, name.ptr, - dg.object.di_compile_unit.?.toScope(), + compile_unit_scope, null, // file 0, // line ); gop.value_ptr.* = fwd_decl; + if (ty.isTupleOrAnonStruct()) { + const tuple = ty.tupleFields(); + + var di_fields: std.ArrayListUnmanaged(*llvm.DIType) = .{}; + defer di_fields.deinit(gpa); + + try di_fields.ensureUnusedCapacity(gpa, tuple.types.len); + + comptime assert(struct_layout_version == 2); + var offset: u64 = 0; + + for (tuple.types) |field_ty, i| { + const field_val = tuple.values[i]; + if (field_val.tag() != .unreachable_value) continue; + + const field_size = field_ty.abiSize(target); + const field_align = field_ty.abiAlignment(target); + const field_offset = std.mem.alignForwardGeneric(u64, offset, field_align); + offset = field_offset + field_size; + + const field_name = if (ty.castTag(.anon_struct)) |payload| + try gpa.dupeZ(u8, payload.data.names[i]) + else + try std.fmt.allocPrintZ(gpa, "{d}", .{i}); + defer gpa.free(field_name); + + try di_fields.append(gpa, dib.createMemberType( + fwd_decl.toScope(), + field_name, + null, // file + 0, // line + field_size * 8, // size in bits + field_align * 8, // align in bits + field_offset * 8, // offset in bits + 0, // flags + try dg.lowerDebugType(field_ty), + )); + } + + const replacement_di_ty = dib.createStructType( + compile_unit_scope, + name.ptr, + null, // file + 0, // line + ty.abiSize(target) * 8, // size in bits + ty.abiAlignment(target) * 8, // align in bits + 0, // flags + null, // derived from + di_fields.items.ptr, + @intCast(c_int, di_fields.items.len), + 0, // run time lang + null, // vtable holder + "", // unique id + ); + dib.replaceTemporary(fwd_decl, replacement_di_ty); + // The recursive call to `lowerDebugType` means we can't use `gop` anymore. + try dg.object.di_type_map.put(gpa, ty, replacement_di_ty); + return replacement_di_ty; + } + const TODO_implement_this = true; // TODO if (TODO_implement_this or !ty.hasRuntimeBits()) { + const owner_decl = ty.getOwnerDecl(); const struct_di_ty = try dg.makeEmptyNamespaceDIType(owner_decl); dib.replaceTemporary(fwd_decl, struct_di_ty); // The recursive call to `lowerDebugType` via `makeEmptyNamespaceDIType` @@ -2336,65 +2411,6 @@ pub const DeclGen = struct { } @panic("TODO debug info type for struct"); - //const gop = try dg.object.type_map.getOrPut(gpa, ty); - //if (gop.found_existing) return gop.value_ptr.*; - - //// The Type memory is ephemeral; since we want to store a longer-lived - //// reference, we need to copy it here. - //gop.key_ptr.* = try ty.copy(dg.object.type_map_arena.allocator()); - - //if (ty.isTupleOrAnonStruct()) { - // const tuple = ty.tupleFields(); - // const llvm_struct_ty = dg.context.structCreateNamed(""); - // gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls - - // var llvm_field_types: std.ArrayListUnmanaged(*const llvm.Type) = .{}; - // defer llvm_field_types.deinit(gpa); - - // try llvm_field_types.ensureUnusedCapacity(gpa, tuple.types.len); - - // comptime assert(struct_layout_version == 2); - // var offset: u64 = 0; - // var big_align: u32 = 0; - - // for (tuple.types) |field_ty, i| { - // const field_val = tuple.values[i]; - // if (field_val.tag() != .unreachable_value) continue; - - // const field_align = field_ty.abiAlignment(target); - // big_align = @maximum(big_align, field_align); - // const prev_offset = offset; - // offset = std.mem.alignForwardGeneric(u64, offset, field_align); - - // const padding_len = offset - prev_offset; - // if (padding_len > 0) { - // const llvm_array_ty = dg.context.intType(8).arrayType(@intCast(c_uint, padding_len)); - // try llvm_field_types.append(gpa, llvm_array_ty); - // } - // const field_llvm_ty = try dg.llvmType(field_ty); - // try llvm_field_types.append(gpa, field_llvm_ty); - - // offset += field_ty.abiSize(target); - // } - // { - // const prev_offset = offset; - // offset = std.mem.alignForwardGeneric(u64, offset, big_align); - // const padding_len = offset - prev_offset; - // if (padding_len > 0) { - // const llvm_array_ty = dg.context.intType(8).arrayType(@intCast(c_uint, padding_len)); - // try llvm_field_types.append(gpa, llvm_array_ty); - // } - // } - - // llvm_struct_ty.structSetBody( - // llvm_field_types.items.ptr, - // @intCast(c_uint, llvm_field_types.items.len), - // .False, - // ); - - // return llvm_struct_ty; - //} - //const struct_obj = ty.castTag(.@"struct").?.data; //if (struct_obj.layout == .Packed) { @@ -2554,7 +2570,10 @@ pub const DeclGen = struct { defer param_di_types.deinit(); // Return type goes first. - const di_ret_ty = if (sret) Type.void else fn_info.return_type; + const di_ret_ty = if (sret or !fn_info.return_type.hasRuntimeBits()) + Type.void + else + fn_info.return_type; try param_di_types.append(try dg.lowerDebugType(di_ret_ty)); if (sret) { From fb4ad37e0bd07513a0a56afb45e95c68036b1eea Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 8 Mar 2022 14:55:54 -0700 Subject: [PATCH 11/11] LLVM: fix memory leak of debug type names This required adjusting `Type.nameAlloc` to be used with a general-purpose allocator and added `Type.nameAllocArena` for the arena use case (avoids allocation sometimes). --- src/Sema.zig | 2 +- src/codegen/llvm.zig | 30 +++++++++----- src/link/Dwarf.zig | 2 +- src/type.zig | 96 ++++++++++++++++++++++++++------------------ 4 files changed, 80 insertions(+), 50 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 249e3be34d9b..b47836202c10 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -12405,7 +12405,7 @@ fn zirTypeName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai var anon_decl = try block.startAnonDecl(LazySrcLoc.unneeded); defer anon_decl.deinit(); - const bytes = try ty.nameAlloc(anon_decl.arena()); + const bytes = try ty.nameAllocArena(anon_decl.arena()); const new_decl = try anon_decl.finish( try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), bytes.len), diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index bfe520e1b633..fb7daa80ecd7 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1922,7 +1922,8 @@ pub const DeclGen = struct { .Int => { const info = ty.intInfo(target); assert(info.bits != 0); - const name = try ty.nameAlloc(gpa); // TODO this is a leak + const name = try ty.nameAlloc(gpa); + defer gpa.free(name); const dwarf_encoding: c_uint = switch (info.signedness) { .signed => DW.ATE.signed, .unsigned => DW.ATE.unsigned, @@ -1967,7 +1968,8 @@ pub const DeclGen = struct { const di_file = try dg.object.getDIFile(gpa, owner_decl.src_namespace.file_scope); const di_scope = try dg.namespaceToDebugScope(owner_decl.src_namespace); - const name = try ty.nameAlloc(gpa); // TODO this is a leak + const name = try ty.nameAlloc(gpa); + defer gpa.free(name); var buffer: Type.Payload.Bits = undefined; const int_ty = ty.intTagType(&buffer); @@ -1989,7 +1991,8 @@ pub const DeclGen = struct { }, .Float => { const bits = ty.floatBits(target); - const name = try ty.nameAlloc(gpa); // TODO this is a leak + const name = try ty.nameAlloc(gpa); + defer gpa.free(name); gop.value_ptr.* = dib.createBasicType(name, bits, DW.ATE.float); return gop.value_ptr.*; }, @@ -2039,7 +2042,8 @@ pub const DeclGen = struct { const ptr_ty = ty.slicePtrFieldType(&buf); const len_ty = Type.usize; - const name = try ty.nameAlloc(gpa); // TODO this is a leak + const name = try ty.nameAlloc(gpa); + defer gpa.free(name); const di_file: ?*llvm.DIFile = null; const line = 0; const compile_unit_scope = dg.object.di_compile_unit.?.toScope(); @@ -2109,7 +2113,8 @@ pub const DeclGen = struct { } const elem_di_ty = try lowerDebugType(dg, ptr_info.pointee_type); - const name = try ty.nameAlloc(gpa); // TODO this is a leak + const name = try ty.nameAlloc(gpa); + defer gpa.free(name); const ptr_di_ty = dib.createPointerType( elem_di_ty, target.cpu.arch.ptrBitWidth(), @@ -2125,7 +2130,8 @@ pub const DeclGen = struct { gop.value_ptr.* = dib.createBasicType("anyopaque", 0, DW.ATE.signed); return gop.value_ptr.*; } - const name = try ty.nameAlloc(gpa); // TODO this is a leak + const name = try ty.nameAlloc(gpa); + defer gpa.free(name); const owner_decl = ty.getOwnerDecl(); const opaque_di_ty = dib.createForwardDeclType( DW.TAG.structure_type, @@ -2162,7 +2168,8 @@ pub const DeclGen = struct { return vector_di_ty; }, .Optional => { - const name = try ty.nameAlloc(gpa); // TODO this is a leak + const name = try ty.nameAlloc(gpa); + defer gpa.free(name); var buf: Type.Payload.ElemType = undefined; const child_ty = ty.optionalChild(&buf); if (!child_ty.hasRuntimeBits()) { @@ -2253,7 +2260,8 @@ pub const DeclGen = struct { try dg.object.di_type_map.put(gpa, ty, err_set_di_ty); return err_set_di_ty; } - const name = try ty.nameAlloc(gpa); // TODO this is a leak + const name = try ty.nameAlloc(gpa); + defer gpa.free(name); const di_file: ?*llvm.DIFile = null; const line = 0; const compile_unit_scope = dg.object.di_compile_unit.?.toScope(); @@ -2329,7 +2337,8 @@ pub const DeclGen = struct { }, .Struct => { const compile_unit_scope = dg.object.di_compile_unit.?.toScope(); - const name = try ty.nameAlloc(gpa); // TODO this is a leak + const name = try ty.nameAlloc(gpa); + defer gpa.free(name); const fwd_decl = dib.createReplaceableCompositeType( DW.TAG.structure_type, name.ptr, @@ -2477,7 +2486,8 @@ pub const DeclGen = struct { .Union => { const owner_decl = ty.getOwnerDecl(); - const name = try ty.nameAlloc(gpa); // TODO this is a leak + const name = try ty.nameAlloc(gpa); + defer gpa.free(name); const fwd_decl = dib.createReplaceableCompositeType( DW.TAG.structure_type, name.ptr, diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index c4e31eed6d02..fc8f1fab5503 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -882,7 +882,7 @@ fn addDbgInfoType( const abi_size = ty.abiSize(target); try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size); // DW.AT.name, DW.FORM.string - const struct_name = try ty.nameAlloc(arena); + const struct_name = try ty.nameAllocArena(arena); try dbg_info_buffer.ensureUnusedCapacity(struct_name.len + 1); dbg_info_buffer.appendSliceAssumeCapacity(struct_name); dbg_info_buffer.appendAssumeCapacity(0); diff --git a/src/type.zig b/src/type.zig index 150ea234a049..b6728b4be00a 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1766,8 +1766,20 @@ pub const Type = extern union { } } + pub fn nameAllocArena(ty: Type, arena: Allocator) Allocator.Error![:0]const u8 { + return nameAllocAdvanced(ty, arena, true); + } + + pub fn nameAlloc(ty: Type, gpa: Allocator) Allocator.Error![:0]const u8 { + return nameAllocAdvanced(ty, gpa, false); + } + /// Returns a name suitable for `@typeName`. - pub fn nameAlloc(ty: Type, arena: Allocator) Allocator.Error![:0]const u8 { + pub fn nameAllocAdvanced( + ty: Type, + ally: Allocator, + is_arena: bool, + ) Allocator.Error![:0]const u8 { const t = ty.tag(); switch (t) { .inferred_alloc_const => unreachable, @@ -1812,71 +1824,79 @@ pub const Type = extern union { .noreturn, .var_args_param, .bound_fn, - => return @tagName(t), + => return maybeDupe(@tagName(t), ally, is_arena), - .enum_literal => return "@Type(.EnumLiteral)", - .@"null" => return "@Type(.Null)", - .@"undefined" => return "@Type(.Undefined)", + .enum_literal => return maybeDupe("@Type(.EnumLiteral)", ally, is_arena), + .@"null" => return maybeDupe("@Type(.Null)", ally, is_arena), + .@"undefined" => return maybeDupe("@Type(.Undefined)", ally, is_arena), - .empty_struct, .empty_struct_literal => return "struct {}", + .empty_struct, .empty_struct_literal => return maybeDupe("struct {}", ally, is_arena), .@"struct" => { const struct_obj = ty.castTag(.@"struct").?.data; - return try arena.dupeZ(u8, std.mem.sliceTo(struct_obj.owner_decl.name, 0)); + return try ally.dupeZ(u8, std.mem.sliceTo(struct_obj.owner_decl.name, 0)); }, .@"union", .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; - return try arena.dupeZ(u8, std.mem.sliceTo(union_obj.owner_decl.name, 0)); + return try ally.dupeZ(u8, std.mem.sliceTo(union_obj.owner_decl.name, 0)); }, .enum_full, .enum_nonexhaustive => { const enum_full = ty.cast(Payload.EnumFull).?.data; - return try arena.dupeZ(u8, std.mem.sliceTo(enum_full.owner_decl.name, 0)); + return try ally.dupeZ(u8, std.mem.sliceTo(enum_full.owner_decl.name, 0)); }, .enum_simple => { const enum_simple = ty.castTag(.enum_simple).?.data; - return try arena.dupeZ(u8, std.mem.sliceTo(enum_simple.owner_decl.name, 0)); + return try ally.dupeZ(u8, std.mem.sliceTo(enum_simple.owner_decl.name, 0)); }, .enum_numbered => { const enum_numbered = ty.castTag(.enum_numbered).?.data; - return try arena.dupeZ(u8, std.mem.sliceTo(enum_numbered.owner_decl.name, 0)); + return try ally.dupeZ(u8, std.mem.sliceTo(enum_numbered.owner_decl.name, 0)); }, .@"opaque" => { - // TODO use declaration name - return "opaque {}"; - }, - - .anyerror_void_error_union => return "anyerror!void", - .const_slice_u8 => return "[]const u8", - .const_slice_u8_sentinel_0 => return "[:0]const u8", - .fn_noreturn_no_args => return "fn() noreturn", - .fn_void_no_args => return "fn() void", - .fn_naked_noreturn_no_args => return "fn() callconv(.Naked) noreturn", - .fn_ccc_void_no_args => return "fn() callconv(.C) void", - .single_const_pointer_to_comptime_int => return "*const comptime_int", - .manyptr_u8 => return "[*]u8", - .manyptr_const_u8 => return "[*]const u8", - .manyptr_const_u8_sentinel_0 => return "[*:0]const u8", - .atomic_order => return "AtomicOrder", - .atomic_rmw_op => return "AtomicRmwOp", - .calling_convention => return "CallingConvention", - .address_space => return "AddressSpace", - .float_mode => return "FloatMode", - .reduce_op => return "ReduceOp", - .call_options => return "CallOptions", - .prefetch_options => return "PrefetchOptions", - .export_options => return "ExportOptions", - .extern_options => return "ExternOptions", - .type_info => return "Type", + const opaque_obj = ty.cast(Payload.Opaque).?.data; + return try ally.dupeZ(u8, std.mem.sliceTo(opaque_obj.owner_decl.name, 0)); + }, + + .anyerror_void_error_union => return maybeDupe("anyerror!void", ally, is_arena), + .const_slice_u8 => return maybeDupe("[]const u8", ally, is_arena), + .const_slice_u8_sentinel_0 => return maybeDupe("[:0]const u8", ally, is_arena), + .fn_noreturn_no_args => return maybeDupe("fn() noreturn", ally, is_arena), + .fn_void_no_args => return maybeDupe("fn() void", ally, is_arena), + .fn_naked_noreturn_no_args => return maybeDupe("fn() callconv(.Naked) noreturn", ally, is_arena), + .fn_ccc_void_no_args => return maybeDupe("fn() callconv(.C) void", ally, is_arena), + .single_const_pointer_to_comptime_int => return maybeDupe("*const comptime_int", ally, is_arena), + .manyptr_u8 => return maybeDupe("[*]u8", ally, is_arena), + .manyptr_const_u8 => return maybeDupe("[*]const u8", ally, is_arena), + .manyptr_const_u8_sentinel_0 => return maybeDupe("[*:0]const u8", ally, is_arena), + .atomic_order => return maybeDupe("AtomicOrder", ally, is_arena), + .atomic_rmw_op => return maybeDupe("AtomicRmwOp", ally, is_arena), + .calling_convention => return maybeDupe("CallingConvention", ally, is_arena), + .address_space => return maybeDupe("AddressSpace", ally, is_arena), + .float_mode => return maybeDupe("FloatMode", ally, is_arena), + .reduce_op => return maybeDupe("ReduceOp", ally, is_arena), + .call_options => return maybeDupe("CallOptions", ally, is_arena), + .prefetch_options => return maybeDupe("PrefetchOptions", ally, is_arena), + .export_options => return maybeDupe("ExportOptions", ally, is_arena), + .extern_options => return maybeDupe("ExternOptions", ally, is_arena), + .type_info => return maybeDupe("Type", ally, is_arena), else => { // TODO this is wasteful and also an incorrect implementation of `@typeName` - var buf = std.ArrayList(u8).init(arena); + var buf = std.ArrayList(u8).init(ally); try buf.writer().print("{}", .{ty}); return try buf.toOwnedSliceSentinel(0); }, } } + fn maybeDupe(s: [:0]const u8, ally: Allocator, is_arena: bool) Allocator.Error![:0]const u8 { + if (is_arena) { + return s; + } else { + return try ally.dupeZ(u8, s); + } + } + pub fn toValue(self: Type, allocator: Allocator) Allocator.Error!Value { switch (self.tag()) { .u1 => return Value.initTag(.u1_type),