authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-12-13 00:14:54+02:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-12-17 13:22:09+02:00
log9bb1104e373dec192fb2a22d48b023330ddbaeae
tree0105b65b2a6e405ee13f2c8f3719c81d5de1b2ad
parent728dd29f1ac4e75111fec0299e50cf94c6a78760

implement defining C variadic functions


21 files changed, 598 insertions(+), 12 deletions(-)

doc/langref.html.in+53-6
......@@ -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#}
10809export fn printf(format: [*:0]const u8, ...) c_int {
10810 _ = format;
10836 {#code_begin|test|defining_variadic_function#}
10837const std = @import("std");
10838const testing = std.testing;
10839const builtin = @import("builtin");
1081110840
10812 return 0;
10841fn 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
10852test "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+81
......@@ -623,6 +623,87 @@ pub const CallModifier = enum {
623623 compile_time,
624624};
625625
626/// This data structure is used by the Zig language code generation and
627/// therefore must be kept in sync with the compiler implementation.
628pub const VaListAarch64 = extern struct {
629 __stack: *anyopaque,
630 __gr_top: *anyopaque,
631 __vr_top: *anyopaque,
632 __gr_offs: c_int,
633 __vr_offs: c_int,
634};
635
636/// This data structure is used by the Zig language code generation and
637/// therefore must be kept in sync with the compiler implementation.
638pub const VaListHexagon = extern struct {
639 __gpr: c_long,
640 __fpr: c_long,
641 __overflow_arg_area: *anyopaque,
642 __reg_save_area: *anyopaque,
643};
644
645/// This data structure is used by the Zig language code generation and
646/// therefore must be kept in sync with the compiler implementation.
647pub const VaListPowerPc = extern struct {
648 gpr: u8,
649 fpr: u8,
650 reserved: c_ushort,
651 overflow_arg_area: *anyopaque,
652 reg_save_area: *anyopaque,
653};
654
655/// This data structure is used by the Zig language code generation and
656/// therefore must be kept in sync with the compiler implementation.
657pub const VaListS390x = extern struct {
658 __current_saved_reg_area_pointer: *anyopaque,
659 __saved_reg_area_end_pointer: *anyopaque,
660 __overflow_area_pointer: *anyopaque,
661};
662
663/// This data structure is used by the Zig language code generation and
664/// therefore must be kept in sync with the compiler implementation.
665pub const VaListX86_64 = extern struct {
666 gp_offset: c_uint,
667 fp_offset: c_uint,
668 overflow_arg_area: *anyopaque,
669 reg_save_area: *anyopaque,
670};
671
672/// This data structure is used by the Zig language code generation and
673/// therefore must be kept in sync with the compiler implementation.
674pub const VaList = switch (builtin.cpu.arch) {
675 .aarch64 => switch (builtin.os.tag) {
676 .windows => *u8,
677 .ios, .macos, .tvos, .watchos => *u8,
678 else => @compileError("disabled due to miscompilations"), // VaListAarch64,
679 },
680 .arm => switch (builtin.os.tag) {
681 .ios, .macos, .tvos, .watchos => *u8,
682 else => *anyopaque,
683 },
684 .amdgcn => *u8,
685 .avr => *anyopaque,
686 .bpfel, .bpfeb => *anyopaque,
687 .hexagon => if (builtin.target.isMusl()) VaListHexagon else *u8,
688 .mips, .mipsel, .mips64, .mips64el => *anyopaque,
689 .riscv32, .riscv64 => *anyopaque,
690 .powerpc, .powerpcle => switch (builtin.os.tag) {
691 .ios, .macos, .tvos, .watchos, .aix => *u8,
692 else => VaListPowerPc,
693 },
694 .powerpc64, .powerpc64le => *u8,
695 .sparc, .sparcel, .sparc64 => *anyopaque,
696 .spirv32, .spirv64 => *anyopaque,
697 .s390x => VaListS390x,
698 .wasm32, .wasm64 => *anyopaque,
699 .x86 => *u8,
700 .x86_64 => switch (builtin.os.tag) {
701 .windows => @compileError("disabled due to miscompilations"), // *u8,
702 else => VaListX86_64,
703 },
704 else => @compileError("VaList not supported for this target yet"),
705};
706
626707/// This data structure is used by the Zig language code generation and
627708/// therefore must be kept in sync with the compiler implementation.
628709pub const PrefetchOptions = struct {
src/Air.zig+17
......@@ -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+45-4
......@@ -42,6 +42,7 @@ string_table: std.HashMapUnmanaged(u32, void, StringIndexContext, std.hash_map.d
4242compile_errors: ArrayListUnmanaged(Zir.Inst.CompileErrors.Item) = .{},
4343/// The topmost block of the current function.
4444fn_block: ?*GenZir = null,
45fn_var_args: bool = false,
4546/// Maps string table indexes to the first `@import` ZIR instruction
4647/// that uses this string as the operand.
4748imports: std.AutoArrayHashMapUnmanaged(u32, Ast.TokenIndex) = .{},
......@@ -3892,10 +3893,6 @@ 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
......@@ -3903,6 +3900,10 @@ fn fnDecl(
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;
......@@ -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+28
......@@ -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+8
......@@ -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 });
src/Sema.zig+96-1
......@@ -1148,6 +1148,10 @@ fn analyzeBodyInner(
11481148 .builtin_async_call => try sema.zirBuiltinAsyncCall( block, extended),
11491149 .cmpxchg => try sema.zirCmpxchg( block, extended),
11501150 .addrspace_cast => try sema.zirAddrSpaceCast( block, extended),
1151 .c_va_arg => try sema.zirCVaArg( block, extended),
1152 .c_va_copy => try sema.zirCVaCopy( block, extended),
1153 .c_va_end => try sema.zirCVaEnd( block, extended),
1154 .c_va_start => try sema.zirCVaStart( block, extended),
11511155 // zig fmt: on
11521156
11531157 .fence => {
......@@ -6427,6 +6431,11 @@ fn analyzeCall(
64276431 else => unreachable,
64286432 };
64296433 if (!is_comptime_call and module_fn.state == .sema_failure) return error.AnalysisFail;
6434 if (func_ty_info.is_var_args) {
6435 return sema.fail(block, call_src, "{s} call of variadic function", .{
6436 @as([]const u8, if (is_comptime_call) "comptime" else "inline"),
6437 });
6438 }
64306439
64316440 // Analyze the ZIR. The same ZIR gets analyzed into a runtime function
64326441 // or an inlined call depending on what union tag the `label` field is
......@@ -8404,6 +8413,7 @@ fn funcCommon(
84048413) CompileError!Air.Inst.Ref {
84058414 const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = src_node_offset };
84068415 const cc_src: LazySrcLoc = .{ .node_offset_fn_type_cc = src_node_offset };
8416 const func_src = LazySrcLoc.nodeOffset(src_node_offset);
84078417
84088418 var is_generic = bare_return_type.tag() == .generic_poison or
84098419 alignment == null or
......@@ -8411,6 +8421,15 @@ fn funcCommon(
84118421 section == .generic or
84128422 cc == null;
84138423
8424 if (var_args) {
8425 if (is_generic) {
8426 return sema.fail(block, func_src, "generic function cannot be variadic", .{});
8427 }
8428 if (cc.? != .C) {
8429 return sema.fail(block, cc_src, "variadic function must have 'C' calling convention", .{});
8430 }
8431 }
8432
84148433 var destroy_fn_on_error = false;
84158434 const new_func: *Module.Fn = new_func: {
84168435 if (!has_body) break :new_func undefined;
......@@ -19054,6 +19073,79 @@ fn zirAddrSpaceCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Inst
1905419073 });
1905519074}
1905619075
19076fn resolveVaListRef(sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref) CompileError!Air.Inst.Ref {
19077 const va_list_ty = try sema.getBuiltinType("VaList");
19078 const va_list_ptr = try Type.ptr(sema.arena, sema.mod, .{
19079 .pointee_type = va_list_ty,
19080 .mutable = true,
19081 .@"addrspace" = .generic,
19082 });
19083
19084 const inst = try sema.resolveInst(zir_ref);
19085 return sema.coerce(block, va_list_ptr, inst, src);
19086}
19087
19088fn zirCVaArg(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
19089 const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
19090 const src = LazySrcLoc.nodeOffset(extra.node);
19091 const va_list_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node };
19092 const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node };
19093
19094 const va_list_ref = try sema.resolveVaListRef(block, va_list_src, extra.lhs);
19095 const arg_ty = try sema.resolveType(block, ty_src, extra.rhs);
19096
19097 if (!try sema.validateExternType(arg_ty, .param_ty)) {
19098 const msg = msg: {
19099 const msg = try sema.errMsg(block, ty_src, "cannot get '{}' from variadic argument", .{arg_ty.fmt(sema.mod)});
19100 errdefer msg.destroy(sema.gpa);
19101
19102 const src_decl = sema.mod.declPtr(block.src_decl);
19103 try sema.explainWhyTypeIsNotExtern(msg, ty_src.toSrcLoc(src_decl), arg_ty, .param_ty);
19104
19105 try sema.addDeclaredHereNote(msg, arg_ty);
19106 break :msg msg;
19107 };
19108 return sema.failWithOwnedErrorMsg(msg);
19109 }
19110
19111 try sema.requireRuntimeBlock(block, src, null);
19112 return block.addTyOp(.c_va_arg, arg_ty, va_list_ref);
19113}
19114
19115fn zirCVaCopy(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
19116 const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
19117 const src = LazySrcLoc.nodeOffset(extra.node);
19118 const va_list_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node };
19119
19120 const va_list_ref = try sema.resolveVaListRef(block, va_list_src, extra.operand);
19121 const va_list_ty = try sema.getBuiltinType("VaList");
19122
19123 try sema.requireRuntimeBlock(block, src, null);
19124 return block.addTyOp(.c_va_copy, va_list_ty, va_list_ref);
19125}
19126
19127fn zirCVaEnd(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
19128 const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
19129 const src = LazySrcLoc.nodeOffset(extra.node);
19130 const va_list_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node };
19131
19132 const va_list_ref = try sema.resolveVaListRef(block, va_list_src, extra.operand);
19133
19134 try sema.requireRuntimeBlock(block, src, null);
19135 return block.addUnOp(.c_va_end, va_list_ref);
19136}
19137
19138fn zirCVaStart(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
19139 const src = LazySrcLoc.nodeOffset(@bitCast(i32, extended.operand));
19140
19141 const va_list_ty = try sema.getBuiltinType("VaList");
19142 try sema.requireRuntimeBlock(block, src, null);
19143 return block.addInst(.{
19144 .tag = .c_va_start,
19145 .data = .{ .ty = va_list_ty },
19146 });
19147}
19148
1905719149fn zirTypeName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
1905819150 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
1905919151 const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
......@@ -21558,7 +21650,10 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
2155821650 else => |e| return e,
2155921651 };
2156021652 break :blk cc_tv.val.toEnum(std.builtin.CallingConvention);
21561 } else std.builtin.CallingConvention.Unspecified;
21653 } else if (sema.owner_decl.is_exported and has_body)
21654 .C
21655 else
21656 .Unspecified;
2156221657
2156321658 const ret_ty: Type = if (extra.data.bits.has_ret_ty_body) blk: {
2156421659 const body_len = sema.code.extra[extra_index];
src/Zir.zig+12
......@@ -1993,6 +1993,18 @@ pub const Inst = struct {
19931993 /// Implement the builtin `@addrSpaceCast`
19941994 /// `Operand` is payload index to `BinNode`. `lhs` is dest type, `rhs` is operand.
19951995 addrspace_cast,
1996 /// Implement builtin `@cVaArg`.
1997 /// `operand` is payload index to `BinNode`.
1998 c_va_arg,
1999 /// Implement builtin `@cVaStart`.
2000 /// `operand` is payload index to `UnNode`.
2001 c_va_copy,
2002 /// Implement builtin `@cVaStart`.
2003 /// `operand` is payload index to `UnNode`.
2004 c_va_end,
2005 /// Implement builtin `@cVaStart`.
2006 /// `operand` is `src_node: i32`.
2007 c_va_start,
19962008
19972009 pub const InstData = struct {
19982010 opcode: Extended,
src/arch/aarch64/CodeGen.zig+5
......@@ -875,6 +875,11 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
875875 .error_set_has_value => return self.fail("TODO implement error_set_has_value", .{}),
876876 .vector_store_elem => return self.fail("TODO implement vector_store_elem", .{}),
877877
878 .c_va_arg => return self.fail("TODO implement c_va_arg", .{}),
879 .c_va_copy => return self.fail("TODO implement c_va_copy", .{}),
880 .c_va_end => return self.fail("TODO implement c_va_end", .{}),
881 .c_va_start => return self.fail("TODO implement c_va_start", .{}),
882
878883 .wasm_memory_size => unreachable,
879884 .wasm_memory_grow => unreachable,
880885 // zig fmt: on
src/arch/arm/CodeGen.zig+5
......@@ -785,6 +785,11 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
785785 .error_set_has_value => return self.fail("TODO implement error_set_has_value", .{}),
786786 .vector_store_elem => return self.fail("TODO implement vector_store_elem", .{}),
787787
788 .c_va_arg => return self.fail("TODO implement c_va_arg", .{}),
789 .c_va_copy => return self.fail("TODO implement c_va_copy", .{}),
790 .c_va_end => return self.fail("TODO implement c_va_end", .{}),
791 .c_va_start => return self.fail("TODO implement c_va_start", .{}),
792
788793 .wasm_memory_size => unreachable,
789794 .wasm_memory_grow => unreachable,
790795 // zig fmt: on
src/arch/riscv64/CodeGen.zig+5
......@@ -699,6 +699,11 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
699699 .error_set_has_value => return self.fail("TODO implement error_set_has_value", .{}),
700700 .vector_store_elem => return self.fail("TODO implement vector_store_elem", .{}),
701701
702 .c_va_arg => return self.fail("TODO implement c_va_arg", .{}),
703 .c_va_copy => return self.fail("TODO implement c_va_copy", .{}),
704 .c_va_end => return self.fail("TODO implement c_va_end", .{}),
705 .c_va_start => return self.fail("TODO implement c_va_start", .{}),
706
702707 .wasm_memory_size => unreachable,
703708 .wasm_memory_grow => unreachable,
704709 // zig fmt: on
src/arch/sparc64/CodeGen.zig+5
......@@ -716,6 +716,11 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
716716 .error_set_has_value => @panic("TODO implement error_set_has_value"),
717717 .vector_store_elem => @panic("TODO implement vector_store_elem"),
718718
719 .c_va_arg => @panic("TODO implement c_va_arg"),
720 .c_va_copy => @panic("TODO implement c_va_copy"),
721 .c_va_end => @panic("TODO implement c_va_end"),
722 .c_va_start => @panic("TODO implement c_va_start"),
723
719724 .wasm_memory_size => unreachable,
720725 .wasm_memory_grow => unreachable,
721726 // zig fmt: on
src/arch/wasm/CodeGen.zig+4
......@@ -1972,6 +1972,10 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
19721972 .error_set_has_value,
19731973 .addrspace_cast,
19741974 .vector_store_elem,
1975 .c_va_arg,
1976 .c_va_copy,
1977 .c_va_end,
1978 .c_va_start,
19751979 => |tag| return func.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
19761980
19771981 .add_optimized,
src/arch/x86_64/CodeGen.zig+5
......@@ -787,6 +787,11 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
787787 .error_set_has_value => return self.fail("TODO implement error_set_has_value", .{}),
788788 .vector_store_elem => return self.fail("TODO implement vector_store_elem", .{}),
789789
790 .c_va_arg => return self.fail("TODO implement c_va_arg", .{}),
791 .c_va_copy => return self.fail("TODO implement c_va_copy", .{}),
792 .c_va_end => return self.fail("TODO implement c_va_end", .{}),
793 .c_va_start => return self.fail("TODO implement c_va_start", .{}),
794
790795 .wasm_memory_size => unreachable,
791796 .wasm_memory_grow => unreachable,
792797 // zig fmt: on
src/codegen/c.zig+5
......@@ -2909,6 +2909,11 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
29092909 .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}),
29102910 .error_set_has_value => return f.fail("TODO: C backend: implement error_set_has_value", .{}),
29112911 .vector_store_elem => return f.fail("TODO: C backend: implement vector_store_elem", .{}),
2912
2913 .c_va_arg => return f.fail("TODO implement c_va_arg", .{}),
2914 .c_va_copy => return f.fail("TODO implement c_va_copy", .{}),
2915 .c_va_end => return f.fail("TODO implement c_va_end", .{}),
2916 .c_va_start => return f.fail("TODO implement c_va_start", .{}),
29122917 // zig fmt: on
29132918 };
29142919 if (result_value == .local) {
src/codegen/llvm.zig+93
......@@ -4699,6 +4699,11 @@ pub const FuncGen = struct {
46994699 .dbg_block_end => try self.airDbgBlockEnd(),
47004700 .dbg_var_ptr => try self.airDbgVarPtr(inst),
47014701 .dbg_var_val => try self.airDbgVarVal(inst),
4702
4703 .c_va_arg => try self.airCVaArg(inst),
4704 .c_va_copy => try self.airCVaCopy(inst),
4705 .c_va_end => try self.airCVaEnd(inst),
4706 .c_va_start => try self.airCVaStart(inst),
47024707 // zig fmt: on
47034708 };
47044709 if (opt_value) |val| {
......@@ -5136,6 +5141,94 @@ pub const FuncGen = struct {
51365141 return null;
51375142 }
51385143
5144 fn airCVaArg(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
5145 if (self.liveness.isUnused(inst)) return null;
5146
5147 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
5148 const list = try self.resolveInst(ty_op.operand);
5149 const arg_ty = self.air.getRefType(ty_op.ty);
5150 const llvm_arg_ty = try self.dg.lowerType(arg_ty);
5151
5152 return self.builder.buildVAArg(list, llvm_arg_ty, "");
5153 }
5154
5155 fn airCVaCopy(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
5156 if (self.liveness.isUnused(inst)) return null;
5157
5158 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
5159 const src_list = try self.resolveInst(ty_op.operand);
5160 const va_list_ty = self.air.getRefType(ty_op.ty);
5161 const llvm_va_list_ty = try self.dg.lowerType(va_list_ty);
5162
5163 const target = self.dg.module.getTarget();
5164 const result_alignment = va_list_ty.abiAlignment(target);
5165 const dest_list = self.buildAlloca(llvm_va_list_ty, result_alignment);
5166
5167 const llvm_fn_name = "llvm.va_copy";
5168 const llvm_fn = self.dg.object.llvm_module.getNamedFunction(llvm_fn_name) orelse blk: {
5169 const param_types = [_]*llvm.Type{
5170 self.dg.context.intType(8).pointerType(0),
5171 self.dg.context.intType(8).pointerType(0),
5172 };
5173 const fn_type = llvm.functionType(self.context.voidType(), &param_types, param_types.len, .False);
5174 break :blk self.dg.object.llvm_module.addFunction(llvm_fn_name, fn_type);
5175 };
5176
5177 const args: [2]*llvm.Value = .{ dest_list, src_list };
5178 _ = self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, &args, args.len, .Fast, .Auto, "");
5179
5180 if (isByRef(va_list_ty)) {
5181 return dest_list;
5182 } else {
5183 const loaded = self.builder.buildLoad(llvm_va_list_ty, dest_list, "");
5184 loaded.setAlignment(result_alignment);
5185 return loaded;
5186 }
5187 }
5188
5189 fn airCVaEnd(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
5190 const un_op = self.air.instructions.items(.data)[inst].un_op;
5191 const list = try self.resolveInst(un_op);
5192
5193 const llvm_fn_name = "llvm.va_end";
5194 const llvm_fn = self.dg.object.llvm_module.getNamedFunction(llvm_fn_name) orelse blk: {
5195 const param_types = [_]*llvm.Type{self.dg.context.intType(8).pointerType(0)};
5196 const fn_type = llvm.functionType(self.context.voidType(), &param_types, param_types.len, .False);
5197 break :blk self.dg.object.llvm_module.addFunction(llvm_fn_name, fn_type);
5198 };
5199 const args: [1]*llvm.Value = .{list};
5200 _ = self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, &args, args.len, .Fast, .Auto, "");
5201 return null;
5202 }
5203
5204 fn airCVaStart(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
5205 if (self.liveness.isUnused(inst)) return null;
5206
5207 const va_list_ty = self.air.typeOfIndex(inst);
5208 const llvm_va_list_ty = try self.dg.lowerType(va_list_ty);
5209
5210 const target = self.dg.module.getTarget();
5211 const result_alignment = va_list_ty.abiAlignment(target);
5212 const list = self.buildAlloca(llvm_va_list_ty, result_alignment);
5213
5214 const llvm_fn_name = "llvm.va_start";
5215 const llvm_fn = self.dg.object.llvm_module.getNamedFunction(llvm_fn_name) orelse blk: {
5216 const param_types = [_]*llvm.Type{self.dg.context.intType(8).pointerType(0)};
5217 const fn_type = llvm.functionType(self.context.voidType(), &param_types, param_types.len, .False);
5218 break :blk self.dg.object.llvm_module.addFunction(llvm_fn_name, fn_type);
5219 };
5220 const args: [1]*llvm.Value = .{list};
5221 _ = self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, &args, args.len, .Fast, .Auto, "");
5222
5223 if (isByRef(va_list_ty)) {
5224 return list;
5225 } else {
5226 const loaded = self.builder.buildLoad(llvm_va_list_ty, list, "");
5227 loaded.setAlignment(result_alignment);
5228 return loaded;
5229 }
5230 }
5231
51395232 fn airCmp(self: *FuncGen, inst: Air.Inst.Index, op: math.CompareOperator, want_fast_math: bool) !?*llvm.Value {
51405233 if (self.liveness.isUnused(inst)) return null;
51415234 self.builder.setFastMath(want_fast_math);
src/codegen/llvm/bindings.zig+3
......@@ -965,6 +965,9 @@ pub const Builder = opaque {
965965
966966 pub const buildAllocaInAddressSpace = ZigLLVMBuildAllocaInAddressSpace;
967967 extern fn ZigLLVMBuildAllocaInAddressSpace(B: *Builder, Ty: *Type, AddressSpace: c_uint, Name: [*:0]const u8) *Value;
968
969 pub const buildVAArg = LLVMBuildVAArg;
970 extern fn LLVMBuildVAArg(*Builder, List: *Value, Ty: *Type, Name: [*:0]const u8) *Value;
968971};
969972
970973pub const MDString = opaque {
src/print_air.zig+4
......@@ -191,6 +191,7 @@ const Writer = struct {
191191 .neg_optimized,
192192 .cmp_lt_errors_len,
193193 .set_err_return_trace,
194 .c_va_end,
194195 => try w.writeUnOp(s, inst),
195196
196197 .breakpoint,
......@@ -205,6 +206,7 @@ const Writer = struct {
205206 .ret_ptr,
206207 .arg,
207208 .err_return_trace,
209 .c_va_start,
208210 => try w.writeTy(s, inst),
209211
210212 .not,
......@@ -246,6 +248,8 @@ const Writer = struct {
246248 .bit_reverse,
247249 .error_set_has_value,
248250 .addrspace_cast,
251 .c_va_arg,
252 .c_va_copy,
249253 => try w.writeTyOp(s, inst),
250254
251255 .block,
src/print_zir.zig+4
......@@ -465,6 +465,7 @@ const Writer = struct {
465465 .frame,
466466 .frame_address,
467467 .breakpoint,
468 .c_va_start,
468469 => try self.writeExtNode(stream, extended),
469470
470471 .builtin_src => {
......@@ -504,6 +505,8 @@ const Writer = struct {
504505 .error_to_int,
505506 .int_to_error,
506507 .reify,
508 .c_va_copy,
509 .c_va_end,
507510 => {
508511 const inst_data = self.code.extraData(Zir.Inst.UnNode, extended.operand).data;
509512 const src = LazySrcLoc.nodeOffset(inst_data.node);
......@@ -518,6 +521,7 @@ const Writer = struct {
518521 .wasm_memory_grow,
519522 .prefetch,
520523 .addrspace_cast,
524 .c_va_arg,
521525 => {
522526 const inst_data = self.code.extraData(Zir.Inst.BinNode, extended.operand).data;
523527 const src = LazySrcLoc.nodeOffset(inst_data.node);
test/behavior/var_args.zig+108-1
......@@ -1,5 +1,6 @@
11const builtin = @import("builtin");
2const expect = @import("std").testing.expect;
2const std = @import("std");
3const expect = std.testing.expect;
34
45fn add(args: anytype) i32 {
56 var sum = @as(i32, 0);
......@@ -91,3 +92,109 @@ test "pass zero length array to var args param" {
9192fn doNothingWithFirstArg(args: anytype) void {
9293 _ = args[0];
9394}
95
96test "simple variadic function" {
97 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
98 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
99 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
100 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
101 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
102 if (builtin.cpu.arch == .aarch64 and builtin.os.tag != .windows and builtin.os.tag != .macos) return error.SkipZigTest; // TODO
103 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO
104
105 const S = struct {
106 fn simple(...) callconv(.C) c_int {
107 var ap = @cVaStart();
108 defer @cVaEnd(&ap);
109 return @cVaArg(&ap, c_int);
110 }
111
112 fn add(count: c_int, ...) callconv(.C) c_int {
113 var ap = @cVaStart();
114 defer @cVaEnd(&ap);
115 var i: usize = 0;
116 var sum: c_int = 0;
117 while (i < count) : (i += 1) {
118 sum += @cVaArg(&ap, c_int);
119 }
120 return sum;
121 }
122 };
123
124 try std.testing.expectEqual(@as(c_int, 0), S.simple(@as(c_int, 0)));
125 try std.testing.expectEqual(@as(c_int, 1024), S.simple(@as(c_int, 1024)));
126 try std.testing.expectEqual(@as(c_int, 0), S.add(0));
127 try std.testing.expectEqual(@as(c_int, 1), S.add(1, @as(c_int, 1)));
128 try std.testing.expectEqual(@as(c_int, 3), S.add(2, @as(c_int, 1), @as(c_int, 2)));
129}
130
131test "variadic functions" {
132 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
133 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
134 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
135 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
136 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
137 if (builtin.cpu.arch == .aarch64 and builtin.os.tag != .windows and builtin.os.tag != .macos) return error.SkipZigTest; // TODO
138 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO
139
140 const S = struct {
141 fn printf(list_ptr: *std.ArrayList(u8), format: [*:0]const u8, ...) callconv(.C) void {
142 var ap = @cVaStart();
143 defer @cVaEnd(&ap);
144 vprintf(list_ptr, format, &ap);
145 }
146
147 fn vprintf(
148 list: *std.ArrayList(u8),
149 format: [*:0]const u8,
150 ap: *std.builtin.VaList,
151 ) callconv(.C) void {
152 for (std.mem.span(format)) |c| switch (c) {
153 's' => {
154 const arg = @cVaArg(ap, [*:0]const u8);
155 list.writer().print("{s}", .{arg}) catch return;
156 },
157 'd' => {
158 const arg = @cVaArg(ap, c_int);
159 list.writer().print("{d}", .{arg}) catch return;
160 },
161 else => unreachable,
162 };
163 }
164 };
165
166 var list = std.ArrayList(u8).init(std.testing.allocator);
167 defer list.deinit();
168 S.printf(&list, "dsd", @as(c_int, 1), @as([*:0]const u8, "hello"), @as(c_int, 5));
169 try std.testing.expectEqualStrings("1hello5", list.items);
170}
171
172test "copy VaList" {
173 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
174 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
175 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
176 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
177 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
178 if (builtin.cpu.arch == .aarch64 and builtin.os.tag != .windows and builtin.os.tag != .macos) return error.SkipZigTest; // TODO
179 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO
180
181 const S = struct {
182 fn add(count: c_int, ...) callconv(.C) c_int {
183 var ap = @cVaStart();
184 defer @cVaEnd(&ap);
185 var copy = @cVaCopy(&ap);
186 defer @cVaEnd(&copy);
187 var i: usize = 0;
188 var sum: c_int = 0;
189 while (i < count) : (i += 1) {
190 sum += @cVaArg(&ap, c_int);
191 sum += @cVaArg(&copy, c_int) * 2;
192 }
193 return sum;
194 }
195 };
196
197 try std.testing.expectEqual(@as(c_int, 0), S.add(0));
198 try std.testing.expectEqual(@as(c_int, 3), S.add(1, @as(c_int, 1)));
199 try std.testing.expectEqual(@as(c_int, 9), S.add(2, @as(c_int, 1), @as(c_int, 2)));
200}
test/cases/compile_errors/invalid_variadic_function.zig created+12
......@@ -0,0 +1,12 @@
1fn foo(...) void {}
2fn bar(a: anytype, ...) callconv(a) void {}
3
4comptime { _ = foo; }
5comptime { _ = bar; }
6
7// error
8// backend=stage2
9// target=native
10//
11// :1:1: error: variadic function must have 'C' calling convention
12// :2:1: error: generic function cannot be variadic