diff --git a/spec/compiler/codegen/debug_spec.cr b/spec/compiler/codegen/debug_spec.cr index 1903d1e58601..4a57056fc7a3 100644 --- a/spec/compiler/codegen/debug_spec.cr +++ b/spec/compiler/codegen/debug_spec.cr @@ -16,6 +16,33 @@ describe "Code gen: debug" do ), debug: Crystal::Debug::All) end + it "codegens lib union (#7335)" do + codegen <<-CRYSTAL, debug: Crystal::Debug::All + lib Foo + union Bar + a : Int32 + b : Int16 + c : Int8 + end + end + + x = Foo::Bar.new + CRYSTAL + end + + it "codegens extern union (#7335)" do + codegen <<-CRYSTAL, debug: Crystal::Debug::All + @[Extern(union: true)] + struct Foo + @a = uninitialized Int32 + @b = uninitialized Int16 + @c = uninitialized Int8 + end + + x = Foo.new + CRYSTAL + end + it "inlines instance var access through getter in debug mode" do run(%( struct Bar diff --git a/spec/compiler/codegen/offsetof_spec.cr b/spec/compiler/codegen/offsetof_spec.cr index fa2a0dbe8b37..f7e7971be6b2 100644 --- a/spec/compiler/codegen/offsetof_spec.cr +++ b/spec/compiler/codegen/offsetof_spec.cr @@ -39,4 +39,21 @@ describe "Code gen: offsetof" do run(code).to_b.should be_true end + + it "returns offset of extern union" do + run(<<-CRYSTAL).to_b.should be_true + @[Extern(union: true)] + struct Foo + @x = 1.0_f32 + @y = uninitialized UInt32 + + def y + @y + end + end + + f = Foo.new + (pointerof(f).as(Void*) + offsetof(Foo, @y).to_i64).as(UInt32*).value == f.y + CRYSTAL + end end diff --git a/spec/debug/extern_unions.cr b/spec/debug/extern_unions.cr new file mode 100644 index 000000000000..66943926dcff --- /dev/null +++ b/spec/debug/extern_unions.cr @@ -0,0 +1,17 @@ +@[Extern(union: true)] +struct Foo + @x : Float32 + @y = uninitialized UInt32 + @z = uninitialized UInt8[4] + + def initialize(@x) + end +end + +raise "wrong endianness" unless IO::ByteFormat::SystemEndian == IO::ByteFormat::LittleEndian + +x = Foo.new(1.0_f32) +# print: x +# lldb-check: $0 = (x = 1065353216, y = 1, z = "\0\0\x80?") +# gdb-check: $1 = {x = 1065353216, y = 1, z = "\000\000\200?"} +debugger diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 562e52eaf1ff..3b3a81757bb8 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -105,10 +105,13 @@ module Crystal end def offset_of(type, element_index) + return 0_u64 if type.extern_union? llvm_typer.offset_of(llvm_typer.llvm_type(type), element_index) end def instance_offset_of(type, element_index) + # extern unions must be value types, which always use the above + # `offset_of` instead llvm_typer.offset_of(llvm_typer.llvm_struct_type(type), element_index + 1) end end diff --git a/src/compiler/crystal/codegen/debug.cr b/src/compiler/crystal/codegen/debug.cr index a363e994d1dd..df1b33b23bd8 100644 --- a/src/compiler/crystal/codegen/debug.cr +++ b/src/compiler/crystal/codegen/debug.cr @@ -82,12 +82,20 @@ module Crystal @debug_files_per_module[@llvm_mod] ||= {} of DebugFilename => LibLLVM::MetadataRef end - def current_debug_file - filename = @current_debug_location.try(&.filename) || "??" - debug_files_cache[filename] ||= begin - file, dir = file_and_dir(filename) - di_builder.create_file(file, dir) - end + private def current_debug_file + # These debug files are only used for `DIBuilder#create_union_type`, even + # though they are unneeded here, just as struct types don't need a file; + # LLVM 12 or below produces an assertion failure that is now removed + # (https://github.com/llvm/llvm-project/commit/ad60802a7187aa39b0374536be3fa176fe3d6256) + {% if LibLLVM::IS_LT_130 %} + filename = @current_debug_location.try(&.filename) || "??" + debug_files_cache[filename] ||= begin + file, dir = file_and_dir(filename) + di_builder.create_file(file, dir) + end + {% else %} + Pointer(Void).null.as(LibLLVM::MetadataRef) + {% end %} end def get_debug_type(type, original_type : Type) @@ -148,21 +156,23 @@ module Crystal ivars.each_with_index do |(name, ivar), idx| next if ivar.type.is_a?(NilType) if (ivar_type = ivar.type?) && (ivar_debug_type = get_debug_type(ivar_type)) - offset = @program.target_machine.data_layout.offset_of_element(struct_type, idx &+ (type.struct? ? 0 : 1)) + offset = type.extern_union? ? 0_u64 : @program.target_machine.data_layout.offset_of_element(struct_type, idx &+ (type.struct? ? 0 : 1)) size = @program.target_machine.data_layout.size_in_bits(llvm_embedded_type(ivar_type)) - # FIXME structs like LibC::PthreadMutexT generate huge offset values - next if offset > UInt64::MAX // 8u64 - member = di_builder.create_member_type(nil, name[1..-1], nil, 1, size, size, 8u64 * offset, LLVM::DIFlags::Zero, ivar_debug_type) element_types << member end end size = @program.target_machine.data_layout.size_in_bits(struct_type) - debug_type = di_builder.create_struct_type(nil, original_type.to_s, nil, 1, size, size, LLVM::DIFlags::Zero, nil, di_builder.get_or_create_type_array(element_types)) - unless type.struct? - debug_type = di_builder.create_pointer_type(debug_type, 8u64 * llvm_typer.pointer_size, 8u64 * llvm_typer.pointer_size, original_type.to_s) + elements = di_builder.get_or_create_type_array(element_types) + if type.extern_union? + debug_type = di_builder.create_union_type(nil, original_type.to_s, current_debug_file, 1, size, size, LLVM::DIFlags::Zero, elements) + else + debug_type = di_builder.create_struct_type(nil, original_type.to_s, nil, 1, size, size, LLVM::DIFlags::Zero, nil, elements) + unless type.struct? + debug_type = di_builder.create_pointer_type(debug_type, 8u64 * llvm_typer.pointer_size, 8u64 * llvm_typer.pointer_size, original_type.to_s) + end end di_builder.replace_temporary(tmp_debug_type, debug_type) debug_type @@ -257,7 +267,7 @@ module Crystal if ivar_debug_type = get_debug_type(ivar_type) offset = @program.target_machine.data_layout.offset_of_element(struct_type, idx &+ (type.struct? ? 0 : 1)) size = @program.target_machine.data_layout.size_in_bits(llvm_embedded_type(ivar_type)) - next if offset > UInt64::MAX // 8u64 # TODO: Figure out why it is happening sometimes with offset + member = di_builder.create_member_type(nil, "[#{idx}]", nil, 1, size, size, 8u64 * offset, LLVM::DIFlags::Zero, ivar_debug_type) element_types << member end @@ -286,9 +296,6 @@ module Crystal offset = @program.target_machine.data_layout.offset_of_element(struct_type, idx &+ (type.struct? ? 0 : 1)) size = @program.target_machine.data_layout.size_in_bits(llvm_embedded_type(ivar_type)) - # FIXME structs like LibC::PthreadMutexT generate huge offset values - next if offset > UInt64::MAX // 8u64 - member = di_builder.create_member_type(nil, ivar.name, nil, 1, size, size, 8u64 * offset, LLVM::DIFlags::Zero, ivar_debug_type) element_types << member end diff --git a/src/llvm/target_data.cr b/src/llvm/target_data.cr index 150e193bc7cb..ed8d0c07ed91 100644 --- a/src/llvm/target_data.cr +++ b/src/llvm/target_data.cr @@ -23,6 +23,8 @@ struct LLVM::TargetData end def offset_of_element(struct_type, element) + # element_count = LibLLVM.count_struct_element_types(struct_type) + # raise "Invalid element idx!" unless element < element_count LibLLVM.offset_of_element(self, struct_type, element) end