From fb8a9d083984da9eca76054f62f35d1c60265fa5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 25 May 2026 10:28:15 +0100 Subject: [PATCH 1/3] Support `**` as `expand_values` operator: `**x` == `expand_values(x)` --- core/odin/parser/parser.odin | 3 +- core/odin/tokenizer/token.odin | 2 + core/odin/tokenizer/tokenizer.odin | 3 ++ src/check_builtin.cpp | 2 +- src/check_expr.cpp | 37 ++++++++++++++ src/llvm_backend_expr.cpp | 6 +++ src/llvm_backend_proc.cpp | 82 ++++++++++++++++-------------- src/parser.cpp | 1 + src/tokenizer.cpp | 4 ++ 9 files changed, 99 insertions(+), 41 deletions(-) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index c170e06cf..0fc8872bb 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -3553,7 +3553,8 @@ parse_unary_expr :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { case .Add, .Sub, .Not, .Xor, - .And: + .And, + .Mul_Mul: op := advance_token(p) expr := parse_unary_expr(p, lhs) diff --git a/core/odin/tokenizer/token.odin b/core/odin/tokenizer/token.odin index 48d08f127..406c8eaf2 100644 --- a/core/odin/tokenizer/token.odin +++ b/core/odin/tokenizer/token.odin @@ -67,6 +67,8 @@ Token_Kind :: enum u32 { Cmp_And, // && Cmp_Or, // || + Cmp_MulMul, // ** + B_Assign_Op_Begin, Add_Eq, // += Sub_Eq, // -= diff --git a/core/odin/tokenizer/tokenizer.odin b/core/odin/tokenizer/tokenizer.odin index f4d50b36c..df9d5b01d 100644 --- a/core/odin/tokenizer/tokenizer.odin +++ b/core/odin/tokenizer/tokenizer.odin @@ -679,6 +679,9 @@ scan :: proc(t: ^Tokenizer) -> Token { if t.ch == '=' { advance_rune(t) kind = .Mul_Eq + } else if t.ch == '*' { + advance_rune(t) + kind = .Mul_Mul } case '=': kind = .Eq diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 584264f10..00b22e84b 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1173,7 +1173,7 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan // masked_load (ptr: rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) -> #simd[N]T // masked_store(ptr: rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) - // masked_expand_load (ptr: rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) -> #simd[N]T + // masked_expand_load (ptr: rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) -> #simd[N]T // masked_compress_store(ptr: rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) Operand ptr = {}; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index ed2115a30..e60218099 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2946,6 +2946,43 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * return; } + + case Token_MulMul: { // 'expand_values' operator + if (!o->type) { + return; + } + + Type *type = base_type(o->type); + if (!is_type_struct(type) && !is_type_array(type)) { + gbString type_str = type_to_string(o->type); + error(node, "Expected a struct or array type to 'expand_values', got '%s'", type_str); + gb_string_free(type_str); + return; + } + + Type *tuple = alloc_type_tuple(); + + if (is_type_struct(type)) { + isize variable_count = type->Struct.fields.count; + tuple->Tuple.variables = permanent_slice_make(variable_count); + // NOTE(bill): don't copy the entities, this should be good enough + gb_memmove_array(tuple->Tuple.variables.data, type->Struct.fields.data, variable_count); + } else if (is_type_array(type)) { + isize variable_count = cast(isize)type->Array.count; + tuple->Tuple.variables = permanent_slice_make(variable_count); + for (isize i = 0; i < variable_count; i++) { + tuple->Tuple.variables[i] = alloc_entity_array_elem(nullptr, blank_token, type->Array.elem, cast(i32)i); + } + } + o->type = tuple; + o->mode = Addressing_Value; + + if (tuple->Tuple.variables.count == 1) { + o->type = tuple->Tuple.variables[0]->type; + } + + return; + } } if (!check_unary_op(c, o, op)) { diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index fd148e5c5..472e78b44 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -1,5 +1,6 @@ gb_internal lbValue lb_emit_arith_matrix(lbProcedure *p, TokenKind op, lbValue lhs, lbValue rhs, Type *type, bool component_wise); gb_internal lbValue lb_build_slice_expr_value(lbProcedure *p, Ast *expr); +gb_internal lbValue lb_expand_values(lbProcedure *p, lbValue val, Type *type); gb_internal LLVMValueRef lb_const_low_bits_mask(LLVMTypeRef type, u64 bit_count) { GB_ASSERT(bit_count <= 64); @@ -4664,6 +4665,11 @@ gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) { switch (ue->op.kind) { case Token_And: return lb_build_unary_and(p, expr); + case Token_MulMul: + { + lbValue val = lb_build_expr(p, ue->expr); + return lb_expand_values(p, val, type); + } default: { lbValue v = lb_build_expr(p, ue->expr); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 63ca868ec..a88833df2 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1321,6 +1321,48 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array c return result; } +gb_internal lbValue lb_expand_values(lbProcedure *p, lbValue val, Type *type) { + Type *t = base_type(val.type); + + if (!is_type_tuple(type)) { + if (t->kind == Type_Struct) { + GB_ASSERT(t->Struct.fields.count == 1); + return lb_emit_struct_ev(p, val, 0); + } else if (t->kind == Type_Array) { + GB_ASSERT(t->Array.count == 1); + return lb_emit_struct_ev(p, val, 0); + } else { + GB_PANIC("Unknown type of expand_values"); + } + + } + + GB_ASSERT(is_type_tuple(type)); + // NOTE(bill): Doesn't need to be zero because it will be initialized in the loops + lbValue tuple = lb_addr_get_ptr(p, lb_add_local_generated(p, type, false)); + if (t->kind == Type_Struct) { + for_array(src_index, t->Struct.fields) { + Entity *field = t->Struct.fields[src_index]; + i32 field_index = field->Variable.field_index; + lbValue f = lb_emit_struct_ev(p, val, field_index); + lbValue ep = lb_emit_struct_ep(p, tuple, cast(i32)src_index); + lb_emit_store(p, ep, f); + } + } else if (is_type_array_like(t)) { + // TODO(bill): Clean-up this code + lbValue ap = lb_address_from_load_or_generate_local(p, val); + i32 n = cast(i32)get_array_type_count(t); + for (i32 i = 0; i < n; i++) { + lbValue f = lb_emit_load(p, lb_emit_array_epi(p, ap, i)); + lbValue ep = lb_emit_struct_ep(p, tuple, i); + lb_emit_store(p, ep, f); + } + } else { + GB_PANIC("Unknown type of expand_values"); + } + return lb_emit_load(p, tuple); +} + gb_internal LLVMValueRef llvm_splat_int(i64 count, LLVMTypeRef type, i64 value, bool is_signed=false) { LLVMValueRef v = LLVMConstInt(type, value, is_signed); LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, count); @@ -3051,45 +3093,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu case BuiltinProc_expand_values: { lbValue val = lb_build_expr(p, ce->args[0]); - Type *t = base_type(val.type); - - if (!is_type_tuple(tv.type)) { - if (t->kind == Type_Struct) { - GB_ASSERT(t->Struct.fields.count == 1); - return lb_emit_struct_ev(p, val, 0); - } else if (t->kind == Type_Array) { - GB_ASSERT(t->Array.count == 1); - return lb_emit_struct_ev(p, val, 0); - } else { - GB_PANIC("Unknown type of expand_values"); - } - - } - - GB_ASSERT(is_type_tuple(tv.type)); - // NOTE(bill): Doesn't need to be zero because it will be initialized in the loops - lbValue tuple = lb_addr_get_ptr(p, lb_add_local_generated(p, tv.type, false)); - if (t->kind == Type_Struct) { - for_array(src_index, t->Struct.fields) { - Entity *field = t->Struct.fields[src_index]; - i32 field_index = field->Variable.field_index; - lbValue f = lb_emit_struct_ev(p, val, field_index); - lbValue ep = lb_emit_struct_ep(p, tuple, cast(i32)src_index); - lb_emit_store(p, ep, f); - } - } else if (is_type_array_like(t)) { - // TODO(bill): Clean-up this code - lbValue ap = lb_address_from_load_or_generate_local(p, val); - i32 n = cast(i32)get_array_type_count(t); - for (i32 i = 0; i < n; i++) { - lbValue f = lb_emit_load(p, lb_emit_array_epi(p, ap, i)); - lbValue ep = lb_emit_struct_ep(p, tuple, i); - lb_emit_store(p, ep, f); - } - } else { - GB_PANIC("Unknown type of expand_values"); - } - return lb_emit_load(p, tuple); + return lb_expand_values(p, val, tv.type); } case BuiltinProc_compress_values: { diff --git a/src/parser.cpp b/src/parser.cpp index 6dac8914f..01c4165b5 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -3525,6 +3525,7 @@ gb_internal Ast *parse_unary_expr(AstFile *f, bool lhs) { case Token_Xor: case Token_And: case Token_Not: + case Token_MulMul: // 'expand_values' operator case Token_Mul: // Used for error handling when people do C-like things { Token token = advance_token(f); diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index 2bdd15a60..f505b142e 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -35,6 +35,7 @@ TOKEN_KIND(Token__OperatorBegin, ""), \ TOKEN_KIND(Token_Shr, ">>"), \ TOKEN_KIND(Token_CmpAnd, "&&"), \ TOKEN_KIND(Token_CmpOr, "||"), \ + TOKEN_KIND(Token_MulMul, "**"), \ \ TOKEN_KIND(Token__AssignOpBegin, ""), \ TOKEN_KIND(Token_AddEq, "+="), \ @@ -879,6 +880,9 @@ gb_internal void tokenizer_get_token(Tokenizer *t, Token *token, int repeat=0) { if (t->curr_rune == '=') { advance_to_next_rune(t); token->kind = Token_MulEq; + } else if (t->curr_rune == '*') { + advance_to_next_rune(t); + token->kind = Token_MulMul; } break; case '=': From d762a8247fab8b999b2e9b5eca6d2400f4148874 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 25 May 2026 11:12:49 +0100 Subject: [PATCH 2/3] Fix look up table --- core/odin/tokenizer/token.odin | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/odin/tokenizer/token.odin b/core/odin/tokenizer/token.odin index 406c8eaf2..e5718ab13 100644 --- a/core/odin/tokenizer/token.odin +++ b/core/odin/tokenizer/token.odin @@ -67,7 +67,7 @@ Token_Kind :: enum u32 { Cmp_And, // && Cmp_Or, // || - Cmp_MulMul, // ** + MulMul, // ** B_Assign_Op_Begin, Add_Eq, // += @@ -204,6 +204,8 @@ tokens := [Token_Kind.COUNT]string { "&&", "||", + "**", + "", "+=", "-=", From 13487a45f20d8ffc8f93740c2b5eec90b0ffff1b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 26 May 2026 10:58:54 +0100 Subject: [PATCH 3/3] Fix typo --- core/odin/tokenizer/token.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/odin/tokenizer/token.odin b/core/odin/tokenizer/token.odin index e5718ab13..8c84ca69b 100644 --- a/core/odin/tokenizer/token.odin +++ b/core/odin/tokenizer/token.odin @@ -67,7 +67,7 @@ Token_Kind :: enum u32 { Cmp_And, // && Cmp_Or, // || - MulMul, // ** + Mul_Mul, // ** B_Assign_Op_Begin, Add_Eq, // +=