Commit 8c6e7fb2c7

Veikka Tuominen <git@vexu.eu>
2021-01-29 11:19:10
stage2: implement var args
1 parent 17e6e09
Changed files (7)
src/codegen/c.zig
@@ -215,8 +215,9 @@ pub const DeclGen = struct {
         try dg.renderType(w, tv.ty.fnReturnType());
         const decl_name = mem.span(dg.decl.name);
         try w.print(" {s}(", .{decl_name});
-        var param_len = tv.ty.fnParamLen();
-        if (param_len == 0)
+        const param_len = tv.ty.fnParamLen();
+        const is_var_args = tv.ty.fnIsVarArgs();
+        if (param_len == 0 and !is_var_args)
             try w.writeAll("void")
         else {
             var index: usize = 0;
@@ -228,6 +229,10 @@ pub const DeclGen = struct {
                 try w.print(" a{d}", .{index});
             }
         }
+        if (is_var_args) {
+            if (param_len != 0) try w.writeAll(", ");
+            try w.writeAll("...");
+        }
         try w.writeByte(')');
     }
 
src/Module.zig
@@ -1183,7 +1183,8 @@ fn astgenAndSemaFn(
     const param_count = blk: {
         var count: usize = 0;
         var it = fn_proto.iterate(tree);
-        while (it.next()) |_| {
+        while (it.next()) |param| {
+            if (param.anytype_ellipsis3) |some| if (token_tags[some] == .ellipsis3) break;
             count += 1;
         }
         break :blk count;
@@ -1196,6 +1197,7 @@ fn astgenAndSemaFn(
     });
     const type_type_rl: astgen.ResultLoc = .{ .ty = type_type };
 
+    var is_var_args = false;
     {
         var param_type_i: usize = 0;
         var it = fn_proto.iterate(tree);
@@ -1208,12 +1210,10 @@ fn astgenAndSemaFn(
                         "TODO implement anytype parameter",
                         .{},
                     ),
-                    .ellipsis3 => return mod.failTok(
-                        &fn_type_scope.base,
-                        token,
-                        "TODO implement var args",
-                        .{},
-                    ),
+                    .ellipsis3 => {
+                        is_var_args = true;
+                        break;
+                    },
                     else => unreachable,
                 }
             }
@@ -1295,7 +1295,13 @@ fn astgenAndSemaFn(
         type_type_rl,
         fn_proto.ast.return_type,
     );
-    const fn_type_inst = if (fn_proto.ast.callconv_expr != 0) cc: {
+
+    const is_extern = if (fn_proto.extern_export_token) |maybe_export_token|
+        token_tags[maybe_export_token] == .keyword_extern
+    else
+        false;
+
+    const cc_inst = if (fn_proto.ast.callconv_expr != 0) cc: {
         // TODO instead of enum literal type, this needs to be the
         // std.builtin.CallingConvention enum. We need to implement importing other files
         // and enums in order to fix this.
@@ -1304,18 +1310,31 @@ fn astgenAndSemaFn(
             .ty = Type.initTag(.type),
             .val = Value.initTag(.enum_literal_type),
         });
-        const cc = try astgen.comptimeExpr(mod, &fn_type_scope.base, .{
+        break :cc try astgen.comptimeExpr(mod, &fn_type_scope.base, .{
             .ty = enum_lit_ty,
         }, fn_proto.ast.callconv_expr);
-        break :cc try astgen.addZirInstTag(mod, &fn_type_scope.base, fn_src, .fn_type_cc, .{
+    } else if (is_extern) cc: {
+        // note: https://github.com/ziglang/zig/issues/5269
+        const src = token_starts[fn_proto.extern_export_token.?];
+        break :cc try astgen.addZIRInst(mod, &fn_type_scope.base, src, zir.Inst.EnumLiteral, .{ .name = "C" }, .{});
+    } else null;
+
+    const fn_type_inst = if (cc_inst) |cc| fn_type: {
+        var fn_type = try astgen.addZirInstTag(mod, &fn_type_scope.base, fn_src, .fn_type_cc, .{
             .return_type = return_type_inst,
             .param_types = param_types,
             .cc = cc,
         });
-    } else try astgen.addZirInstTag(mod, &fn_type_scope.base, fn_src, .fn_type, .{
-        .return_type = return_type_inst,
-        .param_types = param_types,
-    });
+        if (is_var_args) fn_type.tag = .fn_type_cc_var_args;
+        break :fn_type fn_type;
+    } else fn_type: {
+        var fn_type = try astgen.addZirInstTag(mod, &fn_type_scope.base, fn_src, .fn_type, .{
+            .return_type = return_type_inst,
+            .param_types = param_types,
+        });
+        if (is_var_args) fn_type.tag = .fn_type_var_args;
+        break :fn_type fn_type;
+    };
 
     if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
         zir.dumpZir(mod.gpa, "fn_type", decl.name, fn_type_scope.instructions.items) catch {};
@@ -1348,7 +1367,12 @@ fn astgenAndSemaFn(
     const fn_type = try zir_sema.analyzeBodyValueAsType(mod, &block_scope, fn_type_inst, .{
         .instructions = fn_type_scope.instructions.items,
     });
+
     if (body_node == 0) {
+        if (!is_extern) {
+            return mod.failNode(&block_scope.base, fn_proto.ast.fn_token, "non-extern function has no body", .{});
+        }
+
         // Extern function.
         var type_changed = true;
         if (decl.typedValueManaged()) |tvm| {
@@ -1378,6 +1402,10 @@ fn astgenAndSemaFn(
         return type_changed;
     }
 
+    if (fn_type.fnIsVarArgs()) {
+        return mod.failNode(&block_scope.base, fn_proto.ast.fn_token, "non-extern function is variadic", .{});
+    }
+
     const new_func = try decl_arena.allocator.create(Fn);
     const fn_payload = try decl_arena.allocator.create(Value.Payload.Function);
 
@@ -3356,6 +3384,9 @@ pub fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Ty
 }
 
 pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) InnerError!*Inst {
+    if (dest_type.tag() == .var_args_param) {
+        return self.coerceVarArgParam(scope, inst);
+    }
     // If the types are the same, we can return the operand.
     if (dest_type.eql(inst.ty))
         return inst;
@@ -3508,6 +3539,15 @@ pub fn coerceNum(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) Inn
     return null;
 }
 
+pub fn coerceVarArgParam(mod: *Module, scope: *Scope, inst: *Inst) !*Inst {
+    switch (inst.ty.zigTypeTag()) {
+        .ComptimeInt, .ComptimeFloat => return mod.fail(scope, inst.src, "integer and float literals in var args function must be casted", .{}),
+        else => {},
+    }
+    // TODO implement more of this function.
+    return inst;
+}
+
 pub fn storePtr(self: *Module, scope: *Scope, src: usize, ptr: *Inst, uncasted_value: *Inst) !*Inst {
     if (ptr.ty.isConstPtr())
         return self.fail(scope, src, "cannot assign to constant", .{});
src/test.zig
@@ -871,6 +871,7 @@ pub const TestContext = struct {
                                 "-std=c89",
                                 "-pedantic",
                                 "-Werror",
+                                "-Wno-incompatible-library-redeclaration", // https://github.com/ziglang/zig/issues/875
                                 "-Wno-declaration-after-statement",
                                 "--",
                                 "-lc",
src/type.zig
@@ -97,6 +97,8 @@ pub const Type = extern union {
             .@"struct", .empty_struct => return .Struct,
             .@"enum" => return .Enum,
             .@"union" => return .Union,
+
+            .var_args_param => unreachable, // can be any type
         }
     }
 
@@ -258,6 +260,8 @@ pub const Type = extern union {
                     if (!a.fnParamType(i).eql(b.fnParamType(i)))
                         return false;
                 }
+                if (a.fnIsVarArgs() != b.fnIsVarArgs())
+                    return false;
                 return true;
             },
             .Optional => {
@@ -323,6 +327,7 @@ pub const Type = extern union {
                 while (i < params_len) : (i += 1) {
                     std.hash.autoHash(&hasher, self.fnParamType(i).hash());
                 }
+                std.hash.autoHash(&hasher, self.fnIsVarArgs());
             },
             .Optional => {
                 var buf: Payload.ElemType = undefined;
@@ -397,6 +402,7 @@ pub const Type = extern union {
             .@"anyframe",
             .inferred_alloc_const,
             .inferred_alloc_mut,
+            .var_args_param,
             => unreachable,
 
             .array_u8,
@@ -446,6 +452,7 @@ pub const Type = extern union {
                     .return_type = try payload.return_type.copy(allocator),
                     .param_types = param_types,
                     .cc = payload.cc,
+                    .is_var_args = payload.is_var_args,
                 });
             },
             .pointer => {
@@ -535,6 +542,7 @@ pub const Type = extern union {
                 .comptime_int,
                 .comptime_float,
                 .noreturn,
+                .var_args_param,
                 => return out_stream.writeAll(@tagName(t)),
 
                 .enum_literal => return out_stream.writeAll("@Type(.EnumLiteral)"),
@@ -558,6 +566,12 @@ pub const Type = extern union {
                         if (i != 0) try out_stream.writeAll(", ");
                         try param_type.format("", .{}, out_stream);
                     }
+                    if (payload.is_var_args) {
+                        if (payload.param_types.len != 0) {
+                            try out_stream.writeAll(", ");
+                        }
+                        try out_stream.writeAll("...");
+                    }
                     try out_stream.writeAll(") callconv(.");
                     try out_stream.writeAll(@tagName(payload.cc));
                     try out_stream.writeAll(")");
@@ -844,6 +858,7 @@ pub const Type = extern union {
 
             .inferred_alloc_const => unreachable,
             .inferred_alloc_mut => unreachable,
+            .var_args_param => unreachable,
         };
     }
 
@@ -969,6 +984,7 @@ pub const Type = extern union {
             .inferred_alloc_const,
             .inferred_alloc_mut,
             .@"opaque",
+            .var_args_param,
             => unreachable,
         };
     }
@@ -995,6 +1011,7 @@ pub const Type = extern union {
             .inferred_alloc_const => unreachable,
             .inferred_alloc_mut => unreachable,
             .@"opaque" => unreachable,
+            .var_args_param => unreachable,
 
             .u8,
             .i8,
@@ -1179,6 +1196,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => false,
 
             .single_const_pointer,
@@ -1256,6 +1274,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => unreachable,
 
             .const_slice,
@@ -1354,6 +1373,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => false,
 
             .const_slice,
@@ -1434,6 +1454,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => false,
 
             .single_const_pointer,
@@ -1523,6 +1544,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => false,
 
             .pointer => {
@@ -1607,6 +1629,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => false,
 
             .pointer => {
@@ -1733,6 +1756,7 @@ pub const Type = extern union {
             .@"struct" => unreachable,
             .@"union" => unreachable,
             .@"opaque" => unreachable,
+            .var_args_param => unreachable,
 
             .array => self.castTag(.array).?.data.elem_type,
             .array_sentinel => self.castTag(.array_sentinel).?.data.elem_type,
@@ -1862,6 +1886,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => unreachable,
 
             .array => self.castTag(.array).?.data.len,
@@ -1936,6 +1961,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => unreachable,
 
             .single_const_pointer,
@@ -2025,6 +2051,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => false,
 
             .int_signed,
@@ -2110,6 +2137,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => false,
 
             .int_unsigned,
@@ -2181,6 +2209,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => unreachable,
 
             .int_unsigned => .{
@@ -2280,6 +2309,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => false,
 
             .usize,
@@ -2400,6 +2430,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => unreachable,
         };
     }
@@ -2486,6 +2517,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => unreachable,
         }
     }
@@ -2571,6 +2603,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => unreachable,
         }
     }
@@ -2656,6 +2689,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => unreachable,
         };
     }
@@ -2738,6 +2772,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => unreachable,
         };
     }
@@ -2749,7 +2784,7 @@ pub const Type = extern union {
             .fn_void_no_args => false,
             .fn_naked_noreturn_no_args => false,
             .fn_ccc_void_no_args => false,
-            .function => false,
+            .function => self.castTag(.function).?.data.is_var_args,
 
             .f16,
             .f32,
@@ -2820,6 +2855,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => unreachable,
         };
     }
