Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Codegen: fix how unions are represented to not miss bytes #12551

Merged
merged 2 commits into from
Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions spec/compiler/codegen/c_union_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
33 changes: 31 additions & 2 deletions src/compiler/crystal/codegen/llvm_typer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down