From 423a559dbae4d6dbcb8b43d19f4cdb98bb07d87e Mon Sep 17 00:00:00 2001 From: David Gonzalez Martin Date: Sat, 22 Feb 2025 09:28:11 -0600 Subject: [PATCH] Implement basic debug information --- src/LLVM.zig | 281 ++++++++++++++++++++++++++++++++++------- src/converter.zig | 196 ++++++++++++++++++++++++---- src/converter_test.zig | 6 +- src/lib.zig | 14 ++ src/llvm.cpp | 6 + src/llvm_api.zig | 16 ++- src/main.zig | 21 +-- 7 files changed, 454 insertions(+), 86 deletions(-) diff --git a/src/LLVM.zig b/src/LLVM.zig index e28826d..d7bed00 100644 --- a/src/LLVM.zig +++ b/src/LLVM.zig @@ -606,6 +606,12 @@ pub const Builder = opaque { pub fn create_load(builder: *Builder, ty: *Type, pointer: *Value) *Value { return api.LLVMBuildLoad2(builder, ty, pointer, ""); } + + pub fn clear_current_debug_location(builder: *Builder) void { + return builder.set_current_debug_location(null); + } + + pub const set_current_debug_location = api.LLVMSetCurrentDebugLocation2; }; pub const GlobalValue = opaque { @@ -632,6 +638,8 @@ pub const Function = opaque { result.error_message = string.to_slice(); return result; } + pub const set_subprogram = api.LLVMSetSubprogram; + pub const get_subprogram = api.LLVMGetSubprogram; }; pub const Constant = opaque { @@ -652,48 +660,107 @@ pub const Value = opaque { pub const DI = struct { pub const Builder = opaque { - pub fn create_file(builder: *DI.Builder, file_name: []const u8, directory_name: []const u8) *File { - return api.LLVMCreateDIBuilder(builder, file_name.ptr, file_name.len, directory_name.ptr, directory_name.len); + pub fn create_file(builder: *DI.Builder, file_name: []const u8, directory: []const u8) *File { + return api.LLVMDIBuilderCreateFile(builder, String.from_slice(file_name), String.from_slice(directory)); + } + + pub fn create_compile_unit(builder: *DI.Builder, file: *DI.File, optimized: bool) *DI.CompileUnit { + return api.LLVMDIBuilderCreateCompileUnit(builder, .C17, file, String.from_slice("bloat buster"), @intFromBool(optimized), String{}, 0, String{}, .full, 0, 0, @intFromBool(optimized), String{}, String{}); + } + + pub const finalize = api.LLVMDIBuilderFinalize; + + pub fn create_subroutine_type(builder: *DI.Builder, file: *DI.File, parameter_types: []const *DI.Type, flags: DI.Flags) *DI.Type.Subroutine { + return api.LLVMDIBuilderCreateSubroutineType(builder, file, parameter_types.ptr, @intCast(parameter_types.len), flags); + } + + pub fn create_function(builder: *DI.Builder, scope: *DI.Scope, name: []const u8, linkage_name: []const u8, file: *DI.File, line_number: c_uint, subroutine_type: *DI.Type.Subroutine, local_to_unit: bool, is_definition: bool, scope_line: c_uint, flags: DI.Flags, is_optimized: bool) *DI.Subprogram { + return api.LLVMDIBuilderCreateFunction(builder, scope, String.from_slice(name), String.from_slice(linkage_name), file, line_number, subroutine_type, @intFromBool(local_to_unit), @intFromBool(is_definition), scope_line, flags, @intFromBool(is_optimized)); + } + + pub fn create_basic_type(builder: *DI.Builder, name: []const u8, bit_count: u64, dwarf_type: Dwarf.Type, flags: DI.Flags) *DI.Type { + return api.LLVMDIBuilderCreateBasicType(builder, name.ptr, name.len, bit_count, dwarf_type, flags); + } + + pub const finalize_subprogram = api.LLVMDIBuilderFinalizeSubprogram; + + pub fn create_expression(builder: *DI.Builder, slice: []const u64) *DI.Expression { + return api.LLVMDIBuilderCreateExpression(builder, slice.ptr, slice.len); + } + + pub fn null_expression(builder: *DI.Builder) *DI.Expression { + return api.LLVMDIBuilderCreateExpression(builder, null, 0); + } + + pub fn create_auto_variable(builder: *DI.Builder, scope: *DI.Scope, name: []const u8, file: *DI.File, line: c_uint, auto_type: *DI.Type, always_preserve: bool, flags: DI.Flags, alignment_in_bits: u32) *DI.LocalVariable { + return api.LLVMDIBuilderCreateAutoVariable(builder, scope, name.ptr, name.len, file, line, auto_type, @intFromBool(always_preserve), flags, alignment_in_bits); + } + + pub const insert_declare_record_at_end = api.LLVMDIBuilderInsertDeclareRecordAtEnd; + }; + + pub const create_debug_location = api.LLVMDIBuilderCreateDebugLocation; + + pub const CompileUnit = opaque { + pub fn to_scope(compile_unit: *DI.CompileUnit) *DI.Scope { + return @ptrCast(compile_unit); } }; pub const File = opaque {}; + pub const Scope = opaque {}; + pub const Subprogram = opaque {}; + pub const Expression = opaque {}; + pub const LocalVariable = opaque {}; + pub const Location = opaque {}; + pub const Metadata = opaque {}; + pub const Record = opaque {}; - const Flags = enum(c_int) { - _, - const Zero = 0; - const Private = 1; - const Protected = 2; - const Public = 3; - const FwdDecl = 1 << 2; - const AppleBlock = 1 << 3; - const ReservedBit4 = 1 << 4; - const Virtual = 1 << 5; - const Artificial = 1 << 6; - const Explicit = 1 << 7; - const Prototyped = 1 << 8; - const ObjcClassComplete = 1 << 9; - const ObjectPointer = 1 << 10; - const Vector = 1 << 11; - const StaticMember = 1 << 12; - const LValueReference = 1 << 13; - const RValueReference = 1 << 14; - const Reserved = 1 << 15; - const SingleInheritance = 1 << 16; - const MultipleInheritance = 2 << 16; - const VirtualInheritance = 3 << 16; - const IntroducedVirtual = 1 << 18; - const BitField = 1 << 19; - const NoReturn = 1 << 20; - const TypePassByValue = 1 << 22; - const TypePassByReference = 1 << 23; - const EnumClass = 1 << 24; - const Thunk = 1 << 25; - const NonTrivial = 1 << 26; - const BigEndian = 1 << 27; - const LittleEndian = 1 << 28; - const IndirectVirtualBase = (1 << 2) | (1 << 5); - const Accessibility = Private | Protected | Public; - const PtrToMemberRep = SingleInheritance | MultipleInheritance | VirtualInheritance; + pub const Type = opaque { + pub const Subroutine = opaque {}; + }; + + pub const Flags = packed struct(u32) { + visibility: Visibility = .none, + forward_declaration: bool = false, + apple_block: bool = false, + block_by_ref_struct: bool = false, + virtual: bool = false, + artificial: bool = false, + explicit: bool = false, + prototyped: bool = false, + objective_c_class_complete: bool = false, + object_pointer: bool = false, + vector: bool = false, + static_member: bool = false, + lvalue_reference: bool = false, + rvalue_reference: bool = false, + reserved: bool = false, + inheritance: Inheritance = .none, + introduced_virtual: bool = false, + bit_field: bool = false, + no_return: bool = false, + type_pass_by_value: bool = false, + type_pass_by_reference: bool = false, + enum_class: bool = false, + thunk: bool = false, + non_trivial: bool = false, + big_endian: bool = false, + little_endian: bool = false, + all_calls_described: bool = false, + _: u3 = 0, + + const Visibility = enum(u2) { + none = 0, + private = 1, + protected = 2, + public = 3, + }; + const Inheritance = enum(u2) { + none = 0, + single = 1, + multiple = 2, + virtual = 3, + }; }; }; @@ -722,6 +789,131 @@ pub const Type = opaque { pub fn to_type(integer: *Type.Integer) *Type { return @ptrCast(integer); } + pub const get_bit_count = api.llvm_integer_type_get_bit_count; + }; +}; + +pub const Dwarf = struct { + pub const Type = enum(c_uint) { + void = 0x0, + address = 0x1, + boolean = 0x2, + complex_float = 0x3, + float = 0x4, + signed = 0x5, + signed_char = 0x6, + unsigned = 0x7, + unsigned_char = 0x8, + + // DWARF 3. + imaginary_float = 0x9, + packed_decimal = 0xa, + numeric_string = 0xb, + edited = 0xc, + signed_fixed = 0xd, + unsigned_fixed = 0xe, + decimal_float = 0xf, + + // DWARF 4. + UTF = 0x10, + + // DWARF 5. + UCS = 0x11, + ASCII = 0x12, + + // HP extensions. + HP_float80 = 0x80, // Floating-point (80 bit). + HP_complex_float80 = 0x81, // Complex floating-point (80 bit). + HP_float128 = 0x82, // Floating-point (128 bit). + HP_complex_float128 = 0x83, // Complex fp (128 bit). + HP_floathpintel = 0x84, // Floating-point (82 bit IA64). + HP_imaginary_float80 = 0x85, + HP_imaginary_float128 = 0x86, + HP_VAX_float = 0x88, // F or G floating. + HP_VAX_float_d = 0x89, // D floating. + HP_packed_decimal = 0x8a, // Cobol. + HP_zoned_decimal = 0x8b, // Cobol. + HP_edited = 0x8c, // Cobol. + HP_signed_fixed = 0x8d, // Cobol. + HP_unsigned_fixed = 0x8e, // Cobol. + HP_VAX_complex_float = 0x8f, // F or G floating complex. + HP_VAX_complex_float_d = 0x90, // D floating complex. + }; + + pub const EmissionKind = enum(c_int) { + none, + full, + line_tables_only, + }; + + pub const SourceLanguage = enum(c_int) { + C89, + C, + Ada83, + C_plus_plus, + Cobol74, + Cobol85, + Fortran77, + Fortran90, + Pascal83, + Modula2, + // New in DWARF v3: + Java, + C99, + Ada95, + Fortran95, + PLI, + ObjC, + ObjC_plus_plus, + UPC, + D, + // New in DWARF v4: + Python, + // New in DWARF v5: + OpenCL, + Go, + Modula3, + Haskell, + C_plus_plus_03, + C_plus_plus_11, + OCaml, + Rust, + C11, + Swift, + Julia, + Dylan, + C_plus_plus_14, + Fortran03, + Fortran08, + RenderScript, + BLISS, + Kotlin, + Zig, + Crystal, + C_plus_plus_17, + C_plus_plus_20, + C17, + Fortran18, + Ada2005, + Ada2012, + HIP, + Assembly, + C_sharp, + Mojo, + GLSL, + GLSL_ES, + HLSL, + OpenCL_CPP, + CPP_for_OpenCL, + SYCL, + Ruby, + Move, + Hylo, + + // Vendor extensions: + Mips_Assembler, + GOOGLE_RenderScript, + BORLAND_Delphi, }; }; @@ -739,15 +931,6 @@ pub const LinkageType = enum(c_int) { CommonLinkage, }; -pub const DwarfSourceLanguage = enum(c_int) { - c17 = 0x2c, -}; -pub const DwarfEmissionKind = enum(c_int) { - none, - full, - line_tables_only, -}; - pub const lld = struct { pub const Result = extern struct { stdout: String, @@ -881,7 +1064,7 @@ pub const GenerateObject = struct { pub const ObjectGenerate = struct { path: []const u8, optimization_level: ?OptimizationLevel, - debug_info: u1, + debug_info: bool, optimize_when_possible: u1, }; @@ -889,7 +1072,7 @@ pub fn object_generate(module: *Module, target_machine: *Target.Machine, generat module.set_target(target_machine); if (generate.optimization_level) |optimization_level| { - module.run_optimization_pipeline(target_machine, OptimizationPipelineOptions.default(.{ .optimization_level = optimization_level, .debug_info = generate.debug_info })); + module.run_optimization_pipeline(target_machine, OptimizationPipelineOptions.default(.{ .optimization_level = optimization_level, .debug_info = @intFromBool(generate.debug_info) })); } const result = module.run_code_generation_pipeline(target_machine, CodeGenerationPipelineOptions{ diff --git a/src/converter.zig b/src/converter.zig index ab67acd..511939b 100644 --- a/src/converter.zig +++ b/src/converter.zig @@ -72,6 +72,7 @@ const Local = struct { pub const FunctionBuilder = struct { function: *llvm.Function, current_basic_block: *llvm.BasicBlock, + current_scope: *llvm.DI.Scope, return_type: Type, local_buffer: [64]Local = undefined, local_count: u32 = 0, @@ -92,11 +93,32 @@ const Type = packed struct(u64) { pub fn get(t: Type) *llvm.Type { return @ptrFromInt(t.llvm); } + + pub fn to_debug_type(ty: Type, module: *ModuleBuilder) *llvm.DI.Type { + if (ty.get().is_integer()) { + const integer_type = ty.get().to_integer(); + const bit_count = integer_type.get_bit_count(); + const index = (@ctz(bit_count) - 3) + (@as(u8, 4) * @intFromBool(ty.signedness)); + return module.integer_types[index]; + } else { + os.abort(); + } + } }; const Converter = struct { content: []const u8, offset: usize, + line_offset: usize, + line_character_offset: usize, + + fn get_line(converter: *const Converter) u32 { + return @intCast(converter.line_offset + 1); + } + + fn get_column(converter: *const Converter) u32 { + return @intCast(converter.offset - converter.line_character_offset + 1); + } fn report_error(noalias converter: *Converter) noreturn { @branchHint(.cold); @@ -106,6 +128,8 @@ const Converter = struct { fn skip_space(noalias converter: *Converter) void { while (converter.offset < converter.content.len and is_space(converter.content[converter.offset])) { + converter.line_offset += @intFromBool(converter.content[converter.offset] == '\n'); + converter.line_character_offset = if (converter.content[converter.offset] == '\n') converter.offset else converter.line_character_offset; converter.offset += 1; } } @@ -255,13 +279,13 @@ const Converter = struct { } } - fn parse_block(noalias converter: *Converter, noalias thread: *llvm.Thread, noalias function_builder: *FunctionBuilder) void { + fn parse_block(noalias converter: *Converter, noalias thread: *llvm.Thread, noalias module: *ModuleBuilder, noalias function: *FunctionBuilder) void { converter.skip_space(); converter.expect_character(left_brace); - const local_offset = function_builder.local_count; - defer function_builder.local_count = local_offset; + const local_offset = function.local_count; + defer function.local_count = local_offset; while (true) { converter.skip_space(); @@ -276,6 +300,16 @@ const Converter = struct { const require_semicolon = true; + const line = converter.get_line(); + const column = converter.get_column(); + + var statement_debug_location: *llvm.DI.Location = undefined; + if (module.di_builder) |_| { + const inlined_at: ?*llvm.DI.Metadata = null; // TODO + statement_debug_location = llvm.DI.create_debug_location(thread.context, line, column, function.current_scope, inlined_at); + thread.builder.set_current_debug_location(statement_debug_location); + } + const statement_start_ch = converter.content[converter.offset]; if (statement_start_ch == '>') { converter.offset += 1; @@ -297,12 +331,30 @@ const Converter = struct { converter.skip_space(); - const value = converter.parse_value(thread, local_type, function_builder); + if (module.di_builder) |_| { + thread.builder.clear_current_debug_location(); + } const alloca = thread.builder.create_alloca(local_type.get(), local_name); + + const value = converter.parse_value(thread, module, function, local_type); + + if (module.di_builder) |di_builder| { + thread.builder.set_current_debug_location(statement_debug_location); + const debug_type = local_type.to_debug_type(module); + const always_preserve = true; + // TODO: + const alignment = 0; + const flags = llvm.DI.Flags{}; + const local_variable = di_builder.create_auto_variable(function.current_scope, local_name, module.file, line, debug_type, always_preserve, flags, alignment); + const inlined_at: ?*llvm.DI.Metadata = null; // TODO + const debug_location = llvm.DI.create_debug_location(thread.context, line, column, function.current_scope, inlined_at); + _ = di_builder.insert_declare_record_at_end(alloca, local_variable, di_builder.null_expression(), debug_location, function.current_basic_block); + thread.builder.set_current_debug_location(statement_debug_location); + } _ = thread.builder.create_store(value, alloca); - const local = &function_builder.local_buffer[function_builder.local_count]; - function_builder.local_count += 1; + const local = &function.local_buffer[function.local_count]; + function.local_count += 1; local.* = .{ .name = local_name, .alloca = alloca, @@ -317,7 +369,7 @@ const Converter = struct { if (string_to_enum(StatementStartKeyword, statement_start_identifier)) |statement_start_keyword| { switch (statement_start_keyword) { .@"return" => { - const return_value = converter.parse_value(thread, function_builder.return_type, function_builder); + const return_value = converter.parse_value(thread, module, function, function.return_type); thread.builder.create_ret(return_value); }, else => unreachable, @@ -356,7 +408,7 @@ const Converter = struct { xor, }; - fn parse_value(noalias converter: *Converter, noalias thread: *llvm.Thread, expected_type: Type, function_builder: *FunctionBuilder) *llvm.Value { + fn parse_value(noalias converter: *Converter, noalias thread: *llvm.Thread, noalias module: *ModuleBuilder, noalias function: *FunctionBuilder, expected_type: Type) *llvm.Value { converter.skip_space(); var value_state = ExpressionState.none; @@ -365,7 +417,7 @@ const Converter = struct { const value = while (true) { const current_value = switch (converter.content[converter.offset] == left_parenthesis) { true => os.abort(), - false => converter.parse_single_value(thread, expected_type, function_builder), + false => converter.parse_single_value(thread, module, function, expected_type), }; converter.skip_space(); @@ -470,9 +522,17 @@ const Converter = struct { negative, }; - fn parse_single_value(noalias converter: *Converter, thread: *llvm.Thread, expected_type: Type, noalias function_builder: *FunctionBuilder) *llvm.Value { + fn parse_single_value(noalias converter: *Converter, noalias thread: *llvm.Thread, noalias module: *ModuleBuilder, noalias function: *FunctionBuilder, expected_type: Type) *llvm.Value { converter.skip_space(); + if (module.di_builder) |_| { + const line = converter.get_line(); + const column = converter.get_column(); + const inlined_at: ?*llvm.DI.Metadata = null; // TODO + const debug_location = llvm.DI.create_debug_location(thread.context, line, column, function.current_scope, inlined_at); + thread.builder.set_current_debug_location(debug_location); + } + const prefix_offset = converter.offset; const prefix_ch = converter.content[prefix_offset]; const prefix: Prefix = switch (prefix_ch) { @@ -492,7 +552,7 @@ const Converter = struct { const value = switch (value_start_ch) { 'a'...'z', 'A'...'Z', '_' => b: { const identifier = converter.parse_identifier(); - for (function_builder.local_buffer[0..function_builder.local_count]) |*local| { + for (function.local_buffer[0..function.local_count]) |*local| { if (lib.string.equal(identifier, local.name)) { break :b thread.builder.create_load(local.type.get(), local.alloca); } @@ -527,6 +587,10 @@ pub const BuildMode = enum { aggressively_optimize_for_speed, aggressively_optimize_for_size, + fn is_optimized(build_mode: BuildMode) bool { + return @intFromEnum(build_mode) >= @intFromEnum(BuildMode.soft_optimize); + } + fn to_llvm_ir(build_mode: BuildMode) llvm.OptimizationLevel { return switch (build_mode) { .debug_none => unreachable, @@ -559,17 +623,77 @@ const ConvertOptions = struct { executable: [:0]const u8, build_mode: BuildMode, name: []const u8, - has_debug_info: u1, + has_debug_info: bool, +}; + +const ModuleBuilder = struct { + handle: *llvm.Module, + di_builder: ?*llvm.DI.Builder, + global_scope: *llvm.DI.Scope, + file: *llvm.DI.File, + integer_types: [8]*llvm.DI.Type, }; pub noinline fn convert(options: ConvertOptions) void { var converter = Converter{ .content = options.content, .offset = 0, + .line_offset = 0, + .line_character_offset = 0, }; const thread = llvm.default_initialize(); - const module = thread.context.create_module(options.name); + + const m = thread.context.create_module(options.name); + var module = ModuleBuilder{ + .handle = m, + .di_builder = if (options.has_debug_info) m.create_di_builder() else null, + .global_scope = undefined, + .file = undefined, + .integer_types = undefined, + }; + + if (module.di_builder) |di_builder| { + var directory: []const u8 = undefined; + var file_name: []const u8 = undefined; + if (lib.string.last_character(options.path, '/')) |index| { + directory = options.path[0..index]; + file_name = options.path[index + 1 ..]; + } else { + os.abort(); + } + const file = di_builder.create_file(file_name, directory); + const compile_unit = di_builder.create_compile_unit(file, options.build_mode.is_optimized()); + module.global_scope = compile_unit.to_scope(); + module.file = file; + + for ([2]bool{ false, true }) |sign| { + for (0..4) |i| { + var name_buffer = [3]u8{ if (sign) 's' else 'u', 0, 0 }; + const bit_count = @as(u64, 1) << @intCast(3 + i); + switch (bit_count) { + 8 => name_buffer[1] = '8', + 16 => { + name_buffer[1] = '1'; + name_buffer[2] = '6'; + }, + 32 => { + name_buffer[1] = '3'; + name_buffer[2] = '2'; + }, + 64 => { + name_buffer[1] = '6'; + name_buffer[2] = '4'; + }, + else => unreachable, + } + const name_length = @as(usize, 2) + @intFromBool(bit_count > 9); + const name = name_buffer[0..name_length]; + const dwarf_type: llvm.Dwarf.Type = if (bit_count == 8 and !sign) .unsigned_char else if (sign) .signed else .unsigned; + module.integer_types[i + @as(usize, 4) * @intFromBool(sign)] = di_builder.create_basic_type(name, bit_count, dwarf_type, .{}); + } + } + } while (true) { converter.skip_space(); @@ -580,6 +704,8 @@ pub noinline fn convert(options: ConvertOptions) void { var is_export = false; + const global_line_offset = converter.line_offset; + if (converter.content[converter.offset] == left_bracket) { converter.offset += 1; @@ -673,7 +799,7 @@ pub noinline fn convert(options: ConvertOptions) void { const return_type = converter.parse_type(thread); const function_type = llvm.Type.Function.get(return_type.get(), &.{}, false); - const function = module.create_function(.{ + const handle = module.handle.create_function(.{ .name = global_name, .linkage = switch (is_export) { true => .ExternalLinkage, @@ -682,19 +808,39 @@ pub noinline fn convert(options: ConvertOptions) void { .type = function_type, }); - const entry_block = thread.context.create_basic_block("entry", function); + const entry_block = thread.context.create_basic_block("entry", handle); thread.builder.position_at_end(entry_block); - var function_builder = FunctionBuilder{ - .function = function, + var function = FunctionBuilder{ + .function = handle, .current_basic_block = entry_block, .return_type = return_type, + .current_scope = undefined, }; - converter.parse_block(thread, &function_builder); + if (module.di_builder) |di_builder| { + const debug_return_type = return_type.to_debug_type(&module); + const subroutine_type = di_builder.create_subroutine_type(module.file, &.{debug_return_type}, .{}); + const linkage_name = global_name; + const line: u32 = @intCast(global_line_offset + 1); + const scope_line: u32 = @intCast(converter.line_offset + 1); + const local_to_unit = !is_export; + const flags = llvm.DI.Flags{}; + const is_definition = true; + const subprogram = di_builder.create_function(module.global_scope, global_name, linkage_name, module.file, line, subroutine_type, local_to_unit, is_definition, scope_line, flags, options.build_mode.is_optimized()); + handle.set_subprogram(subprogram); - if (lib.optimization_mode == .Debug) { - const verify_result = function.verify(); + function.current_scope = @ptrCast(subprogram); + } + + converter.parse_block(thread, &module, &function); + + if (module.di_builder) |di_builder| { + di_builder.finalize_subprogram(handle.get_subprogram()); + } + + if (lib.optimization_mode == .Debug and module.di_builder == null) { + const verify_result = handle.verify(); if (!verify_result.success) { os.abort(); } @@ -704,14 +850,18 @@ pub noinline fn convert(options: ConvertOptions) void { } } + if (module.di_builder) |di_builder| { + di_builder.finalize(); + } + if (lib.optimization_mode == .Debug) { - const verify_result = module.verify(); + const verify_result = module.handle.verify(); if (!verify_result.success) { os.abort(); } if (!lib.is_test) { - const module_string = module.to_string(); + const module_string = module.handle.to_string(); lib.print_string_stderr(module_string); } } @@ -730,7 +880,7 @@ pub noinline fn convert(options: ConvertOptions) void { os.abort(); }; - const object_generate_result = llvm.object_generate(module, target_machine, .{ + const object_generate_result = llvm.object_generate(module.handle, target_machine, .{ .optimize_when_possible = @intFromBool(@intFromEnum(options.build_mode) > @intFromEnum(BuildMode.soft_optimize)), .debug_info = options.has_debug_info, .optimization_level = if (options.build_mode != .debug_none) options.build_mode.to_llvm_ir() else null, diff --git a/src/converter_test.zig b/src/converter_test.zig index b1c4585..44d4c30 100644 --- a/src/converter_test.zig +++ b/src/converter_test.zig @@ -15,7 +15,7 @@ fn invoke(name: []const u8) !void { inline for (@typeInfo(BuildMode).@"enum".fields) |f| { const build_mode = @field(BuildMode, f.name); - inline for ([2]u1{ 0, 1 }) |has_debug_info| { + inline for ([2]bool{ false, true }) |has_debug_info| { var tmp_dir = std.testing.tmpDir(.{}); defer tmp_dir.cleanup(); const base_path = lib.global.arena.join_string(&.{ ".zig-cache/tmp/", &tmp_dir.sub_path, "/", name }); @@ -61,11 +61,11 @@ const InvokeWrapper = struct { }; // We invoke a function with comptime parameters so it's easily visible in CI stack traces -fn invoke_wrapper(options: InvokeWrapper, comptime build_mode: BuildMode, comptime has_debug_info: u1) void { +fn invoke_wrapper(options: InvokeWrapper, comptime build_mode: BuildMode, comptime has_debug_info: bool) void { return invoke_single(options, build_mode, has_debug_info); } -fn invoke_single(options: InvokeWrapper, build_mode: BuildMode, has_debug_info: u1) void { +fn invoke_single(options: InvokeWrapper, build_mode: BuildMode, has_debug_info: bool) void { const file_content = lib.file.read(lib.global.arena, options.file_path); converter.convert(.{ diff --git a/src/lib.zig b/src/lib.zig index c396c39..44524e4 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -518,11 +518,25 @@ pub const os = struct { }, }; } + + pub fn absolute_path_stack(noalias buffer: []u8, noalias relative_path: [*:0]const u8) [:0]const u8 { + const real_path = libc.realpath(relative_path, buffer.ptr) orelse unreachable; + const result = cstring.to_slice(real_path); + assert(result.len < buffer.len); + return result; + } + + pub fn absolute_path(arena: *Arena, noalias relative_path: [*:0]const u8) [:0]const u8 { + // TODO: pick a more correct number + var buffer: [4096]u8 = undefined; + return arena.duplicate_string(absolute_path_stack(&buffer, relative_path)); + } }; pub const libc = struct { pub extern "c" fn exit(c_int) noreturn; pub extern "c" fn memcmp(a: [*]const u8, b: [*]const u8, byte_count: usize) c_int; + pub extern "c" fn realpath(noalias path: [*:0]const u8, noalias resolved_path: [*]u8) ?[*:0]const u8; }; pub const string = struct { diff --git a/src/llvm.cpp b/src/llvm.cpp index add167f..61cd3f3 100644 --- a/src/llvm.cpp +++ b/src/llvm.cpp @@ -65,6 +65,12 @@ EXPORT bool llvm_type_is_integer(const Type& type) return result; } +EXPORT unsigned llvm_integer_type_get_bit_count(const IntegerType& integer_type) +{ + auto result = integer_type.getBitWidth(); + return result; +} + EXPORT Function* llvm_module_create_function(Module* module, FunctionType* function_type, GlobalValue::LinkageTypes linkage_type, unsigned address_space, BBLLVMString name) { auto* function = Function::Create(function_type, linkage_type, address_space, name.string_ref(), module); diff --git a/src/llvm_api.zig b/src/llvm_api.zig index 116fbaf..4adf863 100644 --- a/src/llvm_api.zig +++ b/src/llvm_api.zig @@ -37,6 +37,8 @@ pub extern fn llvm_builder_create_alloca(builder: *llvm.Builder, ty: *llvm.Type, pub extern fn LLVMBuildStore(builder: *llvm.Builder, value: *llvm.Value, pointer: *llvm.Value) *llvm.Value; pub extern fn LLVMBuildLoad2(builder: *llvm.Builder, ty: *llvm.Type, pointer: *llvm.Value, name: [*:0]const u8) *llvm.Value; +pub extern fn LLVMSetCurrentDebugLocation2(builder: *llvm.Builder, location: ?*llvm.DI.Location) void; + pub extern fn LLVMTypeOf(value: *llvm.Value) *llvm.Type; pub extern fn LLVMGlobalGetValueType(value: *llvm.GlobalValue) *llvm.Type; @@ -61,6 +63,8 @@ pub extern fn LLVMFP128TypeInContext(context: *llvm.Context) *llvm.Type; pub extern fn LLVMFunctionType(return_type: *llvm.Type, parameter_type_pointer: [*]const *llvm.Type, parameter_type_count: c_uint, is_var_arg: Bool) *llvm.Type.Function; pub extern fn LLVMIsFunctionVarArg(function_type: *llvm.Type.Function) Bool; pub extern fn LLVMGetReturnType(function_type: *llvm.Type.Function) *llvm.Type; +pub extern fn LLVMSetSubprogram(function: *llvm.Function, subprogram: *llvm.DI.Subprogram) void; +pub extern fn LLVMGetSubprogram(function: *llvm.Function) *llvm.DI.Subprogram; pub extern fn LLVMCountParamTypes(function_type: *llvm.Type.Function) c_uint; pub extern fn LLVMGetParamTypes(function_type: *llvm.Type.Function, types: [*]*llvm.Type) void; @@ -81,14 +85,24 @@ pub extern fn LLVMScalableVectorType(element_type: *llvm.Type, element_count: c_ pub extern fn llvm_type_is_function(ty: *llvm.Type) bool; pub extern fn llvm_type_is_integer(ty: *llvm.Type) bool; +pub extern fn llvm_integer_type_get_bit_count(integer_type: *llvm.Type.Integer) c_uint; + // VALUES pub extern fn LLVMConstInt(type: *llvm.Type.Integer, value: c_ulonglong, sign_extend: Bool) *llvm.Constant.Integer; // Debug info API pub extern fn LLVMCreateDIBuilder(module: *llvm.Module) *llvm.DI.Builder; +pub extern fn LLVMDIBuilderFinalize(builder: *llvm.DI.Builder) void; pub extern fn LLVMDIBuilderCreateFile(builder: *llvm.DI.Builder, file_name: llvm.String, directory_name: llvm.String) *llvm.DI.File; -pub extern fn LLVMDIBuilderCreateCompileUnit(builder: *llvm.DI.Builder, language: llvm.DwarfSourceLanguage, file: *llvm.DI.File, producer_name: llvm.String, optimized: Bool, flags: llvm.String, runtime_version: c_uint, split_name: llvm.String, dwarf_emission_kind: llvm.DwarfEmissionKind, debug_with_offset_id: c_uint, split_debug_inlining: Bool, debug_info_for_profiling: Bool, sysroot: llvm.String, sdk: llvm.String) *llvm.DI.CompileUnit; +pub extern fn LLVMDIBuilderCreateCompileUnit(builder: *llvm.DI.Builder, language: llvm.Dwarf.SourceLanguage, file: *llvm.DI.File, producer_name: llvm.String, optimized: Bool, flags: llvm.String, runtime_version: c_uint, split_name: llvm.String, dwarf_emission_kind: llvm.Dwarf.EmissionKind, debug_with_offset_id: c_uint, split_debug_inlining: Bool, debug_info_for_profiling: Bool, sysroot: llvm.String, sdk: llvm.String) *llvm.DI.CompileUnit; +pub extern fn LLVMDIBuilderCreateSubroutineType(builder: *llvm.DI.Builder, file: *llvm.DI.File, parameter_type_pointer: [*]const *llvm.DI.Type, parameter_type_count: c_uint, flags: llvm.DI.Flags) *llvm.DI.Type.Subroutine; pub extern fn LLVMDIBuilderCreateFunction(builder: *llvm.DI.Builder, scope: *llvm.DI.Scope, name: llvm.String, linkage_name: llvm.String, file: *llvm.DI.File, line_number: c_uint, type: *llvm.DI.Type.Subroutine, local_to_unit: Bool, is_definition: Bool, scope_line: c_uint, flags: llvm.DI.Flags, is_optimized: Bool) *llvm.DI.Subprogram; +pub extern fn LLVMDIBuilderFinalizeSubprogram(builder: *llvm.DI.Builder, subprogram: *llvm.DI.Subprogram) void; +pub extern fn LLVMDIBuilderCreateExpression(builder: *llvm.DI.Builder, address: ?[*]const u64, length: u64) *llvm.DI.Expression; +pub extern fn LLVMDIBuilderCreateDebugLocation(context: *llvm.Context, line: c_uint, column: c_uint, scope: *llvm.DI.Scope, inlined_at: ?*llvm.DI.Metadata) *llvm.DI.Location; +pub extern fn LLVMDIBuilderCreateBasicType(builder: *llvm.DI.Builder, name_pointer: [*]const u8, name_length: usize, bit_count: u64, dwarf_type: llvm.Dwarf.Type, flags: llvm.DI.Flags) *llvm.DI.Type; +pub extern fn LLVMDIBuilderCreateAutoVariable(builder: *llvm.DI.Builder, scope: *llvm.DI.Scope, name_pointer: [*]const u8, name_length: usize, file: *llvm.DI.File, line: c_uint, type: *llvm.DI.Type, always_preserve: Bool, flags: llvm.DI.Flags, align_in_bits: u32) *llvm.DI.LocalVariable; +pub extern fn LLVMDIBuilderInsertDeclareRecordAtEnd(builder: *llvm.DI.Builder, storage: *llvm.Value, local_variable: *llvm.DI.LocalVariable, expression: *llvm.DI.Expression, debug_location: *llvm.DI.Location, basic_block: *llvm.BasicBlock) *llvm.DI.Record; // Target pub extern fn llvm_default_target_triple() llvm.String; diff --git a/src/main.zig b/src/main.zig index 36a45d1..8bc2e34 100644 --- a/src/main.zig +++ b/src/main.zig @@ -142,19 +142,19 @@ pub fn main(argc: c_int, argv: [*:null]const ?[*:0]const u8) callconv(.C) c_int lib.print_string("Failed to match argument count"); return 1; } - const file_path_pointer = argv[1] orelse return 1; - const file_path = lib.cstring.to_slice(file_path_pointer); - if (file_path.len < 5) { + const relative_file_path_pointer = argv[1] orelse return 1; + const relative_file_path = lib.cstring.to_slice(relative_file_path_pointer); + if (relative_file_path.len < 5) { return 1; } - const extension_start = lib.string.last_character(file_path, '.') orelse return 1; - if (!lib.string.equal(file_path[extension_start..], ".bbb")) { + const extension_start = lib.string.last_character(relative_file_path, '.') orelse return 1; + if (!lib.string.equal(relative_file_path[extension_start..], ".bbb")) { return 1; } - const separator_index = lib.string.last_character(file_path, '/') orelse 0; - const base_start = separator_index + @intFromBool(separator_index != 0 or file_path[separator_index] == '/'); - const base_name = file_path[base_start..extension_start]; + const separator_index = lib.string.last_character(relative_file_path, '/') orelse 0; + const base_start = separator_index + @intFromBool(separator_index != 0 or relative_file_path[separator_index] == '/'); + const base_name = relative_file_path[base_start..extension_start]; lib.GlobalState.initialize(); @@ -166,7 +166,8 @@ pub fn main(argc: c_int, argv: [*:null]const ?[*:0]const u8) callconv(.C) c_int const output_object_path = arena.join_string(&.{ output_path_base, ".o" }); const output_executable_path = output_path_base; - const file_content = lib.file.read(arena, file_path); + const file_content = lib.file.read(arena, relative_file_path); + const file_path = os.absolute_path(arena, relative_file_path); converter.convert(.{ .executable = output_executable_path, .object = output_object_path, @@ -174,7 +175,7 @@ pub fn main(argc: c_int, argv: [*:null]const ?[*:0]const u8) callconv(.C) c_int .build_mode = .debug_none, .content = file_content, .path = file_path, - .has_debug_info = 1, + .has_debug_info = true, }); return 0; }