From 0d85caf21c6ab98d764f3a0bc776f91f857d2f0f Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Thu, 9 Oct 2025 12:45:55 +0200 Subject: [PATCH] Add splat defaults for designated initialization #2441. Add ??? and +++= to list-precedence. --- releasenotes.md | 2 + src/compiler/compiler.c | 4 +- src/compiler/compiler_internal.h | 12 +++- src/compiler/copying.c | 3 +- src/compiler/expr.c | 2 +- src/compiler/llvm_codegen_expr.c | 64 ++++++++++++++---- src/compiler/parse_expr.c | 16 ++++- src/compiler/sema_initializers.c | 54 ++++++++++++++- src/compiler/sema_liveness.c | 3 +- .../functions/splat_initializer.c3t | 66 +++++++++++++++++++ 10 files changed, 204 insertions(+), 22 deletions(-) create mode 100644 test/test_suite/functions/splat_initializer.c3t diff --git a/releasenotes.md b/releasenotes.md index 5cf36339a..24011fb10 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -4,6 +4,7 @@ ### Changes / improvements - Error when using $vaarg/$vacount/$vasplat and similar in a macro without vaargs #2510. +- Add splat defaults for designated initialization #2441. ### Fixes - Bug in `io::write_using_write_byte`. @@ -13,6 +14,7 @@ - Compiler segfault when accessing member of number cast to bitstruct #2516. - Compiler assert when getting a member of a `bitstruct : char @bigendian` #2517. - Incorrect visibility on local globals with public aliases. #2519 +- Add ??? and +++= to list-precedence. ### Stdlib changes - Sorting functions correctly took slices by value, but also other types by value. Now, only slices are accepted by value, other containers are always by ref. diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index ea7c732fc..c4188e181 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -1171,8 +1171,8 @@ void print_syntax(BuildOptions *options) puts(" 8. Relational | < > <= >= == !="); puts(" 9. And | && &&&"); puts("10. Or | || |||"); - puts("11. Ternary | ?: ??"); - puts("12. Assign | = *= /= %= -= += |= &= ^= <<= >>="); + puts("11. Ternary | ?: ?? ???"); + puts("12. Assign | = *= /= %= -= += |= &= ^= <<= >>= +++="); } } diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 6412058d6..84f81ab11 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -1179,6 +1179,12 @@ typedef struct bool is_ref; } ExprOtherContext; +typedef struct +{ + Expr **list; + Expr *splat; +} ExprDesignatedInit; + typedef struct { Expr *inner; @@ -1231,7 +1237,7 @@ struct Expr_ ExprIdentifierRaw ct_ident_expr; // 24 Decl *decl_expr; // 8 Decl *iota_decl_expr; // 8 - Expr **designated_init_list; // 8 + ExprDesignatedInit designated_init; // 16 ExprDesignator designator_expr; // 16 ExprNamedArgument named_argument_expr; ExprEmbedExpr embed_expr; // 16 @@ -3719,6 +3725,7 @@ static inline void exprid_set_span(ExprId expr_id, SourceSpan loc); static inline void expr_set_span(Expr *expr, SourceSpan loc) { + if (!expr) return; expr->span = loc; switch (expr->expr_kind) { @@ -3755,7 +3762,8 @@ static inline void expr_set_span(Expr *expr, SourceSpan loc) expr_list_set_span(expr->initializer_list, loc); return; case EXPR_DESIGNATED_INITIALIZER_LIST: - expr_list_set_span(expr->designated_init_list, loc); + expr_set_span(expr->designated_init.splat, loc); + expr_list_set_span(expr->designated_init.list, loc); return; case EXPR_MAKE_ANY: expr_set_span(expr->make_any_expr.inner, loc); diff --git a/src/compiler/copying.c b/src/compiler/copying.c index 56712fb7e..3db95a3ae 100644 --- a/src/compiler/copying.c +++ b/src/compiler/copying.c @@ -592,7 +592,8 @@ Expr *copy_expr(CopyStruct *c, Expr *source_expr) MACRO_COPY_EXPR_LIST(expr->initializer_list); return expr; case EXPR_DESIGNATED_INITIALIZER_LIST: - MACRO_COPY_EXPR_LIST(expr->designated_init_list); + MACRO_COPY_EXPR(expr->designated_init.splat); + MACRO_COPY_EXPR_LIST(expr->designated_init.list); return expr; case EXPR_EXPRESSION_LIST: MACRO_COPY_EXPR_LIST(expr->expression_list); diff --git a/src/compiler/expr.c b/src/compiler/expr.c index a52e262c0..9d208f971 100644 --- a/src/compiler/expr.c +++ b/src/compiler/expr.c @@ -420,7 +420,7 @@ bool expr_is_runtime_const(Expr *expr) case EXPR_INITIALIZER_LIST: return expr_list_is_constant_eval(expr->initializer_list); case EXPR_DESIGNATED_INITIALIZER_LIST: - return expr_list_is_constant_eval(expr->designated_init_list); + return (!expr->designated_init.splat || expr_is_const(expr->designated_init.splat)) && expr_list_is_constant_eval(expr->designated_init.list); case EXPR_SLICE: if (!exprid_is_runtime_const(expr->slice_expr.expr)) return false; return expr->slice_expr.range.range_type == RANGE_CONST_RANGE; diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index 75962e9f3..0743e292e 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -49,7 +49,7 @@ static void llvm_emit_unary_expr(GenContext *c, BEValue *value, Expr *expr); static inline void llvm_emit_memcmp(GenContext *c, BEValue *be_value, LLVMValueRef ptr, LLVMValueRef other_ptr, LLVMValueRef size); static LLVMTypeRef llvm_find_inner_struct_type_for_coerce(GenContext *c, LLVMTypeRef struct_type, ByteSize dest_size); static void llvm_expand_type_to_args(GenContext *context, Type *param_type, LLVMValueRef expand_ptr, LLVMValueRef *args, unsigned *arg_count_ref, AlignSize alignment); -static inline void llvm_emit_initialize_reference_designated_bitstruct(GenContext *c, BEValue *ref, Decl *bitstruct, Expr **elements); +static inline void llvm_emit_initialize_reference_designated_bitstruct(GenContext *c, BEValue *ref, Decl *bitstruct, Expr **elements, Expr *splat); INLINE LLVMValueRef llvm_emit_bitstruct_value_update(GenContext *c, LLVMValueRef current_val, TypeSize bits, LLVMTypeRef bitstruct_type, Decl *member, LLVMValueRef val); INLINE void llvm_emit_initialize_reference_bitstruct_array(GenContext *c, BEValue *ref, Decl *bitstruct, Expr** elements); #define MAX_AGG 16 @@ -1824,12 +1824,21 @@ static void llvm_emit_initialize_designated_element(GenContext *c, BEValue *ref, } } -static inline void llvm_emit_initialize_reference_designated_bitstruct_array(GenContext *c, BEValue *ref, Decl *bitstruct, Expr **elements) +static inline void llvm_emit_initialize_reference_designated_bitstruct_array(GenContext *c, BEValue *ref, Decl *bitstruct, Expr **elements, Expr *splat) { LLVMTypeRef type = llvm_get_type(c, ref->type); bool is_bitswap = bitstruct_requires_bitswap(bitstruct); llvm_value_addr(c, ref); - llvm_store_zero(c, ref); + if (splat) + { + BEValue splat_val; + llvm_emit_expr(c, &splat_val, splat); + llvm_store(c, ref, &splat_val); + } + else + { + llvm_store_zero(c, ref); + } AlignSize alignment = ref->alignment; LLVMValueRef array_ptr = ref->value; // Now walk through the elements. @@ -1845,16 +1854,27 @@ static inline void llvm_emit_initialize_reference_designated_bitstruct_array(Gen } } -static inline void llvm_emit_initialize_reference_designated_bitstruct(GenContext *c, BEValue *ref, Decl *bitstruct, Expr **elements) +static inline void llvm_emit_initialize_reference_designated_bitstruct(GenContext *c, BEValue *ref, Decl *bitstruct, Expr **elements, Expr *splat) { Type *underlying_type = type_lowering(ref->type); if (underlying_type->type_kind == TYPE_ARRAY) { - llvm_emit_initialize_reference_designated_bitstruct_array(c, ref, bitstruct, elements); + llvm_emit_initialize_reference_designated_bitstruct_array(c, ref, bitstruct, elements, splat); return; } LLVMTypeRef type = llvm_get_type(c, underlying_type); - LLVMValueRef data = LLVMConstNull(type); + LLVMValueRef data; + if (!splat) + { + data = LLVMConstNull(type); + } + else + { + BEValue splat_val; + llvm_emit_expr(c, &splat_val, splat); + llvm_value_rvalue(c, &splat_val); + data = splat_val.value; + } TypeSize bits = type_bit_size(underlying_type); // Now walk through the elements. @@ -1877,13 +1897,15 @@ static inline void llvm_emit_initialize_reference_designated_bitstruct(GenContex static inline void llvm_emit_initialize_reference_designated(GenContext *c, BEValue *ref, Expr *expr) { - Expr **elements = expr->designated_init_list; + + Expr **elements = expr->designated_init.list; + Expr *splat = expr->designated_init.splat; ASSERT(vec_size(elements)); Type *type = type_flatten(expr->type); ASSERT(type->type_kind != TYPE_SLICE); if (type->type_kind == TYPE_BITSTRUCT) { - llvm_emit_initialize_reference_designated_bitstruct(c, ref, type->decl, elements); + llvm_emit_initialize_reference_designated_bitstruct(c, ref, type->decl, elements, splat); return; } @@ -1891,7 +1913,16 @@ static inline void llvm_emit_initialize_reference_designated(GenContext *c, BEVa llvm_value_addr(c, ref); // Clear the memory - llvm_store_zero(c, ref); + if (splat) + { + BEValue splat_value; + llvm_emit_expr(c, &splat_value, splat); + llvm_store(c, ref, &splat_value); + } + else + { + llvm_store_zero(c, ref); + } // Now walk through the elements. FOREACH(Expr *, designator, elements) @@ -6369,8 +6400,19 @@ static inline void llvm_emit_vector_initializer_list(GenContext *c, BEValue *val } else { - vec_value = llvm_get_zero_raw(llvm_type); - Expr **elements = expr->designated_init_list; + Expr **elements = expr->designated_init.list; + Expr *splat = expr->designated_init.splat; + if (splat) + { + BEValue splat_val; + llvm_emit_expr(c, &splat_val, splat); + llvm_value_rvalue(c, &splat_val); + vec_value = splat_val.value; + } + else + { + vec_value = llvm_get_zero_raw(llvm_type); + } FOREACH(Expr *, designator, elements) { diff --git a/src/compiler/parse_expr.c b/src/compiler/parse_expr.c index da440d8c4..a4471ab3a 100644 --- a/src/compiler/parse_expr.c +++ b/src/compiler/parse_expr.c @@ -862,13 +862,18 @@ static Expr *parse_initializer_list(ParseContext *c, Expr *left, SourceSpan lhs_ ASSERT(!left && "Unexpected left hand side"); Expr *initializer_list = EXPR_NEW_TOKEN(EXPR_INITIALIZER_LIST); advance_and_verify(c, TOKEN_LBRACE); + Expr *splat = NULL; if (!try_consume(c, TOKEN_RBRACE)) { Expr **exprs = NULL; if (!parse_init_list(c, &exprs, TOKEN_RBRACE, NULL, true)) return poisoned_expr; int designated = -1; - FOREACH(Expr *, expr, exprs) + FOREACH_IDX(i, Expr *, expr, exprs) { + if (i == 0 && expr->expr_kind == EXPR_SPLAT) + { + splat = expr; + } if (expr->expr_kind == EXPR_DESIGNATOR) { if (designated == 0) @@ -876,10 +881,12 @@ static Expr *parse_initializer_list(ParseContext *c, Expr *left, SourceSpan lhs_ designated = expr->designator_expr.path[0]->kind == DESIGNATOR_FIELD ? 1 : 2; goto ERROR; } + designated = expr->designator_expr.path[0]->kind == DESIGNATOR_FIELD ? 1 : 2; continue; } if (designated > 0) goto ERROR; + if (designated == -1 && splat) continue; designated = 0; continue; ERROR:; @@ -896,7 +903,12 @@ ERROR:; RANGE_EXTEND_PREV(initializer_list); if (designated > 0) { - initializer_list->designated_init_list = exprs; + if (splat) + { + vec_erase_front(exprs, 1); + splat = splat->inner_expr; + } + initializer_list->designated_init = (ExprDesignatedInit) { .splat = splat, .list = exprs }; initializer_list->expr_kind = EXPR_DESIGNATED_INITIALIZER_LIST; } else diff --git a/src/compiler/sema_initializers.c b/src/compiler/sema_initializers.c index 21a077d96..c71bb1182 100644 --- a/src/compiler/sema_initializers.c +++ b/src/compiler/sema_initializers.c @@ -503,7 +503,17 @@ static inline bool sema_expr_analyse_untyped_initializer(SemaContext *context, E static bool sema_expr_analyse_designated_initializer(SemaContext *context, Type *assigned, Type *flattened, Expr *initializer, bool *no_match_ref) { - Expr **init_expressions = initializer->designated_init_list; + Expr **init_expressions = initializer->designated_init.list; + Expr *splat = initializer->designated_init.splat; + if (splat) + { + if (!sema_analyse_expr_rvalue(context, splat)) return false; + sema_cast_const(splat); + if (IS_OPTIONAL(splat)) + { + RETURN_SEMA_ERROR(splat, "An optional splat is not permitted."); + } + } Type *original = flattened->canonical; bool is_bitstruct = original->type_kind == TYPE_BITSTRUCT; bool is_structlike = type_is_union_or_strukt(original) || is_bitstruct; @@ -558,6 +568,36 @@ static bool sema_expr_analyse_designated_initializer(SemaContext *context, Type { type = assigned; } + if (splat && type->canonical != splat->type->canonical) + { + if (type_is_subtype(splat->type->canonical, type->canonical)) + { + Decl *decl = original->decl; + Expr *designator = expr_new(EXPR_DESIGNATOR, initializer->span); + DesignatorElement **elements = NULL; + while (true) + { + DesignatorElement *designator_element = MALLOCS(DesignatorElement); + designator_element->kind = DESIGNATOR_FIELD; + designator_element->index = 0; + vec_add(elements, designator_element); + assert(decl->is_substruct); + Decl *member = decl->strukt.members[0]; + if (member->type->canonical == splat->type) break; + decl = member; + } + designator->resolve_status = RESOLVE_DONE; + designator->designator_expr.path = elements; + designator->designator_expr.value = splat; + designator->type = splat->type; + vec_insert_first(initializer->designated_init.list, designator); + initializer->designated_init.splat = NULL; + } + else + { + RETURN_SEMA_ERROR(splat, "Splat type does not match initializer type."); + } + } initializer->type = type_add_optional(type, optional); initializer->resolve_status = RESOLVE_DONE; if (expr_is_runtime_const(initializer)) @@ -647,7 +687,17 @@ NO_MATCH:; static void sema_create_const_initializer_from_designated_init(ConstInitializer *const_init, Expr *initializer) { // Flatten the type since the external type might be typedef or a distinct type. - const_init_rewrite_to_zero(const_init, type_flatten(initializer->type)); + Type *flattened = type_flatten(initializer->type); + if (initializer->designated_init.splat) + { + Expr *splat = initializer->designated_init.splat; + ASSERT_SPAN(splat, expr_is_const_initializer(splat)); + *const_init = *splat->const_expr.initializer; + } + else + { + const_init_rewrite_to_zero(const_init, flattened); + } ASSERT(type_flatten(initializer->type)->type_kind != TYPE_SLICE); // Loop through the initializers. FOREACH(Expr *, expr, initializer->initializer_list) diff --git a/src/compiler/sema_liveness.c b/src/compiler/sema_liveness.c index 2ec944f0a..53619bf84 100644 --- a/src/compiler/sema_liveness.c +++ b/src/compiler/sema_liveness.c @@ -401,7 +401,8 @@ RETRY: sema_trace_expr_list_liveness(expr->expression_list); return; case EXPR_DESIGNATED_INITIALIZER_LIST: - sema_trace_expr_list_liveness(expr->designated_init_list); + sema_trace_expr_liveness(expr->designated_init.splat); + sema_trace_expr_list_liveness(expr->designated_init.list); return; case EXPR_IDENTIFIER: sema_trace_decl_liveness(expr->ident_expr); diff --git a/test/test_suite/functions/splat_initializer.c3t b/test/test_suite/functions/splat_initializer.c3t new file mode 100644 index 000000000..ddea7d81e --- /dev/null +++ b/test/test_suite/functions/splat_initializer.c3t @@ -0,0 +1,66 @@ +// #target: mingw-x64 +module test; +import std; +struct ParentType +{ + int a; + int b; + int x; + int y; +} +struct ChildType +{ + inline ParentType p; + int c; + int ef; + char[10] ff; +} + +const ParentType ABC = { .a = 1, .b = 2, .x = 12312, .y = -1 }; +const ChildType ABC2 = { .a = 1, .b = 1, .x = 12, .y = 0 }; + +ChildType gc = { ...ABC2, .b = 7, .c = 8 }; +ChildType gc2 = { ...ABC, .b = 7, .c = 8 }; + +fn void main() +{ + + ParentType some_p = { .a = 1, .b = 2, .x = 12312, .y = -1 }; + ChildType c = { ...some_p, .b = 7, .c = 8 }; + ParentType t = { ...some_p, .b = 343}; + ChildType d = gc; + ChildType e = gc2; +} + +/* #expect: test.ll + +@"$ct.test.ParentType" = linkonce global %.introspect { i8 10, i64 0, ptr null, i64 16, i64 0, i64 4, [0 x i64] zeroinitializer }, comdat, align 8 +@"$ct.test.ChildType" = linkonce global %.introspect { i8 10, i64 ptrtoint (ptr @"$ct.test.ParentType" to i64), ptr null, i64 36, i64 0, i64 4, [0 x i64] zeroinitializer }, comdat, align 8 +@test.ABC = local_unnamed_addr constant %ParentType { i32 1, i32 7, i32 12312, i32 -1 }, align 4 +@test.ABC2 = local_unnamed_addr constant %ChildType { %ParentType { i32 1, i32 7, i32 12, i32 0 }, i32 8, i32 0, [10 x i8] zeroinitializer }, align 4 +@test.gc = local_unnamed_addr global %ChildType { %ParentType { i32 1, i32 7, i32 12, i32 0 }, i32 8, i32 0, [10 x i8] zeroinitializer }, align 4 +@test.gc2 = local_unnamed_addr global %ChildType { %ParentType { i32 1, i32 7, i32 12312, i32 -1 }, i32 8, i32 0, [10 x i8] zeroinitializer }, align 4 +@.__const = private unnamed_addr constant %ParentType { i32 1, i32 2, i32 12312, i32 -1 }, align 4 + +; Function Attrs: nounwind uwtable +define void @test.main() #0 { +entry: + %some_p = alloca %ParentType, align 4 + %c = alloca %ChildType, align 4 + %t = alloca %ParentType, align 4 + %d = alloca %ChildType, align 4 + %e = alloca %ChildType, align 4 + call void @llvm.memcpy.p0.p0.i32(ptr align 4 %some_p, ptr align 4 @.__const, i32 16, i1 false) + call void @llvm.memset.p0.i64(ptr align 4 %c, i8 0, i64 36, i1 false) + call void @llvm.memcpy.p0.p0.i32(ptr align 4 %c, ptr align 4 %some_p, i32 16, i1 false) + %ptradd = getelementptr inbounds i8, ptr %c, i64 4 + store i32 7, ptr %ptradd, align 4 + %ptradd1 = getelementptr inbounds i8, ptr %c, i64 16 + store i32 8, ptr %ptradd1, align 4 + call void @llvm.memcpy.p0.p0.i32(ptr align 4 %t, ptr align 4 %some_p, i32 16, i1 false) + %ptradd2 = getelementptr inbounds i8, ptr %t, i64 4 + store i32 343, ptr %ptradd2, align 4 + call void @llvm.memcpy.p0.p0.i32(ptr align 4 %d, ptr align 4 @test.gc, i32 36, i1 false) + call void @llvm.memcpy.p0.p0.i32(ptr align 4 %e, ptr align 4 @test.gc2, i32 36, i1 false) + ret void +}