diff --git a/spec/compiler/codegen/cast_spec.cr b/spec/compiler/codegen/cast_spec.cr index 32b265ea0415..fbe6cd78ac87 100644 --- a/spec/compiler/codegen/cast_spec.cr +++ b/spec/compiler/codegen/cast_spec.cr @@ -77,6 +77,33 @@ describe "Code gen: cast" do )).to_b.should be_true end + it "upcasts from union to union with different alignment" do + run(<<-CRYSTAL).to_i.should eq(1) + require "prelude" + + a = 1 || 2_i64 + a.as(Int32 | Int64 | Int128) + CRYSTAL + end + + it "downcasts from union to union with different alignment" do + run(<<-CRYSTAL).to_i.should eq(1) + require "prelude" + + a = 1 || 2_i64 || 3_i128 + a.as(Int32 | Int64) + CRYSTAL + end + + it "sidecasts from union to union with different alignment" do + run(<<-CRYSTAL).to_i.should eq(1) + require "prelude" + + a = 1 || 2_i64 + a.as(Int32 | Int128) + CRYSTAL + end + it "casts from virtual to single type" do run(%( require "prelude" diff --git a/spec/compiler/codegen/sizeof_spec.cr b/spec/compiler/codegen/sizeof_spec.cr index 8139db8bc426..e90553d0b3c1 100644 --- a/spec/compiler/codegen/sizeof_spec.cr +++ b/spec/compiler/codegen/sizeof_spec.cr @@ -279,6 +279,16 @@ describe "Code gen: sizeof" do alignof(Foo) CRYSTAL end + + it "gets alignof union" do + run("alignof(Int32 | Int8)").to_i.should eq(8) + run("alignof(Int32 | Int64)").to_i.should eq(8) + end + + it "alignof mixed union is not less than alignof its variant types" do + # NOTE: `alignof(Int128) == 16` is not guaranteed + run("alignof(Int32 | Int128) >= alignof(Int128)").to_b.should be_true + end end describe "instance_alignof" do diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index e7128302a66e..441f3cfa09ae 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -2253,11 +2253,11 @@ module Crystal res end - def memcpy(dest, src, len, align, volatile) + def memcpy(dest, src, len, align, volatile, *, src_align = align) res = call c_memcpy_fun, [dest, src, len, volatile] LibLLVM.set_instr_param_alignment(res, 1, align) - LibLLVM.set_instr_param_alignment(res, 2, align) + LibLLVM.set_instr_param_alignment(res, 2, src_align) res end diff --git a/src/compiler/crystal/codegen/unions.cr b/src/compiler/crystal/codegen/unions.cr index 1f29763d504a..7ac58ff84887 100644 --- a/src/compiler/crystal/codegen/unions.cr +++ b/src/compiler/crystal/codegen/unions.cr @@ -28,20 +28,19 @@ module Crystal @structs[llvm_name] = a_struct end - max_size = 0 + max_size = 0_u64 + max_alignment = pointer_size.to_u32! + type.expand_union_types.each do |subtype| unless subtype.void? - size = size_of(llvm_type(subtype, wants_size: true)) - max_size = size if size > max_size + llvm_type = llvm_type(subtype, wants_size: true) + max_size = {size_of(llvm_type), max_size}.max + max_alignment = {align_of(llvm_type), max_alignment}.max end end - max_size /= pointer_size.to_f - max_size = max_size.ceil.to_i - - max_size = 1 if max_size == 0 - - llvm_value_type = size_t.array(max_size) + value_size = {(max_size + (max_alignment - 1)) // max_alignment, 1_u64}.max + llvm_value_type = @llvm_context.int(max_alignment * 8).array(value_size) [@llvm_context.int32, llvm_value_type] end @@ -109,23 +108,60 @@ module Crystal store type_id(@program.void), union_type_id(struct_type, union_pointer) end - def assign_distinct_union_types(target_pointer, target_type, value_type, value) + # this is needed if `union_type` and `value_type` have different alignments, + # i.e. their `#union_value`s do not have the same offsets + def store_union_in_union(union_type, union_pointer, value_type, value) + to_llvm_type = llvm_type(union_type) + from_llvm_type = llvm_type(value_type) + union_value_type = from_llvm_type.struct_element_types[1] + + store type_id(value, value_type), union_type_id(to_llvm_type, union_pointer) + + size = @llvm_typer.size_of(union_value_type) + size = @program.bits64? ? int64(size) : int32(size) + memcpy( + cast_to_void_pointer(union_value(to_llvm_type, union_pointer)), + cast_to_void_pointer(union_value(from_llvm_type, value)), + size, + align: @llvm_typer.align_of(to_llvm_type.struct_element_types[1]), + src_align: @llvm_typer.align_of(union_value_type), + volatile: int1(0), + ) + end + + def assign_distinct_union_types(to_pointer, to_type, from_type, from_pointer) # If we have: - # - target_pointer: Pointer(A | B | C) - # - target_type: A | B | C - # - value_type: A | B - # - value: Pointer(A | B) + # - to_pointer: Pointer(A | B | C) + # - to_type: A | B | C + # - from_type: A | B + # - from_pointer: Pointer(A | B) # - # Then we: - # - load the value, we get A | B - # - cast the target pointer to Pointer(A | B) - # - store the A | B from the first pointer into the casted target pointer - casted_target_pointer = cast_to_pointer target_pointer, value_type - store load(llvm_type(value_type), value), casted_target_pointer + # Then it might happen that from_type and to_type have the same alignment. + # In this case, the two pointers are interchangeable, so we can simply: + if align_of(to_type) == align_of(from_type) + # - load the value, we get A | B + # - cast the target pointer to Pointer(A | B) + # - store the A | B from the value pointer into the casted target pointer + casted_target_pointer = cast_to_pointer to_pointer, from_type + store load(llvm_type(from_type), from_pointer), casted_target_pointer + else + # Otherwise, the type ID and the value must be stored separately + store_union_in_union to_type, to_pointer, from_type, from_pointer + end end def downcast_distinct_union_types(value, to_type : MixedUnionType, from_type : MixedUnionType) - cast_to_pointer value, to_type + # If from_type and to_type have the same alignment, we don't need a + # separate value; just cast the larger value pointer to the smaller one + if align_of(to_type) == align_of(from_type) + cast_to_pointer value, to_type + else + # This is the same as upcasting and we need that separate, newly aligned + # union value + target_pointer = alloca llvm_type(to_type) + store_union_in_union to_type, target_pointer, from_type, value + target_pointer + end end def upcast_distinct_union_types(value, to_type : MixedUnionType, from_type : MixedUnionType)