@@ -2902,6 +2938,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => false,
         };
     }
@@ -2962,6 +2999,7 @@ pub const Type = extern union {
             .error_set,
             .error_set_single,
             .@"opaque",
+            .var_args_param,
             => return null,
 
             .@"enum" => @panic("TODO onePossibleValue enum"),
@@ -3079,6 +3117,7 @@ pub const Type = extern union {
             .@"struct",
             .@"union",
             .@"opaque",
+            .var_args_param,
             => return false,
 
             .c_const_pointer,
@@ -3168,6 +3207,7 @@ pub const Type = extern union {
             .pointer,
             .inferred_alloc_const,
             .inferred_alloc_mut,
+            .var_args_param,
             => unreachable,
 
             .empty_struct => self.castTag(.empty_struct).?.data,
@@ -3285,6 +3325,9 @@ pub const Type = extern union {
         anyerror_void_error_union,
         @"anyframe",
         const_slice_u8,
+        /// This is a special type for variadic parameters of a function call.
+        /// Casts to it will validate that the type can be passed to a c calling convetion function.
+        var_args_param,
         /// This is a special value that tracks a set of types that have been stored
         /// to an inferred allocation. It does not support most of the normal type queries.
         /// However it does respond to `isConstPtr`, `ptrSize`, `zigTypeTag`, etc.
@@ -3373,6 +3416,7 @@ pub const Type = extern union {
                 .const_slice_u8,
                 .inferred_alloc_const,
                 .inferred_alloc_mut,
+                .var_args_param,
                 => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
 
                 .array_u8,
@@ -3479,6 +3523,7 @@ pub const Type = extern union {
                 param_types: []Type,
                 return_type: Type,
                 cc: std.builtin.CallingConvention,
+                is_var_args: bool,
             },
         };
 
src/zir.zig
@@ -178,8 +178,12 @@ pub const Inst = struct {
         @"fn",
         /// Returns a function type, assuming unspecified calling convention.
         fn_type,
+        /// Same as `fn_type` but the function is variadic.
+        fn_type_var_args,
         /// Returns a function type, with a calling convention instruction operand.
         fn_type_cc,
+        /// Same as `fn_type_cc` but the function is variadic.
+        fn_type_cc_var_args,
         /// @import(operand)
         import,
         /// Integer literal.
@@ -502,8 +506,8 @@ pub const Inst = struct {
                 .@"export" => Export,
                 .param_type => ParamType,
                 .primitive => Primitive,
-                .fn_type => FnType,
-                .fn_type_cc => FnTypeCc,
+                .fn_type, .fn_type_var_args => FnType,
+                .fn_type_cc, .fn_type_cc_var_args => FnTypeCc,
                 .elem_ptr, .elem_val => Elem,
                 .condbr => CondBr,
                 .ptr_type => PtrType,
@@ -579,7 +583,9 @@ pub const Inst = struct {
                 .field_val_named,
                 .@"fn",
                 .fn_type,
+                .fn_type_var_args,
                 .fn_type_cc,
+                .fn_type_cc_var_args,
                 .int,
                 .intcast,
                 .int_type,
src/zir_sema.zig
@@ -91,8 +91,10 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
         .@"fn" => return zirFn(mod, scope, old_inst.castTag(.@"fn").?),
         .@"export" => return zirExport(mod, scope, old_inst.castTag(.@"export").?),
         .primitive => return zirPrimitive(mod, scope, old_inst.castTag(.primitive).?),
-        .fn_type => return zirFnType(mod, scope, old_inst.castTag(.fn_type).?),
-        .fn_type_cc => return zirFnTypeCc(mod, scope, old_inst.castTag(.fn_type_cc).?),
+        .fn_type => return zirFnType(mod, scope, old_inst.castTag(.fn_type).?, false),
+        .fn_type_cc => return zirFnTypeCc(mod, scope, old_inst.castTag(.fn_type_cc).?, false),
+        .fn_type_var_args => return zirFnType(mod, scope, old_inst.castTag(.fn_type_var_args).?, true),
+        .fn_type_cc_var_args => return zirFnTypeCc(mod, scope, old_inst.castTag(.fn_type_cc_var_args).?, true),
         .intcast => return zirIntcast(mod, scope, old_inst.castTag(.intcast).?),
         .bitcast => return zirBitcast(mod, scope, old_inst.castTag(.bitcast).?),
         .floatcast => return zirFloatcast(mod, scope, old_inst.castTag(.floatcast).?),
@@ -522,9 +524,11 @@ fn zirParamType(mod: *Module, scope: *Scope, inst: *zir.Inst.ParamType) InnerErr
         },
     };
 
-    // TODO support C-style var args
     const param_count = fn_ty.fnParamLen();
     if (arg_index >= param_count) {
+        if (fn_ty.fnIsVarArgs()) {
+            return mod.constType(scope, inst.base.src, Type.initTag(.var_args_param));
+        }
         return mod.fail(scope, inst.base.src, "arg index {d} out of bounds; '{}' has {d} argument(s)", .{
             arg_index,
             fn_ty,
@@ -946,6 +950,7 @@ fn zirCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst {
     const call_params_len = inst.positionals.args.len;
     const fn_params_len = func.ty.fnParamLen();
     if (func.ty.fnIsVarArgs()) {
+        assert(cc == .C);
         if (call_params_len < fn_params_len) {
             // TODO add error note: declared here
             return mod.fail(
@@ -955,7 +960,6 @@ fn zirCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst {
                 .{ fn_params_len, call_params_len },
             );
         }
-        return mod.fail(scope, inst.base.src, "TODO implement support for calling var args functions", .{});
     } else if (fn_params_len != call_params_len) {
         // TODO add error note: declared here
         return mod.fail(
@@ -974,15 +978,10 @@ fn zirCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst {
     }
 
     // TODO handle function calls of generic functions
-
-    const fn_param_types = try mod.gpa.alloc(Type, fn_params_len);
-    defer mod.gpa.free(fn_param_types);
-    func.ty.fnParamTypes(fn_param_types);
-
-    const casted_args = try scope.arena().alloc(*Inst, fn_params_len);
+    const casted_args = try scope.arena().alloc(*Inst, call_params_len);
     for (inst.positionals.args) |src_arg, i| {
-        const uncasted_arg = try resolveInst(mod, scope, src_arg);
-        casted_args[i] = try mod.coerce(scope, fn_param_types[i], uncasted_arg);
+        // the args are already casted to the result of a param type instruction.
+        casted_args[i] = try resolveInst(mod, scope, src_arg);
     }
 
     const ret_type = func.ty.fnReturnType();
@@ -1503,7 +1502,7 @@ fn zirEnsureErrPayloadVoid(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp)
     return mod.constVoid(scope, unwrap.base.src);
 }
 
-fn zirFnType(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst {
+fn zirFnType(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnType, var_args: bool) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -1514,10 +1513,11 @@ fn zirFnType(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*
         fntype.positionals.param_types,
         fntype.positionals.return_type,
         .Unspecified,
+        var_args,
     );
 }
 
-fn zirFnTypeCc(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnTypeCc) InnerError!*Inst {
+fn zirFnTypeCc(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnTypeCc, var_args: bool) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -1534,6 +1534,7 @@ fn zirFnTypeCc(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnTypeCc) InnerErr
         fntype.positionals.param_types,
         fntype.positionals.return_type,
         cc,
+        var_args,
     );
 }
 
@@ -1544,11 +1545,12 @@ fn fnTypeCommon(
     zir_param_types: []*zir.Inst,
     zir_return_type: *zir.Inst,
     cc: std.builtin.CallingConvention,
+    var_args: bool,
 ) InnerError!*Inst {
     const return_type = try resolveType(mod, scope, zir_return_type);
 
     // Hot path for some common function types.
-    if (zir_param_types.len == 0) {
+    if (zir_param_types.len == 0 and !var_args) {
         if (return_type.zigTypeTag() == .NoReturn and cc == .Unspecified) {
             return mod.constType(scope, zir_inst.src, Type.initTag(.fn_noreturn_no_args));
         }
@@ -1581,6 +1583,7 @@ fn fnTypeCommon(
         .param_types = param_types,
         .return_type = return_type,
         .cc = cc,
+        .is_var_args = var_args,
     });
     return mod.constType(scope, zir_inst.src, fn_ty);
 }
test/stage2/cbe.zig
@@ -41,6 +41,19 @@ pub fn addCases(ctx: *TestContext) !void {
         , "yo!" ++ std.cstr.line_sep);
     }
 
+    {
+        var case = ctx.exeFromCompiledC("var args", .{});
+
+        case.addCompareOutput(
+            \\extern fn printf(format: [*:0]const u8, ...) c_int;
+            \\
+            \\export fn main() c_int {
+            \\    _ = printf("Hello, %s!\n", "world");
+            \\    return 0;
+            \\}
+        , "Hello, world!\n");
+    }
+
     {
         var case = ctx.exeFromCompiledC("x86_64-linux inline assembly", linux_x64);