Commit 57f6adf85d

Jacob Young <jacobly0@users.noreply.github.com>
2023-02-23 06:28:49
CBE: implement c varargs
Removed some backend test skip checks for things disabled in std.
1 parent 597e801
Changed files (3)
lib
src
codegen
test
behavior
lib/zig.h
@@ -5,6 +5,7 @@
 #endif
 #include <float.h>
 #include <limits.h>
+#include <stdarg.h>
 #include <stddef.h>
 #include <stdint.h>
 
src/codegen/c.zig
@@ -211,6 +211,15 @@ const reserved_idents = std.ComptimeStringMap(void, .{
     .{ "volatile", {} },
     .{ "while ", {} },
 
+    // stdarg.h
+    .{ "va_start", {} },
+    .{ "va_arg", {} },
+    .{ "va_end", {} },
+    .{ "va_copy", {} },
+
+    // stddef.h
+    .{ "offsetof", {} },
+
     // windows.h
     .{ "max", {} },
     .{ "min", {} },
@@ -2952,10 +2961,10 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
             .error_set_has_value => return f.fail("TODO: C backend: implement error_set_has_value", .{}),
             .vector_store_elem => return f.fail("TODO: C backend: implement vector_store_elem", .{}),
 
-            .c_va_arg => return f.fail("TODO implement c_va_arg", .{}),
-            .c_va_copy => return f.fail("TODO implement c_va_copy", .{}),
-            .c_va_end => return f.fail("TODO implement c_va_end", .{}),
-            .c_va_start => return f.fail("TODO implement c_va_start", .{}),
+            .c_va_start => try airCVaStart(f, inst),
+            .c_va_arg => try airCVaArg(f, inst),
+            .c_va_end => try airCVaEnd(f, inst),
+            .c_va_copy => try airCVaCopy(f, inst),
             // zig fmt: on
         };
         if (result_value == .new_local) {
@@ -6862,6 +6871,82 @@ fn airMulAdd(f: *Function, inst: Air.Inst.Index) !CValue {
     return local;
 }
 
+fn airCVaStart(f: *Function, inst: Air.Inst.Index) !CValue {
+    if (f.liveness.isUnused(inst)) return .none;
+
+    const inst_ty = f.air.typeOfIndex(inst);
+    const fn_cty = try f.typeToCType(f.object.dg.decl.?.ty, .complete);
+
+    const param_len = fn_cty.castTag(.varargs_function).?.data.param_types.len;
+    if (param_len == 0)
+        return f.fail("CBE: C requires at least one runtime argument for varargs functions", .{});
+
+    const writer = f.object.writer();
+    const local = try f.allocLocal(inst, inst_ty);
+    try writer.writeAll("va_start(*(va_list *)&");
+    try f.writeCValue(writer, local, .Other);
+    try writer.writeAll(", ");
+    try f.writeCValue(writer, .{ .arg = param_len - 1 }, .FunctionArgument);
+    try writer.writeAll(");\n");
+    return local;
+}
+
+fn airCVaArg(f: *Function, inst: Air.Inst.Index) !CValue {
+    const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+    if (f.liveness.isUnused(inst)) {
+        try reap(f, inst, &.{ty_op.operand});
+        return .none;
+    }
+
+    const inst_ty = f.air.typeOfIndex(inst);
+    const va_list = try f.resolveInst(ty_op.operand);
+    try reap(f, inst, &.{ty_op.operand});
+
+    const writer = f.object.writer();
+    const local = try f.allocLocal(inst, inst_ty);
+    try f.writeCValue(writer, local, .Other);
+    try writer.writeAll(" = va_arg(*(va_list *)");
+    try f.writeCValue(writer, va_list, .Other);
+    try writer.writeAll(", ");
+    try f.renderType(writer, f.air.getRefType(ty_op.ty));
+    try writer.writeAll(");\n");
+    return local;
+}
+
+fn airCVaEnd(f: *Function, inst: Air.Inst.Index) !CValue {
+    const un_op = f.air.instructions.items(.data)[inst].un_op;
+
+    const va_list = try f.resolveInst(un_op);
+    try reap(f, inst, &.{un_op});
+
+    const writer = f.object.writer();
+    try writer.writeAll("va_end(*(va_list *)");
+    try f.writeCValue(writer, va_list, .Other);
+    try writer.writeAll(");\n");
+    return .none;
+}
+
+fn airCVaCopy(f: *Function, inst: Air.Inst.Index) !CValue {
+    const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+    if (f.liveness.isUnused(inst)) {
+        try reap(f, inst, &.{ty_op.operand});
+        return .none;
+    }
+
+    const inst_ty = f.air.typeOfIndex(inst);
+    const va_list = try f.resolveInst(ty_op.operand);
+    try reap(f, inst, &.{ty_op.operand});
+
+    const writer = f.object.writer();
+    const local = try f.allocLocal(inst, inst_ty);
+    try writer.writeAll("va_copy(*(va_list *)&");
+    try f.writeCValue(writer, local, .Other);
+    try writer.writeAll(", *(va_list *)");
+    try f.writeCValue(writer, va_list, .Other);
+    try writer.writeAll(");\n");
+    return local;
+}
+
 fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 {
     return switch (order) {
         // Note: unordered is actually even less atomic than relaxed
test/behavior/var_args.zig
@@ -96,10 +96,9 @@ fn doNothingWithFirstArg(args: anytype) void {
 test "simple variadic function" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
-    if (builtin.cpu.arch == .aarch64 and builtin.os.tag != .macos and builtin.zig_backend == .stage2_llvm) {
+    if (builtin.cpu.arch == .aarch64 and builtin.os.tag != .macos) {
         // https://github.com/ziglang/zig/issues/14096
         return error.SkipZigTest;
     }
@@ -124,8 +123,10 @@ test "simple variadic function" {
         }
     };
 
-    try std.testing.expectEqual(@as(c_int, 0), S.simple(@as(c_int, 0)));
-    try std.testing.expectEqual(@as(c_int, 1024), S.simple(@as(c_int, 1024)));
+    if (builtin.zig_backend != .stage2_c) { // C doesn't support varargs without a preceding runtime arg.
+        try std.testing.expectEqual(@as(c_int, 0), S.simple(@as(c_int, 0)));
+        try std.testing.expectEqual(@as(c_int, 1024), S.simple(@as(c_int, 1024)));
+    }
     try std.testing.expectEqual(@as(c_int, 0), S.add(0));
     try std.testing.expectEqual(@as(c_int, 1), S.add(1, @as(c_int, 1)));
     try std.testing.expectEqual(@as(c_int, 3), S.add(2, @as(c_int, 1), @as(c_int, 2)));
@@ -134,10 +135,9 @@ test "simple variadic function" {
 test "variadic functions" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
-    if (builtin.cpu.arch == .aarch64 and builtin.os.tag != .macos and builtin.zig_backend == .stage2_llvm) {
+    if (builtin.cpu.arch == .aarch64 and builtin.os.tag != .macos) {
         // https://github.com/ziglang/zig/issues/14096
         return error.SkipZigTest;
     }
@@ -178,10 +178,9 @@ test "variadic functions" {
 test "copy VaList" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
-    if (builtin.cpu.arch == .aarch64 and builtin.os.tag != .macos and builtin.zig_backend == .stage2_llvm) {
+    if (builtin.cpu.arch == .aarch64 and builtin.os.tag != .macos) {
         // https://github.com/ziglang/zig/issues/14096
         return error.SkipZigTest;
     }