commit 6703c40f482d455f46d2e018f61ab6cbbd0a6db5 (tree)
parent 324c6101f467c34ce5281e263521afdf2f3687be
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Fri, 13 Feb 2026 23:42:15 +0000
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 <noreply@anthropic.com>
Diffstat:
| M | astgen.c | | | 256 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- |
| M | zir.h | | | 17 | +++++++++++++++++ |
2 files changed, 260 insertions(+), 13 deletions(-)
diff --git 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,10 +8901,17 @@ 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) {
is_comptime_param = true;
}
@@ -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
@@ -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)