Skip to content

Commit aca9c74

Browse files
authored
Merge pull request #13914 from Vexu/variadic
implement defining C variadic functions
2 parents d93edad + 9bb1104 commit aca9c74

26 files changed

+661
-26
lines changed

doc/langref.html.in

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8088,6 +8088,35 @@ test "main" {
80888088
{#see_also|Import from C Header File|@cImport|@cDefine|@cInclude#}
80898089
{#header_close#}
80908090

8091+
{#header_open|@cVaArg#}
8092+
<pre>{#syntax#}@cVaArg(operand: *std.builtin.VaList, comptime T: type) T{#endsyntax#}</pre>
8093+
<p>
8094+
Implements the C macro {#syntax#}va_arg{#endsyntax#}.
8095+
</p>
8096+
{#see_also|@cVaCopy|@cVaEnd|@cVaStart#}
8097+
{#header_close#}
8098+
{#header_open|@cVaCopy#}
8099+
<pre>{#syntax#}@cVaCopy(src: *std.builtin.VaList) std.builtin.VaList{#endsyntax#}</pre>
8100+
<p>
8101+
Implements the C macro {#syntax#}va_copy{#endsyntax#}.
8102+
</p>
8103+
{#see_also|@cVaArg|@cVaEnd|@cVaStart#}
8104+
{#header_close#}
8105+
{#header_open|@cVaEnd#}
8106+
<pre>{#syntax#}@cVaEnd(src: *std.builtin.VaList) void{#endsyntax#}</pre>
8107+
<p>
8108+
Implements the C macro {#syntax#}va_end{#endsyntax#}.
8109+
</p>
8110+
{#see_also|@cVaArg|@cVaCopy|@cVaStart#}
8111+
{#header_close#}
8112+
{#header_open|@cVaStart#}
8113+
<pre>{#syntax#}@cVaStart() std.builtin.VaList{#endsyntax#}</pre>
8114+
<p>
8115+
Implements the C macro {#syntax#}va_start{#endsyntax#}. Only valid inside a variadic function.
8116+
</p>
8117+
{#see_also|@cVaArg|@cVaCopy|@cVaEnd#}
8118+
{#header_close#}
8119+
80918120
{#header_open|@divExact#}
80928121
<pre>{#syntax#}@divExact(numerator: T, denominator: T) T{#endsyntax#}</pre>
80938122
<p>
@@ -10802,14 +10831,32 @@ test "variadic function" {
1080210831
}
1080310832
{#code_end#}
1080410833
<p>
10805-
Non extern variadic functions are currently not implemented, but there
10806-
is an accepted proposal. See <a href="https://github.com/ziglang/zig/issues/515">#515</a>.
10834+
Variadic functions can be implemented using {#link|@cVaStart#}, {#link|@cVaEnd#}, {#link|@cVaArg#} and {#link|@cVaCopy#}
1080710835
</p>
10808-
{#code_begin|obj_err|non-extern function is variadic#}
10809-
export fn printf(format: [*:0]const u8, ...) c_int {
10810-
_ = format;
10836+
{#code_begin|test|defining_variadic_function#}
10837+
const std = @import("std");
10838+
const testing = std.testing;
10839+
const builtin = @import("builtin");
1081110840

10812-
return 0;
10841+
fn add(count: c_int, ...) callconv(.C) c_int {
10842+
var ap = @cVaStart();
10843+
defer @cVaEnd(&ap);
10844+
var i: usize = 0;
10845+
var sum: c_int = 0;
10846+
while (i < count) : (i += 1) {
10847+
sum += @cVaArg(&ap, c_int);
10848+
}
10849+
return sum;
10850+
}
10851+
10852+
test "defining a variadic function" {
10853+
// Variadic functions are currently disabled on some targets due to miscompilations.
10854+
if (builtin.cpu.arch == .aarch64 and builtin.os.tag != .windows and builtin.os.tag != .macos) return error.SkipZigTest;
10855+
if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest;
10856+
10857+
try std.testing.expectEqual(@as(c_int, 0), add(0));
10858+
try std.testing.expectEqual(@as(c_int, 1), add(1, @as(c_int, 1)));
10859+
try std.testing.expectEqual(@as(c_int, 3), add(2, @as(c_int, 1), @as(c_int, 2)));
1081310860
}
1081410861
{#code_end#}
1081510862
{#header_close#}

lib/std/builtin.zig

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,87 @@ pub const CallModifier = enum {
620620
compile_time,
621621
};
622622

623+
/// This data structure is used by the Zig language code generation and
624+
/// therefore must be kept in sync with the compiler implementation.
625+
pub const VaListAarch64 = extern struct {
626+
__stack: *anyopaque,
627+
__gr_top: *anyopaque,
628+
__vr_top: *anyopaque,
629+
__gr_offs: c_int,
630+
__vr_offs: c_int,
631+
};
632+
633+
/// This data structure is used by the Zig language code generation and
634+
/// therefore must be kept in sync with the compiler implementation.
635+
pub const VaListHexagon = extern struct {
636+
__gpr: c_long,
637+
__fpr: c_long,
638+
__overflow_arg_area: *anyopaque,
639+
__reg_save_area: *anyopaque,
640+
};
641+
642+
/// This data structure is used by the Zig language code generation and
643+
/// therefore must be kept in sync with the compiler implementation.
644+
pub const VaListPowerPc = extern struct {
645+
gpr: u8,
646+
fpr: u8,
647+
reserved: c_ushort,
648+
overflow_arg_area: *anyopaque,
649+
reg_save_area: *anyopaque,
650+
};
651+
652+
/// This data structure is used by the Zig language code generation and
653+
/// therefore must be kept in sync with the compiler implementation.
654+
pub const VaListS390x = extern struct {
655+
__current_saved_reg_area_pointer: *anyopaque,
656+
__saved_reg_area_end_pointer: *anyopaque,
657+
__overflow_area_pointer: *anyopaque,
658+
};
659+
660+
/// This data structure is used by the Zig language code generation and
661+
/// therefore must be kept in sync with the compiler implementation.
662+
pub const VaListX86_64 = extern struct {
663+
gp_offset: c_uint,
664+
fp_offset: c_uint,
665+
overflow_arg_area: *anyopaque,
666+
reg_save_area: *anyopaque,
667+
};
668+
669+
/// This data structure is used by the Zig language code generation and
670+
/// therefore must be kept in sync with the compiler implementation.
671+
pub const VaList = switch (builtin.cpu.arch) {
672+
.aarch64 => switch (builtin.os.tag) {
673+
.windows => *u8,
674+
.ios, .macos, .tvos, .watchos => *u8,
675+
else => @compileError("disabled due to miscompilations"), // VaListAarch64,
676+
},
677+
.arm => switch (builtin.os.tag) {
678+
.ios, .macos, .tvos, .watchos => *u8,
679+
else => *anyopaque,
680+
},
681+
.amdgcn => *u8,
682+
.avr => *anyopaque,
683+
.bpfel, .bpfeb => *anyopaque,
684+
.hexagon => if (builtin.target.isMusl()) VaListHexagon else *u8,
685+
.mips, .mipsel, .mips64, .mips64el => *anyopaque,
686+
.riscv32, .riscv64 => *anyopaque,
687+
.powerpc, .powerpcle => switch (builtin.os.tag) {
688+
.ios, .macos, .tvos, .watchos, .aix => *u8,
689+
else => VaListPowerPc,
690+
},
691+
.powerpc64, .powerpc64le => *u8,
692+
.sparc, .sparcel, .sparc64 => *anyopaque,
693+
.spirv32, .spirv64 => *anyopaque,
694+
.s390x => VaListS390x,
695+
.wasm32, .wasm64 => *anyopaque,
696+
.x86 => *u8,
697+
.x86_64 => switch (builtin.os.tag) {
698+
.windows => @compileError("disabled due to miscompilations"), // *u8,
699+
else => VaListX86_64,
700+
},
701+
else => @compileError("VaList not supported for this target yet"),
702+
};
703+
623704
/// This data structure is used by the Zig language code generation and
624705
/// therefore must be kept in sync with the compiler implementation.
625706
pub const PrefetchOptions = struct {

src/Air.zig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,19 @@ pub const Inst = struct {
741741
/// Uses the `vector_store_elem` field.
742742
vector_store_elem,
743743

744+
/// Implements @cVaArg builtin.
745+
/// Uses the `ty_op` field.
746+
c_va_arg,
747+
/// Implements @cVaCopy builtin.
748+
/// Uses the `ty_op` field.
749+
c_va_copy,
750+
/// Implements @cVaEnd builtin.
751+
/// Uses the `un_op` field.
752+
c_va_end,
753+
/// Implements @cVaStart builtin.
754+
/// Uses the `ty` field.
755+
c_va_start,
756+
744757
pub fn fromCmpOp(op: std.math.CompareOperator, optimized: bool) Tag {
745758
switch (op) {
746759
.lt => return if (optimized) .cmp_lt_optimized else .cmp_lt,
@@ -1092,6 +1105,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
10921105
.ret_ptr,
10931106
.arg,
10941107
.err_return_trace,
1108+
.c_va_start,
10951109
=> return datas[inst].ty,
10961110

10971111
.assembly,
@@ -1156,6 +1170,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
11561170
.byte_swap,
11571171
.bit_reverse,
11581172
.addrspace_cast,
1173+
.c_va_arg,
1174+
.c_va_copy,
11591175
=> return air.getRefType(datas[inst].ty_op.ty),
11601176

11611177
.loop,
@@ -1187,6 +1203,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
11871203
.prefetch,
11881204
.set_err_return_trace,
11891205
.vector_store_elem,
1206+
.c_va_end,
11901207
=> return Type.void,
11911208

11921209
.ptrtoint,

src/AstGen.zig

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ string_table: std.HashMapUnmanaged(u32, void, StringIndexContext, std.hash_map.d
4242
compile_errors: ArrayListUnmanaged(Zir.Inst.CompileErrors.Item) = .{},
4343
/// The topmost block of the current function.
4444
fn_block: ?*GenZir = null,
45+
fn_var_args: bool = false,
4546
/// Maps string table indexes to the first `@import` ZIR instruction
4647
/// that uses this string as the operand.
4748
imports: std.AutoArrayHashMapUnmanaged(u32, Ast.TokenIndex) = .{},
@@ -3892,17 +3893,17 @@ fn fnDecl(
38923893
.noalias_bits = noalias_bits,
38933894
});
38943895
} else func: {
3895-
if (is_var_args) {
3896-
return astgen.failTok(fn_proto.ast.fn_token, "non-extern function is variadic", .{});
3897-
}
3898-
38993896
// as a scope, fn_gz encloses ret_gz, but for instruction list, fn_gz stacks on ret_gz
39003897
fn_gz.instructions_top = ret_gz.instructions.items.len;
39013898

39023899
const prev_fn_block = astgen.fn_block;
39033900
astgen.fn_block = &fn_gz;
39043901
defer astgen.fn_block = prev_fn_block;
39053902

3903+
const prev_var_args = astgen.fn_var_args;
3904+
astgen.fn_var_args = is_var_args;
3905+
defer astgen.fn_var_args = prev_var_args;
3906+
39063907
astgen.advanceSourceCursorToNode(body_node);
39073908
const lbrace_line = astgen.source_line - decl_gz.decl_line;
39083909
const lbrace_column = astgen.source_column;
@@ -6071,15 +6072,15 @@ fn whileExpr(
60716072
const tag: Zir.Inst.Tag = if (payload_is_ref) .is_non_err_ptr else .is_non_err;
60726073
break :c .{
60736074
.inst = err_union,
6074-
.bool_bit = try cond_scope.addUnNode(tag, err_union, while_full.ast.then_expr),
6075+
.bool_bit = try cond_scope.addUnNode(tag, err_union, while_full.ast.cond_expr),
60756076
};
60766077
} else if (while_full.payload_token) |_| {
60776078
const cond_ri: ResultInfo = .{ .rl = if (payload_is_ref) .ref else .none };
60786079
const optional = try expr(&cond_scope, &cond_scope.base, cond_ri, while_full.ast.cond_expr);
60796080
const tag: Zir.Inst.Tag = if (payload_is_ref) .is_non_null_ptr else .is_non_null;
60806081
break :c .{
60816082
.inst = optional,
6082-
.bool_bit = try cond_scope.addUnNode(tag, optional, while_full.ast.then_expr),
6083+
.bool_bit = try cond_scope.addUnNode(tag, optional, while_full.ast.cond_expr),
60836084
};
60846085
} else {
60856086
const cond = try expr(&cond_scope, &cond_scope.base, bool_ri, while_full.ast.cond_expr);
@@ -8384,6 +8385,46 @@ fn builtinCall(
83848385
});
83858386
return rvalue(gz, ri, result, node);
83868387
},
8388+
.c_va_arg => {
8389+
if (astgen.fn_block == null) {
8390+
return astgen.failNode(node, "'@cVaArg' outside function scope", .{});
8391+
}
8392+
const result = try gz.addExtendedPayload(.c_va_arg, Zir.Inst.BinNode{
8393+
.node = gz.nodeIndexToRelative(node),
8394+
.lhs = try expr(gz, scope, .{ .rl = .none }, params[0]),
8395+
.rhs = try typeExpr(gz, scope, params[1]),
8396+
});
8397+
return rvalue(gz, ri, result, node);
8398+
},
8399+
.c_va_copy => {
8400+
if (astgen.fn_block == null) {
8401+
return astgen.failNode(node, "'@cVaCopy' outside function scope", .{});
8402+
}
8403+
const result = try gz.addExtendedPayload(.c_va_copy, Zir.Inst.UnNode{
8404+
.node = gz.nodeIndexToRelative(node),
8405+
.operand = try expr(gz, scope, .{ .rl = .none }, params[0]),
8406+
});
8407+
return rvalue(gz, ri, result, node);
8408+
},
8409+
.c_va_end => {
8410+
if (astgen.fn_block == null) {
8411+
return astgen.failNode(node, "'@cVaEnd' outside function scope", .{});
8412+
}
8413+
const result = try gz.addExtendedPayload(.c_va_end, Zir.Inst.UnNode{
8414+
.node = gz.nodeIndexToRelative(node),
8415+
.operand = try expr(gz, scope, .{ .rl = .none }, params[0]),
8416+
});
8417+
return rvalue(gz, ri, result, node);
8418+
},
8419+
.c_va_start => {
8420+
if (astgen.fn_block == null) {
8421+
return astgen.failNode(node, "'@cVaStart' outside function scope", .{});
8422+
}
8423+
if (!astgen.fn_var_args) {
8424+
return astgen.failNode(node, "'@cVaStart' in a non-variadic function", .{});
8425+
}
8426+
return rvalue(gz, ri, try gz.addNodeExtended(.c_va_start, node), node);
8427+
},
83878428
}
83888429
}
83898430

src/BuiltinFn.zig

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ pub const Tag = enum {
3030
compile_log,
3131
ctz,
3232
c_undef,
33+
c_va_arg,
34+
c_va_copy,
35+
c_va_end,
36+
c_va_start,
3337
div_exact,
3438
div_floor,
3539
div_trunc,
@@ -354,6 +358,30 @@ pub const list = list: {
354358
.param_count = 1,
355359
},
356360
},
361+
.{
362+
"@cVaArg", .{
363+
.tag = .c_va_arg,
364+
.param_count = 2,
365+
},
366+
},
367+
.{
368+
"@cVaCopy", .{
369+
.tag = .c_va_copy,
370+
.param_count = 1,
371+
},
372+
},
373+
.{
374+
"@cVaEnd", .{
375+
.tag = .c_va_end,
376+
.param_count = 1,
377+
},
378+
},
379+
.{
380+
"@cVaStart", .{
381+
.tag = .c_va_start,
382+
.param_count = 0,
383+
},
384+
},
357385
.{
358386
"@divExact",
359387
.{

src/Liveness.zig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ pub fn categorizeOperand(
238238
.wasm_memory_size,
239239
.err_return_trace,
240240
.save_err_return_trace_index,
241+
.c_va_start,
241242
=> return .none,
242243

243244
.fence => return .write,
@@ -279,6 +280,8 @@ pub fn categorizeOperand(
279280
.splat,
280281
.error_set_has_value,
281282
.addrspace_cast,
283+
.c_va_arg,
284+
.c_va_copy,
282285
=> {
283286
const o = air_datas[inst].ty_op;
284287
if (o.operand == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
@@ -322,6 +325,7 @@ pub fn categorizeOperand(
322325
.trunc_float,
323326
.neg,
324327
.cmp_lt_errors_len,
328+
.c_va_end,
325329
=> {
326330
const o = air_datas[inst].un_op;
327331
if (o == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
@@ -857,6 +861,7 @@ fn analyzeInst(
857861
.wasm_memory_size,
858862
.err_return_trace,
859863
.save_err_return_trace_index,
864+
.c_va_start,
860865
=> return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }),
861866

862867
.not,
@@ -898,6 +903,8 @@ fn analyzeInst(
898903
.splat,
899904
.error_set_has_value,
900905
.addrspace_cast,
906+
.c_va_arg,
907+
.c_va_copy,
901908
=> {
902909
const o = inst_datas[inst].ty_op;
903910
return trackOperands(a, new_set, inst, main_tomb, .{ o.operand, .none, .none });
@@ -936,6 +943,7 @@ fn analyzeInst(
936943
.neg_optimized,
937944
.cmp_lt_errors_len,
938945
.set_err_return_trace,
946+
.c_va_end,
939947
=> {
940948
const operand = inst_datas[inst].un_op;
941949
return trackOperands(a, new_set, inst, main_tomb, .{ operand, .none, .none });

0 commit comments

Comments
 (0)