AstGen: implement functions with inferred error sets

This commit also reclaims +2 ZIR instruction tags by moving the
following to `extended`:
 * func_var_args
 * func_extra
 * func_extra_var_args
The following ZIR instruction tag is added:
 * func_inferred
This commit is contained in:
Andrew Kelley 2021-04-19 15:03:41 -07:00
parent 22015c1b3b
commit 2083208f19
5 changed files with 211 additions and 179 deletions

View File

@ -737,3 +737,27 @@ fn errorSetDecl(
try mod.analyzeExport(&decl_scope.base, export_src, name, decl);
}
}
fn writeFuncExtra(
self: *Writer,
stream: anytype,
inst: Inst.Index,
var_args: bool,
) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
const extra = self.code.extraData(Inst.FuncExtra, inst_data.payload_index);
const param_types = self.code.refSlice(extra.end, extra.data.param_types_len);
const cc = extra.data.cc;
const body = self.code.extra[extra.end + param_types.len ..][0..extra.data.body_len];
return self.writeFuncCommon(
stream,
param_types,
extra.data.return_type,
var_args,
cc,
body,
src,
);
}

View File

@ -1375,9 +1375,7 @@ fn blockExprStmts(
.field_ptr_named,
.field_val_named,
.func,
.func_var_args,
.func_extra,
.func_extra_var_args,
.func_inferred,
.int,
.float,
.float128,
@ -2129,9 +2127,8 @@ fn fnDecl(
}
const maybe_bang = tree.firstToken(fn_proto.ast.return_type) - 1;
if (token_tags[maybe_bang] == .bang) {
return astgen.failTok(maybe_bang, "TODO implement inferred error sets", .{});
}
const is_inferred_error = token_tags[maybe_bang] == .bang;
const return_type_inst = try AstGen.expr(
&decl_gz,
&decl_gz.base,
@ -2153,31 +2150,24 @@ fn fnDecl(
const func_inst: Zir.Inst.Ref = if (body_node == 0) func: {
if (is_extern) {
return astgen.failNode(fn_proto.ast.fn_token, "non-extern function has no body", .{});
return astgen.failTok(fn_proto.ast.fn_token, "non-extern function has no body", .{});
}
if (cc != .none or lib_name != 0) {
const tag: Zir.Inst.Tag = if (is_var_args) .func_extra_var_args else .func_extra;
break :func try decl_gz.addFuncExtra(tag, .{
.src_node = fn_proto.ast.proto_node,
.ret_ty = return_type_inst,
.param_types = param_types,
.cc = cc,
.lib_name = lib_name,
.body = &[0]Zir.Inst.Index{},
});
if (is_inferred_error) {
return astgen.failTok(maybe_bang, "function prototype requires explicit error set", .{});
}
const tag: Zir.Inst.Tag = if (is_var_args) .func_var_args else .func;
break :func try decl_gz.addFunc(tag, .{
break :func try decl_gz.addFunc(.{
.src_node = fn_proto.ast.proto_node,
.ret_ty = return_type_inst,
.param_types = param_types,
.body = &[0]Zir.Inst.Index{},
.cc = cc,
.lib_name = lib_name,
.is_var_args = is_var_args,
.is_inferred_error = false,
});
} else func: {
if (is_var_args) {
return astgen.failNode(fn_proto.ast.fn_token, "non-extern function is variadic", .{});
return astgen.failTok(fn_proto.ast.fn_token, "non-extern function is variadic", .{});
}
var fn_gz: Scope.GenZir = .{
@ -2224,30 +2214,20 @@ fn fnDecl(
if (fn_gz.instructions.items.len == 0 or
!astgen.instructions.items(.tag)[fn_gz.instructions.items.len - 1].isNoReturn())
{
// astgen uses result location semantics to coerce return operands.
// Since we are adding the return instruction here, we must handle the coercion.
// We do this by using the `ret_coerce` instruction.
_ = try fn_gz.addUnTok(.ret_coerce, .void_value, tree.lastToken(body_node));
}
if (cc != .none or lib_name != 0) {
const tag: Zir.Inst.Tag = if (is_var_args) .func_extra_var_args else .func_extra;
break :func try decl_gz.addFuncExtra(tag, .{
.src_node = fn_proto.ast.proto_node,
.ret_ty = return_type_inst,
.param_types = param_types,
.cc = cc,
.lib_name = lib_name,
.body = fn_gz.instructions.items,
});
}
const tag: Zir.Inst.Tag = if (is_var_args) .func_var_args else .func;
break :func try decl_gz.addFunc(tag, .{
break :func try decl_gz.addFunc(.{
.src_node = fn_proto.ast.proto_node,
.ret_ty = return_type_inst,
.param_types = param_types,
.body = fn_gz.instructions.items,
.cc = cc,
.lib_name = lib_name,
.is_var_args = is_var_args,
.is_inferred_error = is_inferred_error,
});
};

View File

@ -1278,79 +1278,90 @@ pub const Scope = struct {
}
}
pub fn addFuncExtra(gz: *GenZir, tag: Zir.Inst.Tag, args: struct {
pub fn addFunc(gz: *GenZir, args: struct {
src_node: ast.Node.Index,
param_types: []const Zir.Inst.Ref,
body: []const Zir.Inst.Index,
ret_ty: Zir.Inst.Ref,
cc: Zir.Inst.Ref,
body: []const Zir.Inst.Index,
lib_name: u32,
is_var_args: bool,
is_inferred_error: bool,
}) !Zir.Inst.Ref {
assert(args.src_node != 0);
assert(args.ret_ty != .none);
assert(args.cc != .none);
const gpa = gz.astgen.gpa;
try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1);
try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len +
@typeInfo(Zir.Inst.FuncExtra).Struct.fields.len + args.param_types.len +
args.body.len);
const astgen = gz.astgen;
const gpa = astgen.gpa;
const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.FuncExtra{
.return_type = args.ret_ty,
.cc = args.cc,
.param_types_len = @intCast(u32, args.param_types.len),
.body_len = @intCast(u32, args.body.len),
.lib_name = args.lib_name,
});
gz.astgen.appendRefsAssumeCapacity(args.param_types);
gz.astgen.extra.appendSliceAssumeCapacity(args.body);
try gz.instructions.ensureUnusedCapacity(gpa, 1);
try astgen.instructions.ensureUnusedCapacity(gpa, 1);
const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len);
gz.astgen.instructions.appendAssumeCapacity(.{
.tag = tag,
.data = .{ .pl_node = .{
if (args.cc != .none or args.lib_name != 0 or args.is_var_args) {
try astgen.extra.ensureUnusedCapacity(
gpa,
@typeInfo(Zir.Inst.ExtendedFunc).Struct.fields.len +
args.param_types.len + args.body.len +
@boolToInt(args.lib_name != 0) +
@boolToInt(args.cc != .none),
);
const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.ExtendedFunc{
.src_node = gz.nodeIndexToRelative(args.src_node),
.payload_index = payload_index,
} },
});
gz.instructions.appendAssumeCapacity(new_index);
return gz.indexToRef(new_index);
}
.return_type = args.ret_ty,
.param_types_len = @intCast(u32, args.param_types.len),
.body_len = @intCast(u32, args.body.len),
});
if (args.cc != .none) {
astgen.extra.appendAssumeCapacity(@enumToInt(args.cc));
}
if (args.lib_name != 0) {
astgen.extra.appendAssumeCapacity(args.lib_name);
}
astgen.appendRefsAssumeCapacity(args.param_types);
astgen.extra.appendSliceAssumeCapacity(args.body);
pub fn addFunc(gz: *GenZir, tag: Zir.Inst.Tag, args: struct {
src_node: ast.Node.Index,
ret_ty: Zir.Inst.Ref,
param_types: []const Zir.Inst.Ref,
body: []const Zir.Inst.Index,
}) !Zir.Inst.Ref {
assert(args.src_node != 0);
assert(args.ret_ty != .none);
const gpa = gz.astgen.gpa;
try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1);
try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len +
@typeInfo(Zir.Inst.Func).Struct.fields.len + args.param_types.len +
args.body.len);
const new_index = @intCast(Zir.Inst.Index, astgen.instructions.len);
astgen.instructions.appendAssumeCapacity(.{
.tag = .extended,
.data = .{ .extended = .{
.opcode = .func,
.small = @bitCast(u16, Zir.Inst.ExtendedFunc.Small{
.is_var_args = args.is_var_args,
.is_inferred_error = args.is_inferred_error,
.has_lib_name = args.lib_name != 0,
.has_cc = args.cc != .none,
}),
.operand = payload_index,
} },
});
gz.instructions.appendAssumeCapacity(new_index);
return gz.indexToRef(new_index);
} else {
try gz.astgen.extra.ensureUnusedCapacity(
gpa,
@typeInfo(Zir.Inst.Func).Struct.fields.len +
args.param_types.len + args.body.len,
);
const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Func{
.return_type = args.ret_ty,
.param_types_len = @intCast(u32, args.param_types.len),
.body_len = @intCast(u32, args.body.len),
});
gz.astgen.appendRefsAssumeCapacity(args.param_types);
gz.astgen.extra.appendSliceAssumeCapacity(args.body);
const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Func{
.return_type = args.ret_ty,
.param_types_len = @intCast(u32, args.param_types.len),
.body_len = @intCast(u32, args.body.len),
});
gz.astgen.appendRefsAssumeCapacity(args.param_types);
gz.astgen.extra.appendSliceAssumeCapacity(args.body);
const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len);
gz.astgen.instructions.appendAssumeCapacity(.{
.tag = tag,
.data = .{ .pl_node = .{
.src_node = gz.nodeIndexToRelative(args.src_node),
.payload_index = payload_index,
} },
});
gz.instructions.appendAssumeCapacity(new_index);
return gz.indexToRef(new_index);
const tag: Zir.Inst.Tag = if (args.is_inferred_error) .func_inferred else .func;
const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len);
gz.astgen.instructions.appendAssumeCapacity(.{
.tag = tag,
.data = .{ .pl_node = .{
.src_node = gz.nodeIndexToRelative(args.src_node),
.payload_index = payload_index,
} },
});
gz.instructions.appendAssumeCapacity(new_index);
return gz.indexToRef(new_index);
}
}
pub fn addCall(

View File

@ -194,9 +194,7 @@ pub fn analyzeBody(
.field_val => try sema.zirFieldVal(block, inst),
.field_val_named => try sema.zirFieldValNamed(block, inst),
.func => try sema.zirFunc(block, inst, false),
.func_extra => try sema.zirFuncExtra(block, inst, false),
.func_extra_var_args => try sema.zirFuncExtra(block, inst, true),
.func_var_args => try sema.zirFunc(block, inst, true),
.func_inferred => try sema.zirFunc(block, inst, true),
.import => try sema.zirImport(block, inst),
.indexable_ptr_len => try sema.zirIndexablePtrLen(block, inst),
.int => try sema.zirInt(block, inst),
@ -2646,7 +2644,12 @@ fn zirEnsureErrPayloadVoid(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Inde
}
}
fn zirFunc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, var_args: bool) InnerError!*Inst {
fn zirFunc(
sema: *Sema,
block: *Scope.Block,
inst: Zir.Inst.Index,
inferred_error_set: bool,
) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
@ -2654,40 +2657,17 @@ fn zirFunc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, var_args: boo
const src = inst_data.src();
const extra = sema.code.extraData(Zir.Inst.Func, inst_data.payload_index);
const param_types = sema.code.refSlice(extra.end, extra.data.param_types_len);
const body = sema.code.extra[extra.end + param_types.len ..][0..extra.data.body_len];
return sema.funcCommon(
block,
inst_data.src_node,
param_types,
body,
extra.data.return_type,
.Unspecified,
var_args,
);
}
fn zirFuncExtra(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, var_args: bool) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
const cc_src: LazySrcLoc = .{ .node_offset_fn_type_cc = inst_data.src_node };
const extra = sema.code.extraData(Zir.Inst.FuncExtra, inst_data.payload_index);
const param_types = sema.code.refSlice(extra.end, extra.data.param_types_len);
const cc_tv = try sema.resolveInstConst(block, cc_src, extra.data.cc);
// TODO once we're capable of importing and analyzing decls from
// std.builtin, this needs to change
const cc_str = cc_tv.val.castTag(.enum_literal).?.data;
const cc = std.meta.stringToEnum(std.builtin.CallingConvention, cc_str) orelse
return sema.mod.fail(&block.base, cc_src, "Unknown calling convention {s}", .{cc_str});
return sema.funcCommon(
block,
inst_data.src_node,
param_types,
extra.data.return_type,
cc,
var_args,
false,
inferred_error_set,
);
}
@ -2696,9 +2676,11 @@ fn funcCommon(
block: *Scope.Block,
src_node_offset: i32,
zir_param_types: []const Zir.Inst.Ref,
body: []const Zir.Inst.Index,
zir_return_type: Zir.Inst.Ref,
cc: std.builtin.CallingConvention,
var_args: bool,
inferred_error_set: bool,
) InnerError!*Inst {
const src: LazySrcLoc = .{ .node_offset = src_node_offset };
const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = src_node_offset };
@ -5307,15 +5289,68 @@ fn zirExtended(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerErro
const extended = sema.code.instructions.items(.data)[inst].extended;
switch (extended.opcode) {
// zig fmt: off
.c_undef => return sema.zirCUndef( block, inst, extended),
.c_include => return sema.zirCInclude( block, inst, extended),
.c_define => return sema.zirCDefine( block, inst, extended),
.wasm_memory_size => return sema.zirWasmMemorySize( block, inst, extended),
.wasm_memory_grow => return sema.zirWasmMemoryGrow( block, inst, extended),
.func => return sema.zirFuncExtended( block, inst, extended),
.c_undef => return sema.zirCUndef( block, inst, extended),
.c_include => return sema.zirCInclude( block, inst, extended),
.c_define => return sema.zirCDefine( block, inst, extended),
.wasm_memory_size => return sema.zirWasmMemorySize(block, inst, extended),
.wasm_memory_grow => return sema.zirWasmMemoryGrow(block, inst, extended),
// zig fmt: on
}
}
fn zirFuncExtended(
sema: *Sema,
block: *Scope.Block,
inst: Zir.Inst.Index,
extended: Zir.Inst.Extended.InstData,
) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
const extra = sema.code.extraData(Zir.Inst.ExtendedFunc, extended.operand);
const src: LazySrcLoc = .{ .node_offset = extra.data.src_node };
const cc_src: LazySrcLoc = .{ .node_offset_fn_type_cc = extra.data.src_node };
const small = @bitCast(Zir.Inst.ExtendedFunc.Small, extended.small);
var extra_index: usize = extra.end;
if (small.has_lib_name) {
const lib_name = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
extra_index += 1;
return sema.mod.fail(&block.base, src, "TODO: implement Sema func lib name", .{});
}
const cc: std.builtin.CallingConvention = if (small.has_cc) blk: {
const cc_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
extra_index += 1;
const cc_tv = try sema.resolveInstConst(block, cc_src, cc_ref);
// TODO this needs to resolve other kinds of Value tags rather than
// assuming the tag will be .enum_field_index.
const cc_field_index = cc_tv.val.castTag(.enum_field_index).?.data;
// TODO should `@intToEnum` do this `@intCast` for you?
const cc = @intToEnum(
std.builtin.CallingConvention,
@intCast(@typeInfo(std.builtin.CallingConvention).Enum.tag_type, cc_field_index),
);
break :blk cc;
} else .Unspecified;
const param_types = sema.code.refSlice(extra_index, extra.data.param_types_len);
extra_index += 1;
const body = sema.code.extra[extra_index..][0..extra.data.body_len];
return sema.funcCommon(
block,
extra.data.src_node,
param_types,
body,
extra.data.return_type,
cc,
small.is_var_args,
small.is_inferred_error,
);
}
fn zirCUndef(
sema: *Sema,
block: *Scope.Block,

View File

@ -371,15 +371,8 @@ pub const Inst = struct {
/// the body_len is 0. Calling convention is auto.
/// Uses the `pl_node` union field. `payload_index` points to a `Func`.
func,
/// Same as `func` but the function is variadic.
func_var_args,
/// Same as `func` but with extra fields:
/// * calling convention
/// * extern lib name
/// Uses the `pl_node` union field. `payload_index` points to a `FuncExtra`.
func_extra,
/// Same as `func_extra` but the function is variadic.
func_extra_var_args,
/// Same as `func` but has an inferred error set.
func_inferred,
/// Implements the `@import` builtin.
/// Uses the `str_tok` field.
import,
@ -1010,9 +1003,7 @@ pub const Inst = struct {
.field_ptr_named,
.field_val_named,
.func,
.func_var_args,
.func_extra,
.func_extra_var_args,
.func_inferred,
.has_decl,
.int,
.float,
@ -1211,6 +1202,11 @@ pub const Inst = struct {
/// Rarer instructions are here; ones that do not fit in the 8-bit `Tag` enum.
/// `noreturn` instructions may not go here; they must be part of the main `Tag` enum.
pub const Extended = enum(u16) {
/// Represents a function declaration or function prototype, depending on
/// whether body_len is 0.
/// `operand` is payload index to `ExtendedFunc`.
/// `small` is `ExtendedFunc.Small`.
func,
/// `operand` is payload index to `UnNode`.
c_undef,
/// `operand` is payload index to `UnNode`.
@ -1774,15 +1770,23 @@ pub const Inst = struct {
};
/// Trailing:
/// 0. param_type: Ref // for each param_types_len
/// 1. body: Index // for each body_len
pub const FuncExtra = struct {
cc: Ref,
/// null terminated string index, or 0 to mean none.
lib_name: u32,
/// 0. lib_name: u32, // null terminated string index, if has_lib_name is set
/// 1. cc: Ref, // if has_cc is set
/// 2. param_type: Ref // for each param_types_len
/// 3. body: Index // for each body_len
pub const ExtendedFunc = struct {
src_node: i32,
return_type: Ref,
param_types_len: u32,
body_len: u32,
pub const Small = packed struct {
is_var_args: bool,
is_inferred_error: bool,
has_lib_name: bool,
has_cc: bool,
_: u12 = undefined,
};
};
/// Trailing:
@ -2459,9 +2463,7 @@ const Writer = struct {
=> try self.writeStrTok(stream, inst),
.func => try self.writeFunc(stream, inst, false),
.func_extra => try self.writeFuncExtra(stream, inst, false),
.func_var_args => try self.writeFunc(stream, inst, true),
.func_extra_var_args => try self.writeFuncExtra(stream, inst, true),
.func_inferred => try self.writeFunc(stream, inst, true),
.@"unreachable" => try self.writeUnreachable(stream, inst),
@ -3099,7 +3101,7 @@ const Writer = struct {
self: *Writer,
stream: anytype,
inst: Inst.Index,
var_args: bool,
inferred_error_set: bool,
) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
@ -3110,36 +3112,14 @@ const Writer = struct {
stream,
param_types,
extra.data.return_type,
var_args,
inferred_error_set,
false,
.none,
body,
src,
);
}
fn writeFuncExtra(
self: *Writer,
stream: anytype,
inst: Inst.Index,
var_args: bool,
) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
const extra = self.code.extraData(Inst.FuncExtra, inst_data.payload_index);
const param_types = self.code.refSlice(extra.end, extra.data.param_types_len);
const cc = extra.data.cc;
const body = self.code.extra[extra.end + param_types.len ..][0..extra.data.body_len];
return self.writeFuncCommon(
stream,
param_types,
extra.data.return_type,
var_args,
cc,
body,
src,
);
}
fn writeBoolBr(self: *Writer, stream: anytype, inst: Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].bool_br;
const extra = self.code.extraData(Inst.Block, inst_data.payload_index);
@ -3184,6 +3164,7 @@ const Writer = struct {
stream: anytype,
param_types: []const Inst.Ref,
ret_ty: Inst.Ref,
inferred_error_set: bool,
var_args: bool,
cc: Inst.Ref,
body: []const Inst.Index,
@ -3197,7 +3178,8 @@ const Writer = struct {
try stream.writeAll("], ");
try self.writeInstRef(stream, ret_ty);
try self.writeOptionalInstRef(stream, ", cc=", cc);
try self.writeFlag(stream, ", var_args", var_args);
try self.writeFlag(stream, ", vargs", var_args);
try self.writeFlag(stream, ", inferror", inferred_error_set);
try stream.writeAll(", {\n");
self.indent += 2;