backend: a union constant in a ternary or builds an ill-typed phi

This commit is contained in:
kalsprite
2026-08-19 16:45:58 -07:00
parent 97b5078ad5
commit a13a4b418d
3 changed files with 60 additions and 3 deletions

View File

@@ -4605,6 +4605,9 @@ gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) {
if (is_type_internally_pointer_like(type)) {
incoming_values[0] = LLVMBuildBitCast(p->builder, incoming_values[0], llvm_type, "");
}
// A union constant is built as an anonymous packed struct, which a phi cannot accept
// alongside the named type it results in, even though the two are laid out identically
incoming_values[0] = OdinLLVMBuildTransmute(p, incoming_values[0], llvm_type);
lb_emit_jump(p, done);
lb_start_block(p, else_);
@@ -4614,6 +4617,7 @@ gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) {
if (is_type_internally_pointer_like(type)) {
incoming_values[1] = LLVMBuildBitCast(p->builder, incoming_values[1], llvm_type, "");
}
incoming_values[1] = OdinLLVMBuildTransmute(p, incoming_values[1], llvm_type);
lb_emit_jump(p, done);
lb_start_block(p, done);

View File

@@ -532,18 +532,22 @@ gb_internal lbValue lb_emit_or_else(lbProcedure *p, Ast *arg, Ast *else_expr, Ty
lb_emit_if(p, lb_emit_try_has_value(p, rhs), then, else_);
lb_start_block(p, then);
incoming_values[0] = lb_emit_conv(p, lhs, type).value;
LLVMTypeRef llvm_type = lb_type(p->module, type);
// A union constant is built as an anonymous packed struct, which a phi cannot accept
// alongside the named type it results in, even though the two are laid out identically
incoming_values[0] = OdinLLVMBuildTransmute(p, lb_emit_conv(p, lhs, type).value, llvm_type);
lb_emit_jump(p, done);
lb_start_block(p, else_);
incoming_values[1] = lb_emit_conv(p, lb_build_expr(p, else_expr), type).value;
incoming_values[1] = OdinLLVMBuildTransmute(p, lb_emit_conv(p, lb_build_expr(p, else_expr), type).value, llvm_type);
lb_emit_jump(p, done);
lb_start_block(p, done);
lbValue res = {};
res.value = LLVMBuildPhi(p->builder, lb_type(p->module, type), "");
res.value = LLVMBuildPhi(p->builder, llvm_type, "");
res.type = type;
GB_ASSERT(p->curr_block->preds.count >= 2);

View File

@@ -0,0 +1,49 @@
package test_internal
import "core:testing"
// A union constant is built as an anonymous packed struct, which is fine everywhere that adopts the
// value's own type -- a global takes it verbatim. A phi cannot: its incoming values must have the
// phi's own named type, even though the two layouts are identical. Both the ternary and `or_else`
// build one
@(test)
union_constant_in_a_ternary :: proc(t: ^testing.T) {
E :: enum { None, Bad, Worse }
EU :: union { E, string }
f := true
g := false
a: EU = EU(E.Worse) if f else nil
b: EU = EU("s") if f else nil
c: EU = nil if g else EU(E.Worse)
d: EU = EU(E.Worse) if f else EU(E.Bad)
e: EU = EU(E.Worse) if g else EU(E.Bad)
if v, ok := a.(E); testing.expect(t, ok, "a lost its variant") { testing.expect(t, v == E.Worse, "a") }
if v, ok := b.(string); testing.expect(t, ok, "b lost its variant") { testing.expect_value(t, v, "s") }
if v, ok := c.(E); testing.expect(t, ok, "c lost its variant") { testing.expect(t, v == E.Worse, "c") }
if v, ok := d.(E); testing.expect(t, ok, "d lost its variant") { testing.expect(t, v == E.Worse, "d") }
if v, ok := e.(E); testing.expect(t, ok, "e lost its variant") { testing.expect(t, v == E.Bad, "e") }
// the arm that is not taken must not decide the result
n: EU = EU(E.Bad) if g else nil
_, is_enum := n.(E)
testing.expect(t, !is_enum, "the untaken arm was selected")
}
@(test)
union_constant_in_or_else :: proc(t: ^testing.T) {
E :: enum { None, Bad }
EU :: union { E, string }
m: map[int]EU
defer delete(m)
missing := m[0] or_else EU(E.Bad)
if v, ok := missing.(E); testing.expect(t, ok, "or_else default lost its variant") { testing.expect(t, v == E.Bad, "default") }
m[1] = EU("here")
present := m[1] or_else EU(E.Bad)
if v, ok := present.(string); testing.expect(t, ok, "or_else present lost its variant") { testing.expect_value(t, v, "here") }
}