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>
This commit is contained in:
2026-02-13 23:42:15 +00:00
parent 324c6101f4
commit 6703c40f48
2 changed files with 260 additions and 13 deletions

252
astgen.c
View File

@@ -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,
// 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.

17
zir.h
View File

@@ -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)