stage2: implement var args

This commit is contained in:
Veikka Tuominen 2021-01-29 12:19:10 +02:00
parent 17e6e09285
commit 8c6e7fb2c7
No known key found for this signature in database
GPG Key ID: 59AEB8936E16A6AC
7 changed files with 147 additions and 34 deletions

View File

@ -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", .{});

View File

@ -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(')');
}

View File

@ -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",

View File

@ -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,
},
};

View File

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

View File

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

View File

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