Commit 0d24bc7da0

Andrew Kelley <andrew@ziglang.org>
2022-03-08 03:54:35
LLVM: add DISubprogram and DIType lowering; handle dbg_stmt
1 parent 6272092
Changed files (3)
lib
src
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,
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;
+};
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);
             }
         }