From c47a0e9df71d3d7ec21799c8aac7ccdc61a7b3e2 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sat, 1 Oct 2022 09:22:46 -0300 Subject: [PATCH 1/2] Codegen: fix how unions are represented to not miss bytes --- src/compiler/crystal/codegen/llvm_typer.cr | 33 ++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/codegen/llvm_typer.cr b/src/compiler/crystal/codegen/llvm_typer.cr index 8d1eedbe026c..edc65e60e20d 100644 --- a/src/compiler/crystal/codegen/llvm_typer.cr +++ b/src/compiler/crystal/codegen/llvm_typer.cr @@ -327,6 +327,33 @@ module Crystal @structs[llvm_name] = a_struct end + # We are going to represent the union like this: + # 1. Find out what's the type with the largest alignment + # 2. Find out what's the type's size + # 3. Have the first member of the union be an array + # of ints that match that alignemnt, up to that type's + # size, followed by another member that is just bytes + # to fill the rest of the union's size. + # + # So for example if we have this: + # + # struct Foo + # x : Int8 + # y : Int32 + # end + # + # union Bar + # foo : Foo + # padding : UInt8[24] + # end + # + # We have that for Bar, the largest alignment of its types + # is 4 (for Foo's Int32). Foo's size is 8 bytes (4 for x, 4 for y). + # Then for the first union member we'll have [2 x i32]. + # The total size of the union is 24 bytes. We already filled + # 8 bytes so we still need 16 bytes. + # The resulting union is { [2 x i32], [16 x i8] }. + max_size = 0 max_align = 0 max_align_type = nil @@ -351,8 +378,10 @@ module Crystal end end - max_align_type = max_align_type.not_nil! - union_fill = [max_align_type] of LLVM::Type + filler = @llvm_context.int(max_align * 8) + filler_size = max_align_type_size // max_align + + union_fill = [filler.array(filler_size)] of LLVM::Type if max_align_type_size < max_size union_fill << @llvm_context.int8.array(max_size - max_align_type_size) end From cb50185d8e4ba8e81687d40cceabd9a370415f3d Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sat, 1 Oct 2022 10:26:32 -0300 Subject: [PATCH 2/2] Add spec --- spec/compiler/codegen/c_union_spec.cr | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/spec/compiler/codegen/c_union_spec.cr b/spec/compiler/codegen/c_union_spec.cr index 2e23d860ef93..2870efd5833f 100644 --- a/spec/compiler/codegen/c_union_spec.cr +++ b/spec/compiler/codegen/c_union_spec.cr @@ -214,4 +214,30 @@ describe "Code gen: c union" do foo.read_int )).to_i.should eq(42) end + + it "moves unions around correctly (#12550)" do + run(%( + require "prelude" + + lib Lib + struct Foo + x : UInt8 + y : UInt16 + end + + union Bar + foo : Foo + padding : UInt8[6] # larger than `Foo` + end + end + + def foo + a = uninitialized Lib::Bar + a.padding.fill { |i| 1_u8 + i } + a + end + + foo.padding.sum + )).to_i.should eq((1..6).sum) + end end