From cf5f1812286d8c1e5b2f60fef5823b91b6c35776 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Wed, 19 Aug 2026 16:30:37 -0700 Subject: [PATCH] peel union constant to its variant --- src/llvm_backend.cpp | 2 +- src/llvm_backend_const.cpp | 44 ++++++++++++----------- src/llvm_backend_expr.cpp | 10 +++++- tests/internal/test_union_const.odin | 54 ++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 22 deletions(-) create mode 100644 tests/internal/test_union_const.odin diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 1be53e5de..9cd3d1c9a 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -3439,7 +3439,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { cc.link_section = e->Variable.link_section; ExactValue v = tav.value; - lbValue init = lb_const_value(m, e->type, v, tav.type, cc); + lbValue init = lb_const_value(m, e->type, v, lb_build_expr_original_const_type(decl->init_expr), cc); LLVMDeleteGlobal(g.value); diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 4e26cb32f..bfb2c3c34 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -620,6 +620,10 @@ gb_internal LLVMValueRef lb_fill_fixed_capacity_dynamic_array(lbModule *m, i64 e return llvm_const_named_struct(m, original_type, svalues, svalue_count); } +// Defined in llvm_backend_expr.cpp. `U(3)` records the union as the expression's type, so a constant +// built from one has to be peeled back to the variant the checker resolved it to +gb_internal Type *lb_build_expr_original_const_type(Ast *expr); + gb_internal lbValue lb_const_value_bit_field(lbModule *m, Type *type, Ast *value_compound) { ast_node(cl, CompoundLit, value_compound); @@ -1411,7 +1415,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty } if (lo == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, tav.type, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value; for (i64 k = lo; k < hi; k++) { aos_values[value_index++] = val; } @@ -1426,7 +1430,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty i64 index = exact_value_to_i64(index_tav.value); if (index == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, tav.type, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value; aos_values[value_index++] = val; found = true; break; @@ -1481,7 +1485,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty for (isize i = 0; i < elem_count; i++) { TypeAndValue tav = cl->elems[i]->tav; GB_ASSERT(tav.mode != Addressing_Invalid); - aos_values[i] = lb_const_value(m, elem_type, tav.value, tav.type, cc).value; + aos_values[i] = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(cl->elems[i]), cc).value; } for (isize i = elem_count; i < type->Struct.soa_count; i++) { aos_values[i] = nullptr; @@ -1560,7 +1564,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty } if (lo == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, tav.type, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value; for (i64 k = lo; k < hi; k++) { values[value_index++] = val; } @@ -1575,7 +1579,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty i64 index = exact_value_to_i64(index_tav.value); if (index == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, tav.type, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value; values[value_index++] = val; found = true; break; @@ -1603,7 +1607,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty if (is_type_tuple(tav.type)) { elem_index += tav.type->Tuple.variables.count; } else { - values[elem_index++] = lb_const_value(m, elem_type, tav.value, tav.type, cc).value; + values[elem_index++] = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(cl->elems[i]), cc).value; } } for (isize i = 0; i < type->Array.count; i++) { @@ -1652,7 +1656,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty } if (lo == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, tav.type, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value; for (i64 k = lo; k < hi; k++) { values[value_index++] = val; } @@ -1667,7 +1671,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty i64 index = exact_value_to_i64(index_tav.value); if (index == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, tav.type, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value; values[value_index++] = val; found = true; break; @@ -1695,7 +1699,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty if (is_type_tuple(tav.type)) { elem_index += tav.type->Tuple.variables.count; } else { - values[elem_index++] = lb_const_value(m, elem_type, tav.value, tav.type, cc).value; + values[elem_index++] = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(cl->elems[i]), cc).value; } } for (isize i = 0; i < type->EnumeratedArray.count; i++) { @@ -1744,7 +1748,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty if (lo == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, tav.type, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value; for (i64 k = lo; k < hi; k++) { values[value_index++] = val; } @@ -1762,7 +1766,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty if (index == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, tav.type, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value; values[value_index++] = val; found = true; break; @@ -1804,7 +1808,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty if (is_type_tuple(tav.type)) { elem_index += tav.type->Tuple.variables.count; } else { - values[elem_index++] = lb_const_value(m, elem_type, tav.value, tav.type, cc).value; + values[elem_index++] = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(cl->elems[i]), cc).value; } } for (isize i = 0; i < capacity; i++) { @@ -1852,7 +1856,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty } if (lo == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, tav.type, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value; for (i64 k = lo; k < hi; k++) { values[value_index++] = val; } @@ -1867,7 +1871,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty i64 index = exact_value_to_i64(index_tav.value); if (index == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, tav.type, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value; values[value_index++] = val; found = true; break; @@ -1886,7 +1890,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty for (isize i = 0; i < elem_count; i++) { TypeAndValue tav = cl->elems[i]->tav; GB_ASSERT(tav.mode != Addressing_Invalid); - values[i] = lb_const_value(m, elem_type, tav.value, tav.type, cc).value; + values[i] = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(cl->elems[i]), cc).value; } LLVMTypeRef et = lb_type(m, elem_type); @@ -1971,7 +1975,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty i32 index = field_remapping[f->Variable.field_index]; if (elem_type_can_be_constant(f->type)) { if (sel.index.count == 1) { - lbValue value = lb_const_value(m, f->type, tav.value, tav.type, cc); + lbValue value = lb_const_value(m, f->type, tav.value, lb_build_expr_original_const_type(fv->value), cc); LLVMTypeRef value_type = LLVMTypeOf(value.value); GB_ASSERT_MSG(lb_sizeof(value_type) == type_size_of(f->type), "%s vs %s", LLVMPrintTypeToString(value_type), type_to_string(f->type)); values[index] = value.value; @@ -2081,7 +2085,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty if (elem_type_can_be_constant(f->type)) { - lbValue value = lb_const_value(m, f->type, tav.value, tav.type, cc); + lbValue value = lb_const_value(m, f->type, tav.value, lb_build_expr_original_const_type(cl->elems[i]), cc); LLVMTypeRef value_type = LLVMTypeOf(value.value); isize lb_sizeof_value_type = lb_sizeof(value_type); isize type_size_of_f_type = type_size_of(f->type); @@ -2222,7 +2226,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, tav.type, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value; for (i64 k = lo; k < hi; k++) { i64 offset = matrix_row_major_index_to_offset(type, k); GB_ASSERT(values[offset] == nullptr); @@ -2234,7 +2238,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty i64 index = exact_value_to_i64(index_tav.value); GB_ASSERT(index < max_count); TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, tav.type, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value; i64 offset = matrix_row_major_index_to_offset(type, index); GB_ASSERT(values[offset] == nullptr); values[offset] = val; @@ -2258,7 +2262,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty GB_ASSERT(tav.mode != Addressing_Invalid); i64 offset = 0; offset = matrix_row_major_index_to_offset(type, i); - values[offset] = lb_const_value(m, elem_type, tav.value, tav.type, cc).value; + values[offset] = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(cl->elems[i]), cc).value; } for (isize i = 0; i < total_count; i++) { if (values[i] == nullptr) { diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 0a2265464..3783a190d 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4471,6 +4471,14 @@ gb_internal Type *lb_build_expr_original_const_type(Ast *expr) { Type *res = lb_build_expr_original_const_type(expr->CallExpr.args[0]); return res; } + } else if (expr->kind == Ast_Ident || expr->kind == Ast_SelectorExpr) { + // a named constant carries the union as its type, so follow it to the declaration the + // checker resolved: `C :: U(3)` is the variant `int`, not `U` + Entity *e = entity_of_node(expr); + if (e != nullptr && e->kind == Entity_Constant && + e->decl_info != nullptr && e->decl_info->init_expr != nullptr) { + return lb_build_expr_original_const_type(e->decl_info->init_expr); + } } } return type_of_expr(expr); @@ -4850,7 +4858,7 @@ gb_internal lbAddr lb_build_addr_from_entity(lbProcedure *p, Entity *e, Ast *exp GB_ASSERT(e != nullptr); if (e->kind == Entity_Constant) { Type *t = default_type(type_of_expr(expr)); - lbValue v = lb_const_value(p->module, t, e->Constant.value, e->type, LB_CONST_CONTEXT_DEFAULT_NO_LOCAL); + lbValue v = lb_const_value(p->module, t, e->Constant.value, lb_build_expr_original_const_type(expr), LB_CONST_CONTEXT_DEFAULT_NO_LOCAL); if (LLVMIsConstant(v.value)) { lbAddr g = lb_add_global_generated_from_procedure(p, t, v); return g; diff --git a/tests/internal/test_union_const.odin b/tests/internal/test_union_const.odin new file mode 100644 index 000000000..3f3260993 --- /dev/null +++ b/tests/internal/test_union_const.odin @@ -0,0 +1,54 @@ +package test_internal + +import "core:testing" + +// `U(3)` records the union as the constant's type, so the backend has to peel it back to the variant +// the checker resolved. It could not, and panicked in lb_const_value. The variant cannot be guessed +// from the value's kind either -- `W(i16(300))` below is an integer, and picking the first integer +// variant would silently choose i8 +@(test) +union_constant_selects_the_declared_variant :: proc(t: ^testing.T) { + U :: union { string, int, f32 } + W :: union { i8, i16, i32 } + E :: enum { None, Bad } + EU :: union { E, string } + + CI :: U(int(3)) + CS :: U("s") + CF :: U(f32(1.5)) + CW :: W(i16(300)) + CE :: EU(E.Bad) + + ci := CI + cs := CS + cf := CF + cw := CW + ce := CE + + if v, ok := ci.(int); testing.expect(t, ok, "CI is not the int variant") { testing.expect_value(t, v, 3) } + if v, ok := cs.(string); testing.expect(t, ok, "CS is not the string variant") { testing.expect_value(t, v, "s") } + if v, ok := cf.(f32); testing.expect(t, ok, "CF is not the f32 variant") { testing.expect_value(t, v, f32(1.5)) } + // i16, not the first integer variant + if v, ok := cw.(i16); testing.expect(t, ok, "CW is not the i16 variant") { testing.expect_value(t, v, i16(300)) } + if v, ok := ce.(E); testing.expect(t, ok, "CE is not the enum variant") { testing.expect(t, v == E.Bad, "wrong enum value") } + + // the same constant reached through an aggregate + S :: struct { u: U, n: int } + s := S{CI, 7} + if v, ok := s.u.(int); testing.expect(t, ok, "struct field lost its variant") { testing.expect_value(t, v, 3) } + testing.expect_value(t, s.n, 7) + + arr := [2]U{U(int(1)), U("x")} + if v, ok := arr[0].(int); testing.expect(t, ok, "array element 0 lost its variant") { testing.expect_value(t, v, 1) } + if v, ok := arr[1].(string); testing.expect(t, ok, "array element 1 lost its variant") { testing.expect_value(t, v, "x") } +} + +@(private="file") U_G :: union { string, int } +@(private="file") G := U_G(int(3)) +@(private="file") GS := U_G("hello") + +@(test) +union_constant_as_a_global_initializer :: proc(t: ^testing.T) { + if v, ok := G.(int); testing.expect(t, ok, "global lost its variant") { testing.expect_value(t, v, 3) } + if v, ok := GS.(string); testing.expect(t, ok, "string global lost its variant") { testing.expect_value(t, v, "hello") } +}