From 6703c40f482d455f46d2e018f61ab6cbbd0a6db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Fri, 13 Feb 2026 23:42:15 +0000 Subject: [PATCH] astgen: add func_fancy, noalias_bits, varargs, and callconv support in fnDecl Implement several interconnected features for function declarations: - noalias_bits: Track which parameters have the noalias keyword by setting corresponding bits in a uint32_t (supports up to 32 parameters) - is_var_args: Detect ellipsis3 (...) token in parameter list - is_noinline/has_inline_keyword: Detect noinline/inline modifiers - callconv handling: Extract callconv_expr from fn_proto variants, create cc_gz sub-block, emit builtin_value for explicit callconv() or inline - func_fancy instruction: When any of cc_ref, is_var_args, noalias_bits, or is_noinline are present, emit func_fancy instead of func/func_inferred with the appropriate FuncFancy payload layout - fn_var_args: Track in AstGenCtx for body code that checks it - BuiltinValue constants: Add all ZIR_BUILTIN_VALUE_* defines to zir.h - addBuiltinValue helper: Emit extended builtin_value instructions Generic tracking (any_param_used, ret_ty_is_generic, ret_body_param_refs) is not yet implemented as it requires is_used_or_discarded support in ScopeLocalVal scope lookups. Co-Authored-By: Claude Opus 4.6 --- astgen.c | 256 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- zir.h | 17 ++++ 2 files changed, 260 insertions(+), 13 deletions(-) diff --git a/astgen.c b/astgen.c index 269dab2b7f..73c392e4f1 100644 --- a/astgen.c +++ b/astgen.c @@ -114,6 +114,7 @@ typedef struct { uint32_t nodes_need_rl_cap; bool has_compile_errors; bool within_fn; // AstGen.zig:49 + bool fn_var_args; // AstGen.zig:46 } AstGenCtx; static void setCompileError(AstGenCtx* ag, const char* where, int line) { @@ -575,6 +576,25 @@ static uint32_t addPlNodePayloadIndex( return addInstruction(gz, tag, data); } +// Mirrors GenZir.addBuiltinValue (AstGen.zig:12389-12391). +// Emits extended instruction with builtin_value opcode. +static uint32_t addBuiltinValue( + GenZir* gz, uint32_t src_node, uint16_t builtin_val) { + AstGenCtx* ag = gz->astgen; + ensureInstCapacity(ag, 1); + uint32_t idx = ag->inst_len; + ag->inst_tags[idx] = ZIR_INST_EXTENDED; + ZirInstData data; + data.extended.opcode = (uint16_t)ZIR_EXT_BUILTIN_VALUE; + data.extended.small = builtin_val; + data.extended.operand + = (uint32_t)((int32_t)src_node - (int32_t)gz->decl_node_index); + ag->inst_datas[idx] = data; + ag->inst_len++; + gzAppendInstruction(gz, idx); + return idx + ZIR_REF_START_INDEX; +} + // --- Source cursor (AstGen.zig:13335-13359) --- // Mirrors AstGen.advanceSourceCursor (AstGen.zig:13342). @@ -8386,6 +8406,123 @@ static uint32_t addFunc(GenZir* gz, uint32_t src_node, uint32_t block_node, return addInstruction(gz, tag, data); } +// --- addFuncFancy (AstGen.zig:12112-12173) --- +// Emits func_fancy instruction when cc_ref, is_var_args, noalias_bits, +// or is_noinline are present. + +static uint32_t addFuncFancy(GenZir* gz, uint32_t src_node, + uint32_t block_node, uint32_t param_block, uint32_t ret_ref, + const uint32_t* ret_body, uint32_t ret_body_len, uint32_t cc_ref, + const uint32_t* cc_body, uint32_t cc_body_len, const uint32_t* body, + uint32_t body_len, const uint32_t* param_insts, uint32_t param_insts_len, + uint32_t lbrace_line, uint32_t lbrace_column, bool is_var_args, + bool is_inferred_error, bool is_noinline, uint32_t noalias_bits) { + AstGenCtx* ag = gz->astgen; + const Ast* tree = ag->tree; + uint32_t rbrace_tok = lastToken(tree, block_node); + uint32_t rbrace_start = tree->tokens.starts[rbrace_tok]; + advanceSourceCursor(ag, rbrace_start); + uint32_t rbrace_line = ag->source_line - gz->decl_line; + uint32_t rbrace_column = ag->source_column; + + uint32_t fixup_body_len = countBodyLenAfterFixupsExtraRefs( + ag, body, body_len, param_insts, param_insts_len); + + // Calculate cc extra len (AstGen.zig:12231-12236). + uint32_t cc_extra_len = 0; + if (cc_body_len > 0) { + cc_extra_len = countBodyLenAfterFixups(ag, cc_body, cc_body_len) + 1; + } else if (cc_ref != ZIR_REF_NONE) { + cc_extra_len = 1; + } + + // Calculate ret extra len (AstGen.zig:12231-12236). + // Note: ret_param_refs are empty (no generic tracking yet). + uint32_t ret_extra_len = 0; + if (ret_body_len > 0) { + ret_extra_len + = countBodyLenAfterFixups(ag, ret_body, ret_body_len) + 1; + } else if (ret_ref != ZIR_REF_NONE) { + ret_extra_len = 1; + } + + // FuncFancy has 3 fields: param_block, body_len, bits. + uint32_t total_extra = 3 + cc_extra_len + ret_extra_len + + ((noalias_bits != 0) ? 1u : 0u) + fixup_body_len + 7; + ensureExtraCapacity(ag, total_extra); + + // FuncFancy payload (Zir.zig:2589-2610). + uint32_t payload_index = ag->extra_len; + ag->extra[ag->extra_len++] = param_block; + ag->extra[ag->extra_len++] = fixup_body_len; + // Bits packed as u32 (Zir.zig:2598-2609). + uint32_t bits = 0; + if (is_var_args) + bits |= (1u << 0); + if (is_inferred_error) + bits |= (1u << 1); + if (is_noinline) + bits |= (1u << 2); + if (cc_ref != ZIR_REF_NONE) + bits |= (1u << 3); // has_cc_ref + if (cc_body_len > 0) + bits |= (1u << 4); // has_cc_body + if (ret_ref != ZIR_REF_NONE) + bits |= (1u << 5); // has_ret_ty_ref + if (ret_body_len > 0) + bits |= (1u << 6); // has_ret_ty_body + if (noalias_bits != 0) + bits |= (1u << 7); // has_any_noalias + // bit 8 = ret_ty_is_generic (false for now, no generic tracking) + ag->extra[ag->extra_len++] = bits; + + // Trailing cc (AstGen.zig:12143-12151). + if (cc_body_len > 0) { + ag->extra[ag->extra_len++] + = countBodyLenAfterFixups(ag, cc_body, cc_body_len); + for (uint32_t i = 0; i < cc_body_len; i++) + appendPossiblyRefdBodyInst(ag, cc_body[i]); + } else if (cc_ref != ZIR_REF_NONE) { + ag->extra[ag->extra_len++] = cc_ref; + } + + // Trailing ret_ty (AstGen.zig:12152-12164). + // Note: no ret_param_refs (generic tracking not implemented). + if (ret_body_len > 0) { + ag->extra[ag->extra_len++] + = countBodyLenAfterFixups(ag, ret_body, ret_body_len); + for (uint32_t i = 0; i < ret_body_len; i++) + appendPossiblyRefdBodyInst(ag, ret_body[i]); + } else if (ret_ref != ZIR_REF_NONE) { + ag->extra[ag->extra_len++] = ret_ref; + } + + // Trailing noalias_bits (AstGen.zig:12166-12168). + if (noalias_bits != 0) + ag->extra[ag->extra_len++] = noalias_bits; + + // Body (AstGen.zig:12170). + appendBodyWithFixupsExtraRefs( + ag, body, body_len, param_insts, param_insts_len); + + // SrcLocs (AstGen.zig:12098-12106). + uint32_t columns = (lbrace_column & 0xFFFFu) | (rbrace_column << 16); + ag->extra[ag->extra_len++] = lbrace_line; + ag->extra[ag->extra_len++] = rbrace_line; + ag->extra[ag->extra_len++] = columns; + // proto_hash (4 words): zero for now. + ag->extra[ag->extra_len++] = 0; + ag->extra[ag->extra_len++] = 0; + ag->extra[ag->extra_len++] = 0; + ag->extra[ag->extra_len++] = 0; + + // Emit the func_fancy instruction (AstGen.zig:12220-12226). + ZirInstData data; + data.pl_node.src_node = (int32_t)src_node - (int32_t)gz->decl_node_index; + data.pl_node.payload_index = payload_index; + return addInstruction(gz, ZIR_INST_FUNC_FANCY, data); +} + // --- testDecl (AstGen.zig:4708) --- static void testDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, @@ -8541,10 +8678,12 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, uint32_t fn_token = tree->nodes.main_tokens[proto_node]; uint32_t fn_name_token = fn_token + 1; - // Check for 'pub', 'export' modifiers (Ast.zig:2003-2025, - // AstGen.zig:4102-4106). + // Check for 'pub', 'export', 'inline', 'noinline' modifiers + // (Ast.zig:2003-2025, AstGen.zig:4102-4106, 4240-4247). bool is_pub = false; bool is_export = false; + bool is_noinline = false; + bool has_inline_keyword = false; for (uint32_t i = fn_token; i > 0;) { i--; uint32_t ttag = tree->tokens.tags[i]; @@ -8552,6 +8691,10 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, is_pub = true; else if (ttag == TOKEN_KEYWORD_EXPORT) is_export = true; + else if (ttag == TOKEN_KEYWORD_NOINLINE) + is_noinline = true; + else if (ttag == TOKEN_KEYWORD_INLINE) + has_inline_keyword = true; else break; } @@ -8594,13 +8737,16 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, } } - // Extract param type nodes from proto variant (AstGen.zig:4253-4254). + // Extract param type nodes and callconv_expr from proto variant + // (AstGen.zig:4253-4254, Ast.zig:1456-1520). uint32_t param_nodes_buf[1]; // buffer for fn_proto_simple/fn_proto_one const uint32_t* param_nodes = NULL; uint32_t params_len = 0; + uint32_t callconv_expr_node = 0; // 0 = none (OptionalIndex) if (proto_tag == AST_NODE_FN_PROTO_SIMPLE) { // data.lhs = optional param node, data.rhs = return type. + // callconv_expr = .none (Ast.zig:1468). if (proto_data.lhs != 0) { param_nodes_buf[0] = proto_data.lhs; param_nodes = param_nodes_buf; @@ -8616,8 +8762,11 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, param_nodes = param_nodes_buf; params_len = 1; } + // AstFnProtoOne.callconv_expr at offset 4 (Ast.zig:4076). + callconv_expr_node = tree->extra_data.arr[extra_idx + 4]; } else if (proto_tag == AST_NODE_FN_PROTO_MULTI) { // data.lhs = extra_data index → SubRange{start, end}. + // callconv_expr = .none (Ast.zig:1484). uint32_t extra_idx = proto_data.lhs; uint32_t range_start = tree->extra_data.arr[extra_idx]; uint32_t range_end = tree->extra_data.arr[extra_idx + 1]; @@ -8631,6 +8780,8 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, uint32_t pend = tree->extra_data.arr[extra_idx + 1]; // params_end param_nodes = tree->extra_data.arr + pstart; params_len = pend - pstart; + // AstFnProto.callconv_expr at offset 5 (Ast.zig:4089). + callconv_expr_node = tree->extra_data.arr[extra_idx + 5]; } // decl_gz (called value_gz in caller, decl_gz in fnDeclInner) @@ -8656,6 +8807,10 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, // Collect param instruction indices (AstGen.zig:4254, 4360). uint32_t param_insts[32]; uint32_t param_insts_len = 0; + // noalias_bits tracking (AstGen.zig:4259). + uint32_t noalias_bits = 0; + // is_var_args detection (AstGen.zig:4261). + bool is_var_args = false; // Parameter iteration using token-based iterator, mirroring upstream // FnProto.Iterator (Ast.zig:2680-2768, AstGen.zig:4260-4363). @@ -8670,6 +8825,7 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, .src_node = 0, .ctx = RI_CTX_NONE }; + uint32_t param_type_i = 0; // index for noalias_bits (AstGen.zig:4262) while (true) { uint32_t name_token = 0; uint32_t comptime_noalias_token = 0; @@ -8717,9 +8873,11 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, // Skip doc comments. while (tree->tokens.tags[iter_tok_i] == TOKEN_DOC_COMMENT) iter_tok_i++; - // Check for ellipsis3 (varargs) - skip for now. - if (tree->tokens.tags[iter_tok_i] == TOKEN_ELLIPSIS3) + // Check for ellipsis3 (varargs) (AstGen.zig:4275-4281). + if (tree->tokens.tags[iter_tok_i] == TOKEN_ELLIPSIS3) { + is_var_args = true; break; + } // Check for comptime/noalias prefix. if (tree->tokens.tags[iter_tok_i] == TOKEN_KEYWORD_COMPTIME || tree->tokens.tags[iter_tok_i] == TOKEN_KEYWORD_NOALIAS) { @@ -8743,8 +8901,15 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, } } - // Determine is_comptime from comptime_noalias token + // Determine is_comptime and noalias from comptime_noalias token // (AstGen.zig:4265-4273). + if (comptime_noalias_token != 0 + && tree->tokens.tags[comptime_noalias_token] + == TOKEN_KEYWORD_NOALIAS) { + // Track noalias_bits (AstGen.zig:4266-4269). + if (param_type_i < 32) + noalias_bits |= (1u << param_type_i); + } if (comptime_noalias_token != 0 && tree->tokens.tags[comptime_noalias_token] == TOKEN_KEYWORD_COMPTIME) { @@ -8827,6 +8992,7 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, lv->name = param_name_str; params_scope = &lv->base; } + param_type_i++; } // --- Return type (AstGen.zig:4369-4383) --- @@ -8866,6 +9032,46 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, ag->source_line = saved_source_line; ag->source_column = saved_source_column; + // --- Calling convention (AstGen.zig:4390-4413) --- + // Note: cc_gz uses `scope` (= &decl_gz.base), not params_scope. + GenZir cc_gz = makeSubBlock(&decl_gz, &decl_gz.base); + uint32_t cc_ref = ZIR_REF_NONE; + if (callconv_expr_node != 0) { + // Explicit callconv(expr) (AstGen.zig:4393-4405). + uint32_t cc_ty = addBuiltinValue( + &cc_gz, callconv_expr_node, ZIR_BUILTIN_VALUE_CALLING_CONVENTION); + ResultLoc cc_ri = { .tag = RL_COERCED_TY, + .data = cc_ty, + .src_node = 0, + .ctx = RI_CTX_NONE }; + cc_ref = exprRl(&cc_gz, &decl_gz.base, cc_ri, callconv_expr_node); + if (ag->has_compile_errors) { + free(ret_body); + return; + } + if (gzInstructionsLen(&cc_gz) > 0) { + // break_inline targets the func instruction (patched later). + makeBreakInline(&cc_gz, 0, cc_ref, AST_NODE_OFFSET_NONE); + } + } else if (has_inline_keyword) { + // inline keyword → calling_convention_inline + // (AstGen.zig:4406-4409). + cc_ref = addBuiltinValue( + &cc_gz, node, ZIR_BUILTIN_VALUE_CALLING_CONVENTION_INLINE); + makeBreakInline(&cc_gz, 0, cc_ref, AST_NODE_OFFSET_NONE); + } + + uint32_t cc_body_len = gzInstructionsLen(&cc_gz); + uint32_t* cc_body = NULL; + if (cc_body_len > 0) { + cc_body = malloc(cc_body_len * sizeof(uint32_t)); + if (!cc_body) + abort(); + memcpy(cc_body, gzInstructionsSlice(&cc_gz), + cc_body_len * sizeof(uint32_t)); + } + gzUnstack(&cc_gz); + // --- Body (AstGen.zig:4415-4424) --- GenZir body_gz; memset(&body_gz, 0, sizeof(body_gz)); @@ -8878,10 +9084,13 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, body_gz.instructions_top = ag->scratch_inst_len; body_gz.any_defer_node = UINT32_MAX; - // Set fn_block and fn_ret_ty for the body (AstGen.zig:4442-4455). + // Set fn_block, fn_ret_ty, fn_var_args for the body + // (AstGen.zig:4442-4459). void* prev_fn_block = ag->fn_block; setFnBlock(ag, &body_gz); uint32_t prev_fn_ret_ty = ag->fn_ret_ty; + bool prev_fn_var_args = ag->fn_var_args; + ag->fn_var_args = is_var_args; if (is_inferred_error || ret_ref == ZIR_REF_NONE) { // Non-void non-trivial return type: emit ret_type instruction. if (ret_body_len > 0 || is_inferred_error) { @@ -8917,9 +9126,11 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, ag->within_fn = prev_within_fn; ag->fn_block = prev_fn_block; ag->fn_ret_ty = prev_fn_ret_ty; + ag->fn_var_args = prev_fn_var_args; if (ag->has_compile_errors) { free(ret_body); + free(cc_body); return; } @@ -8945,22 +9156,41 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, uint32_t fn_body_len = gzInstructionsLen(&body_gz); gzUnstack(&body_gz); - // Create func instruction (AstGen.zig:4476-4494). - uint32_t func_ref = addFunc(&decl_gz, node, body_node, decl_inst, ret_ref, - ret_body, ret_body_len, fn_body, fn_body_len, param_insts, - param_insts_len, lbrace_line, lbrace_column, is_inferred_error); + // Create func/func_fancy instruction (AstGen.zig:4476-4494, + // 12112-12173). + bool need_fancy = cc_ref != ZIR_REF_NONE || is_var_args + || noalias_bits != 0 || is_noinline; + uint32_t func_ref; + if (need_fancy) { + func_ref = addFuncFancy(&decl_gz, node, body_node, decl_inst, ret_ref, + ret_body, ret_body_len, cc_ref, cc_body, cc_body_len, fn_body, + fn_body_len, param_insts, param_insts_len, lbrace_line, + lbrace_column, is_var_args, is_inferred_error, is_noinline, + noalias_bits); + } else { + func_ref = addFunc(&decl_gz, node, body_node, decl_inst, ret_ref, + ret_body, ret_body_len, fn_body, fn_body_len, param_insts, + param_insts_len, lbrace_line, lbrace_column, is_inferred_error); + } // Patch ret_body break_inline to point to func instruction // (AstGen.zig:12199-12202). if (ret_body_len > 0) { uint32_t break_inst = ret_body[ret_body_len - 1]; - // The break_inline payload is at payload_index; block_inst is at - // offset 1 in the Break struct. + uint32_t break_payload + = ag->inst_datas[break_inst].break_data.payload_index; + ag->extra[break_payload + 1] = func_ref - ZIR_REF_START_INDEX; + } + // Patch cc_body break_inline to point to func instruction + // (AstGen.zig:12146-12148). + if (cc_body_len > 0) { + uint32_t break_inst = cc_body[cc_body_len - 1]; uint32_t break_payload = ag->inst_datas[break_inst].break_data.payload_index; ag->extra[break_payload + 1] = func_ref - ZIR_REF_START_INDEX; } free(ret_body); + free(cc_body); // break_inline returning func to declaration (AstGen.zig:4495). // nodeIndexToRelative(decl_node) = node - decl_gz.decl_node_index. diff --git a/zir.h b/zir.h index 10950e1249..88b858c27e 100644 --- a/zir.h +++ b/zir.h @@ -513,6 +513,23 @@ typedef union { #define ZIR_REF_BOOL_FALSE 122 #define ZIR_REF_EMPTY_TUPLE 123 +// Zir.Inst.BuiltinValue enum (Zir.zig:3476-3494). +#define ZIR_BUILTIN_VALUE_ATOMIC_ORDER 0 +#define ZIR_BUILTIN_VALUE_ATOMIC_RMW_OP 1 +#define ZIR_BUILTIN_VALUE_CALLING_CONVENTION 2 +#define ZIR_BUILTIN_VALUE_ADDRESS_SPACE 3 +#define ZIR_BUILTIN_VALUE_FLOAT_MODE 4 +#define ZIR_BUILTIN_VALUE_REDUCE_OP 5 +#define ZIR_BUILTIN_VALUE_CALL_MODIFIER 6 +#define ZIR_BUILTIN_VALUE_PREFETCH_OPTIONS 7 +#define ZIR_BUILTIN_VALUE_EXPORT_OPTIONS 8 +#define ZIR_BUILTIN_VALUE_EXTERN_OPTIONS 9 +#define ZIR_BUILTIN_VALUE_TYPE_INFO 10 +#define ZIR_BUILTIN_VALUE_BRANCH_HINT 11 +#define ZIR_BUILTIN_VALUE_CLOBBERS 12 +#define ZIR_BUILTIN_VALUE_CALLING_CONVENTION_C 13 +#define ZIR_BUILTIN_VALUE_CALLING_CONVENTION_INLINE 14 + // Ast.Node.OptionalOffset.none = maxInt(i32). #define AST_NODE_OFFSET_NONE ((int32_t)0x7FFFFFFF)