commit ea7bdeb67d474526732b117992971603e4065f98 (tree)
parent 9fd3aeb8088cd9a3b0744d5f508ca256a2bbf19f
Author: Andrew Kelley <andrew@ziglang.org>
Date: Thu, 5 Aug 2021 23:32:42 -0700
Merge pull request #9517 from ziglang/generic-functions
stage2 generic functions
Diffstat:
18 files changed, 1823 insertions(+), 926 deletions(-)
diff --git a/lib/std/hash_map.zig b/lib/std/hash_map.zig
@@ -563,7 +563,7 @@ pub fn HashMap(
return self.unmanaged.getPtrContext(key, self.ctx);
}
pub fn getPtrAdapted(self: Self, key: anytype, ctx: anytype) ?*V {
- return self.unmanaged.getPtrAdapted(key, self.ctx);
+ return self.unmanaged.getPtrAdapted(key, ctx);
}
/// Finds the key and value associated with a key in the map
diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig
@@ -2198,6 +2198,9 @@ pub const full = struct {
.type_expr = param_type,
};
}
+ if (token_tags[it.tok_i] == .comma) {
+ it.tok_i += 1;
+ }
if (token_tags[it.tok_i] == .r_paren) {
return null;
}
diff --git a/src/AstGen.zig b/src/AstGen.zig
@@ -42,7 +42,7 @@ const InnerError = error{ OutOfMemory, AnalysisFail };
fn addExtra(astgen: *AstGen, extra: anytype) Allocator.Error!u32 {
const fields = std.meta.fields(@TypeOf(extra));
- try astgen.extra.ensureCapacity(astgen.gpa, astgen.extra.items.len + fields.len);
+ try astgen.extra.ensureUnusedCapacity(astgen.gpa, fields.len);
return addExtraAssumeCapacity(astgen, extra);
}
@@ -195,6 +195,9 @@ pub const ResultLoc = union(enum) {
none_or_ref,
/// The expression will be coerced into this type, but it will be evaluated as an rvalue.
ty: Zir.Inst.Ref,
+ /// Same as `ty` but it is guaranteed that Sema will additionall perform the coercion,
+ /// so no `as` instruction needs to be emitted.
+ coerced_ty: Zir.Inst.Ref,
/// The expression must store its result into this typed pointer. The result instruction
/// from the expression must be ignored.
ptr: Zir.Inst.Ref,
@@ -225,7 +228,7 @@ pub const ResultLoc = union(enum) {
fn strategy(rl: ResultLoc, block_scope: *GenZir) Strategy {
switch (rl) {
// In this branch there will not be any store_to_block_ptr instructions.
- .discard, .none, .none_or_ref, .ty, .ref => return .{
+ .discard, .none, .none_or_ref, .ty, .coerced_ty, .ref => return .{
.tag = .break_operand,
.elide_store_to_block_ptr_instructions = false,
},
@@ -259,13 +262,15 @@ pub const ResultLoc = union(enum) {
pub const align_rl: ResultLoc = .{ .ty = .u16_type };
pub const bool_rl: ResultLoc = .{ .ty = .bool_type };
+pub const type_rl: ResultLoc = .{ .ty = .type_type };
+pub const coerced_type_rl: ResultLoc = .{ .coerced_ty = .type_type };
fn typeExpr(gz: *GenZir, scope: *Scope, type_node: ast.Node.Index) InnerError!Zir.Inst.Ref {
const prev_force_comptime = gz.force_comptime;
gz.force_comptime = true;
defer gz.force_comptime = prev_force_comptime;
- return expr(gz, scope, .{ .ty = .type_type }, type_node);
+ return expr(gz, scope, coerced_type_rl, type_node);
}
/// Same as `expr` but fails with a compile error if the result type is `noreturn`.
@@ -1046,71 +1051,55 @@ fn fnProtoExpr(
};
assert(!is_extern);
- // The AST params array does not contain anytype and ... parameters.
- // We must iterate to count how many param types to allocate.
- const param_count = blk: {
- var count: usize = 0;
- var it = fn_proto.iterate(tree.*);
- while (it.next()) |param| {
- if (param.anytype_ellipsis3) |token| switch (token_tags[token]) {
- .ellipsis3 => break,
- .keyword_anytype => {},
- else => unreachable,
- };
- count += 1;
- }
- break :blk count;
- };
- const param_types = try gpa.alloc(Zir.Inst.Ref, param_count);
- defer gpa.free(param_types);
-
- const bits_per_param = 1;
- const params_per_u32 = 32 / bits_per_param;
- // We only need this if there are greater than params_per_u32 fields.
- var bit_bag = ArrayListUnmanaged(u32){};
- defer bit_bag.deinit(gpa);
- var cur_bit_bag: u32 = 0;
- var is_var_args = false;
- {
+ const is_var_args = is_var_args: {
var param_type_i: usize = 0;
var it = fn_proto.iterate(tree.*);
while (it.next()) |param| : (param_type_i += 1) {
- if (param_type_i % params_per_u32 == 0 and param_type_i != 0) {
- try bit_bag.append(gpa, cur_bit_bag);
- cur_bit_bag = 0;
- }
const is_comptime = if (param.comptime_noalias) |token|
token_tags[token] == .keyword_comptime
else
false;
- cur_bit_bag = (cur_bit_bag >> bits_per_param) |
- (@as(u32, @boolToInt(is_comptime)) << 31);
- if (param.anytype_ellipsis3) |token| {
+ const is_anytype = if (param.anytype_ellipsis3) |token| blk: {
switch (token_tags[token]) {
- .keyword_anytype => {
- param_types[param_type_i] = .none;
- continue;
- },
- .ellipsis3 => {
- is_var_args = true;
- break;
- },
+ .keyword_anytype => break :blk true,
+ .ellipsis3 => break :is_var_args true,
else => unreachable,
}
- }
- const param_type_node = param.type_expr;
- assert(param_type_node != 0);
- param_types[param_type_i] =
- try expr(gz, scope, .{ .ty = .type_type }, param_type_node);
- }
- assert(param_type_i == param_count);
+ } else false;
+
+ const param_name: u32 = if (param.name_token) |name_token| blk: {
+ if (mem.eql(u8, "_", tree.tokenSlice(name_token)))
+ break :blk 0;
- const empty_slot_count = params_per_u32 - (param_type_i % params_per_u32);
- if (empty_slot_count < params_per_u32) {
- cur_bit_bag >>= @intCast(u5, empty_slot_count * bits_per_param);
+ break :blk try astgen.identAsString(name_token);
+ } else 0;
+
+ if (is_anytype) {
+ const name_token = param.name_token orelse param.anytype_ellipsis3.?;
+
+ const tag: Zir.Inst.Tag = if (is_comptime)
+ .param_anytype_comptime
+ else
+ .param_anytype;
+ _ = try gz.addStrTok(tag, param_name, name_token);
+ } else {
+ const param_type_node = param.type_expr;
+ assert(param_type_node != 0);
+ var param_gz = gz.makeSubBlock(scope);
+ defer param_gz.instructions.deinit(gpa);
+ const param_type = try expr(¶m_gz, scope, coerced_type_rl, param_type_node);
+ const param_inst_expected = @intCast(u32, astgen.instructions.len + 1);
+ _ = try param_gz.addBreak(.break_inline, param_inst_expected, param_type);
+ const main_tokens = tree.nodes.items(.main_token);
+ const name_token = param.name_token orelse main_tokens[param_type_node];
+ const tag: Zir.Inst.Tag = if (is_comptime) .param_comptime else .param;
+ const param_inst = try gz.addParam(tag, name_token, param_name, param_gz.instructions.items);
+ assert(param_inst_expected == param_inst);
+ }
}
- }
+ break :is_var_args false;
+ };
const align_inst: Zir.Inst.Ref = if (fn_proto.ast.align_expr == 0) .none else inst: {
break :inst try expr(gz, scope, align_rl, fn_proto.ast.align_expr);
@@ -1124,15 +1113,13 @@ fn fnProtoExpr(
if (is_inferred_error) {
return astgen.failTok(maybe_bang, "function prototype may not have inferred error set", .{});
}
- const return_type_inst = try AstGen.expr(
- gz,
- scope,
- .{ .ty = .type_type },
- fn_proto.ast.return_type,
- );
+ var ret_gz = gz.makeSubBlock(scope);
+ defer ret_gz.instructions.deinit(gpa);
+ const ret_ty = try expr(&ret_gz, scope, coerced_type_rl, fn_proto.ast.return_type);
+ const ret_br = try ret_gz.addBreak(.break_inline, 0, ret_ty);
const cc: Zir.Inst.Ref = if (fn_proto.ast.callconv_expr != 0)
- try AstGen.expr(
+ try expr(
gz,
scope,
.{ .ty = .calling_convention_type },
@@ -1143,8 +1130,9 @@ fn fnProtoExpr(
const result = try gz.addFunc(.{
.src_node = fn_proto.ast.proto_node,
- .ret_ty = return_type_inst,
- .param_types = param_types,
+ .param_block = 0,
+ .ret_ty = ret_gz.instructions.items,
+ .ret_br = ret_br,
.body = &[0]Zir.Inst.Index{},
.cc = cc,
.align_inst = align_inst,
@@ -1153,8 +1141,6 @@ fn fnProtoExpr(
.is_inferred_error = false,
.is_test = false,
.is_extern = false,
- .cur_bit_bag = cur_bit_bag,
- .bit_bag = bit_bag.items,
});
return rvalue(gz, rl, result, fn_proto.ast.proto_node);
}
@@ -1239,7 +1225,7 @@ fn arrayInitExpr(
return arrayInitExprRlNone(gz, scope, node, array_init.ast.elements, .array_init_anon);
}
},
- .ty => |ty_inst| {
+ .ty, .coerced_ty => |ty_inst| {
if (types.array != .none) {
const result = try arrayInitExprRlTy(gz, scope, node, array_init.ast.elements, types.elem, .array_init);
return rvalue(gz, rl, result, node);
@@ -1408,7 +1394,7 @@ fn structInitExpr(
return structInitExprRlNone(gz, scope, node, struct_init, .struct_init_anon);
}
},
- .ty => |ty_inst| {
+ .ty, .coerced_ty => |ty_inst| {
if (struct_init.ast.type_expr == 0) {
return structInitExprRlTy(gz, scope, node, struct_init, ty_inst, .struct_init);
}
@@ -1447,8 +1433,8 @@ fn structInitExprRlNone(
const init_inst = try gz.addPlNode(tag, node, Zir.Inst.StructInitAnon{
.fields_len = @intCast(u32, fields_list.len),
});
- try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len +
- fields_list.len * @typeInfo(Zir.Inst.StructInitAnon.Item).Struct.fields.len);
+ try astgen.extra.ensureUnusedCapacity(gpa, fields_list.len *
+ @typeInfo(Zir.Inst.StructInitAnon.Item).Struct.fields.len);
for (fields_list) |field| {
_ = gz.astgen.addExtraAssumeCapacity(field);
}
@@ -1520,8 +1506,8 @@ fn structInitExprRlTy(
const init_inst = try gz.addPlNode(tag, node, Zir.Inst.StructInit{
.fields_len = @intCast(u32, fields_list.len),
});
- try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len +
- fields_list.len * @typeInfo(Zir.Inst.StructInit.Item).Struct.fields.len);
+ try astgen.extra.ensureUnusedCapacity(gpa, fields_list.len *
+ @typeInfo(Zir.Inst.StructInit.Item).Struct.fields.len);
for (fields_list) |field| {
_ = gz.astgen.addExtraAssumeCapacity(field);
}
@@ -1918,7 +1904,10 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: ast.Node.Index) Inner
// ZIR instructions that might be a type other than `noreturn` or `void`.
.add,
.addwrap,
- .arg,
+ .param,
+ .param_comptime,
+ .param_anytype,
+ .param_anytype_comptime,
.alloc,
.alloc_mut,
.alloc_comptime,
@@ -2488,7 +2477,7 @@ fn varDecl(
// Move the init_scope instructions into the parent scope, swapping
// store_to_block_ptr for store_to_inferred_ptr.
const expected_len = parent_zir.items.len + init_scope.instructions.items.len;
- try parent_zir.ensureCapacity(gpa, expected_len);
+ try parent_zir.ensureTotalCapacity(gpa, expected_len);
for (init_scope.instructions.items) |src_inst| {
if (zir_tags[src_inst] == .store_to_block_ptr) {
if (zir_datas[src_inst].bin.lhs == init_scope.rl_ptr) {
@@ -2634,7 +2623,7 @@ fn assignOp(
const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs);
const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node);
const lhs_type = try gz.addUnNode(.typeof, lhs, infix_node);
- const rhs = try expr(gz, scope, .{ .ty = lhs_type }, node_datas[infix_node].rhs);
+ const rhs = try expr(gz, scope, .{ .coerced_ty = lhs_type }, node_datas[infix_node].rhs);
const result = try gz.addPlNode(op_inst_tag, infix_node, Zir.Inst.Bin{
.lhs = lhs,
@@ -2750,10 +2739,10 @@ fn ptrType(
}
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.PtrType).Struct.fields.len + trailing_count);
+ try gz.instructions.ensureUnusedCapacity(gpa, 1);
+ try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1);
+ try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.PtrType).Struct.fields.len +
+ trailing_count);
const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.PtrType{ .elem_type = elem_type });
if (sentinel_ref != .none) {
@@ -2899,6 +2888,16 @@ fn fnDecl(
};
defer decl_gz.instructions.deinit(gpa);
+ var fn_gz: GenZir = .{
+ .force_comptime = false,
+ .in_defer = false,
+ .decl_node_index = fn_proto.ast.proto_node,
+ .decl_line = decl_gz.decl_line,
+ .parent = &decl_gz.base,
+ .astgen = astgen,
+ };
+ defer fn_gz.instructions.deinit(gpa);
+
// TODO: support noinline
const is_pub = fn_proto.visib_token != null;
const is_export = blk: {
@@ -2913,80 +2912,82 @@ fn fnDecl(
const maybe_inline_token = fn_proto.extern_export_inline_token orelse break :blk false;
break :blk token_tags[maybe_inline_token] == .keyword_inline;
};
- const align_inst: Zir.Inst.Ref = if (fn_proto.ast.align_expr == 0) .none else inst: {
- break :inst try expr(&decl_gz, &decl_gz.base, align_rl, fn_proto.ast.align_expr);
- };
- const section_inst: Zir.Inst.Ref = if (fn_proto.ast.section_expr == 0) .none else inst: {
- break :inst try comptimeExpr(&decl_gz, &decl_gz.base, .{ .ty = .const_slice_u8_type }, fn_proto.ast.section_expr);
- };
-
- try wip_decls.next(gpa, is_pub, is_export, align_inst != .none, section_inst != .none);
-
- // The AST params array does not contain anytype and ... parameters.
- // We must iterate to count how many param types to allocate.
- const param_count = blk: {
- var count: usize = 0;
- var it = fn_proto.iterate(tree.*);
- while (it.next()) |param| {
- if (param.anytype_ellipsis3) |token| switch (token_tags[token]) {
- .ellipsis3 => break,
- .keyword_anytype => {},
- else => unreachable,
- };
- count += 1;
- }
- break :blk count;
- };
- const param_types = try gpa.alloc(Zir.Inst.Ref, param_count);
- defer gpa.free(param_types);
+ try wip_decls.next(gpa, is_pub, is_export, fn_proto.ast.align_expr != 0, fn_proto.ast.section_expr != 0);
- const bits_per_param = 1;
- const params_per_u32 = 32 / bits_per_param;
- // We only need this if there are greater than params_per_u32 fields.
- var bit_bag = ArrayListUnmanaged(u32){};
- defer bit_bag.deinit(gpa);
- var cur_bit_bag: u32 = 0;
- var is_var_args = false;
- {
+ var params_scope = &fn_gz.base;
+ const is_var_args = is_var_args: {
var param_type_i: usize = 0;
var it = fn_proto.iterate(tree.*);
while (it.next()) |param| : (param_type_i += 1) {
- if (param_type_i % params_per_u32 == 0 and param_type_i != 0) {
- try bit_bag.append(gpa, cur_bit_bag);
- cur_bit_bag = 0;
- }
const is_comptime = if (param.comptime_noalias) |token|
token_tags[token] == .keyword_comptime
else
false;
- cur_bit_bag = (cur_bit_bag >> bits_per_param) |
- (@as(u32, @boolToInt(is_comptime)) << 31);
- if (param.anytype_ellipsis3) |token| {
+ const is_anytype = if (param.anytype_ellipsis3) |token| blk: {
switch (token_tags[token]) {
- .keyword_anytype => {
- param_types[param_type_i] = .none;
- continue;
- },
- .ellipsis3 => {
- is_var_args = true;
- break;
- },
+ .keyword_anytype => break :blk true,
+ .ellipsis3 => break :is_var_args true,
else => unreachable,
}
- }
- const param_type_node = param.type_expr;
- assert(param_type_node != 0);
- param_types[param_type_i] =
- try expr(&decl_gz, &decl_gz.base, .{ .ty = .type_type }, param_type_node);
- }
- assert(param_type_i == param_count);
+ } else false;
+
+ const param_name: u32 = if (param.name_token) |name_token| blk: {
+ if (mem.eql(u8, "_", tree.tokenSlice(name_token)))
+ break :blk 0;
+
+ const param_name = try astgen.identAsString(name_token);
+ if (!is_extern) {
+ try astgen.detectLocalShadowing(params_scope, param_name, name_token);
+ }
+ break :blk param_name;
+ } else if (!is_extern) {
+ if (param.anytype_ellipsis3) |tok| {
+ return astgen.failTok(tok, "missing parameter name", .{});
+ } else {
+ return astgen.failNode(param.type_expr, "missing parameter name", .{});
+ }
+ } else 0;
- const empty_slot_count = params_per_u32 - (param_type_i % params_per_u32);
- if (empty_slot_count < params_per_u32) {
- cur_bit_bag >>= @intCast(u5, empty_slot_count * bits_per_param);
+ const param_inst = if (is_anytype) param: {
+ const name_token = param.name_token orelse param.anytype_ellipsis3.?;
+ const tag: Zir.Inst.Tag = if (is_comptime)
+ .param_anytype_comptime
+ else
+ .param_anytype;
+ break :param try decl_gz.addStrTok(tag, param_name, name_token);
+ } else param: {
+ const param_type_node = param.type_expr;
+ assert(param_type_node != 0);
+ var param_gz = decl_gz.makeSubBlock(scope);
+ defer param_gz.instructions.deinit(gpa);
+ const param_type = try expr(¶m_gz, params_scope, coerced_type_rl, param_type_node);
+ const param_inst_expected = @intCast(u32, astgen.instructions.len + 1);
+ _ = try param_gz.addBreak(.break_inline, param_inst_expected, param_type);
+
+ const main_tokens = tree.nodes.items(.main_token);
+ const name_token = param.name_token orelse main_tokens[param_type_node];
+ const tag: Zir.Inst.Tag = if (is_comptime) .param_comptime else .param;
+ const param_inst = try decl_gz.addParam(tag, name_token, param_name, param_gz.instructions.items);
+ assert(param_inst_expected == param_inst);
+ break :param indexToRef(param_inst);
+ };
+
+ if (param_name == 0) continue;
+
+ const sub_scope = try astgen.arena.create(Scope.LocalVal);
+ sub_scope.* = .{
+ .parent = params_scope,
+ .gen_zir = &decl_gz,
+ .name = param_name,
+ .inst = param_inst,
+ .token_src = param.name_token.?,
+ .id_cat = .@"function parameter",
+ };
+ params_scope = &sub_scope.base;
}
- }
+ break :is_var_args false;
+ };
const lib_name: u32 = if (fn_proto.lib_name) |lib_name_token| blk: {
const lib_name_str = try astgen.strLitAsString(lib_name_token);
@@ -2996,12 +2997,17 @@ fn fnDecl(
const maybe_bang = tree.firstToken(fn_proto.ast.return_type) - 1;
const is_inferred_error = token_tags[maybe_bang] == .bang;
- const return_type_inst = try AstGen.expr(
- &decl_gz,
- &decl_gz.base,
- .{ .ty = .type_type },
- fn_proto.ast.return_type,
- );
+ const align_inst: Zir.Inst.Ref = if (fn_proto.ast.align_expr == 0) .none else inst: {
+ break :inst try expr(&decl_gz, params_scope, align_rl, fn_proto.ast.align_expr);
+ };
+ const section_inst: Zir.Inst.Ref = if (fn_proto.ast.section_expr == 0) .none else inst: {
+ break :inst try comptimeExpr(&decl_gz, params_scope, .{ .ty = .const_slice_u8_type }, fn_proto.ast.section_expr);
+ };
+
+ var ret_gz = decl_gz.makeSubBlock(params_scope);
+ defer ret_gz.instructions.deinit(gpa);
+ const ret_ty = try expr(&ret_gz, params_scope, coerced_type_rl, fn_proto.ast.return_type);
+ const ret_br = try ret_gz.addBreak(.break_inline, 0, ret_ty);
const cc: Zir.Inst.Ref = blk: {
if (fn_proto.ast.callconv_expr != 0) {
@@ -3012,9 +3018,9 @@ fn fnDecl(
.{},
);
}
- break :blk try AstGen.expr(
+ break :blk try expr(
&decl_gz,
- &decl_gz.base,
+ params_scope,
.{ .ty = .calling_convention_type },
fn_proto.ast.callconv_expr,
);
@@ -3037,8 +3043,9 @@ fn fnDecl(
}
break :func try decl_gz.addFunc(.{
.src_node = decl_node,
- .ret_ty = return_type_inst,
- .param_types = param_types,
+ .ret_ty = ret_gz.instructions.items,
+ .ret_br = ret_br,
+ .param_block = block_inst,
.body = &[0]Zir.Inst.Index{},
.cc = cc,
.align_inst = .none, // passed in the per-decl data
@@ -3047,75 +3054,18 @@ fn fnDecl(
.is_inferred_error = false,
.is_test = false,
.is_extern = true,
- .cur_bit_bag = cur_bit_bag,
- .bit_bag = bit_bag.items,
});
} else func: {
if (is_var_args) {
return astgen.failTok(fn_proto.ast.fn_token, "non-extern function is variadic", .{});
}
- var fn_gz: GenZir = .{
- .force_comptime = false,
- .in_defer = false,
- .decl_node_index = fn_proto.ast.proto_node,
- .decl_line = decl_gz.decl_line,
- .parent = &decl_gz.base,
- .astgen = astgen,
- };
- defer fn_gz.instructions.deinit(gpa);
-
const prev_fn_block = astgen.fn_block;
astgen.fn_block = &fn_gz;
defer astgen.fn_block = prev_fn_block;
- // Iterate over the parameters. We put the param names as the first N
- // items inside `extra` so that debug info later can refer to the parameter names
- // even while the respective source code is unloaded.
- try astgen.extra.ensureUnusedCapacity(gpa, param_count);
-
- {
- var params_scope = &fn_gz.base;
- var i: usize = 0;
- var it = fn_proto.iterate(tree.*);
- while (it.next()) |param| : (i += 1) {
- const name_token = param.name_token orelse {
- if (param.anytype_ellipsis3) |tok| {
- return astgen.failTok(tok, "missing parameter name", .{});
- } else {
- return astgen.failNode(param.type_expr, "missing parameter name", .{});
- }
- };
- if (param.type_expr != 0)
- _ = try typeExpr(&fn_gz, params_scope, param.type_expr);
- if (mem.eql(u8, "_", tree.tokenSlice(name_token)))
- continue;
- const param_name = try astgen.identAsString(name_token);
- // Create an arg instruction. This is needed to emit a semantic analysis
- // error for shadowing decls.
- try astgen.detectLocalShadowing(params_scope, param_name, name_token);
- const arg_inst = try fn_gz.addStrTok(.arg, param_name, name_token);
- const sub_scope = try astgen.arena.create(Scope.LocalVal);
- sub_scope.* = .{
- .parent = params_scope,
- .gen_zir = &fn_gz,
- .name = param_name,
- .inst = arg_inst,
- .token_src = name_token,
- .id_cat = .@"function parameter",
- };
- params_scope = &sub_scope.base;
-
- // Additionally put the param name into `string_bytes` and reference it with
- // `extra` so that we have access to the data in codegen, for debug info.
- const str_index = try astgen.identAsString(name_token);
- try astgen.extra.append(astgen.gpa, str_index);
- }
- _ = try typeExpr(&fn_gz, params_scope, fn_proto.ast.return_type);
-
- _ = try expr(&fn_gz, params_scope, .none, body_node);
- try checkUsed(gz, &fn_gz.base, params_scope);
- }
+ _ = try expr(&fn_gz, params_scope, .none, body_node);
+ try checkUsed(gz, &fn_gz.base, params_scope);
const need_implicit_ret = blk: {
if (fn_gz.instructions.items.len == 0)
@@ -3132,8 +3082,9 @@ fn fnDecl(
break :func try decl_gz.addFunc(.{
.src_node = decl_node,
- .ret_ty = return_type_inst,
- .param_types = param_types,
+ .param_block = block_inst,
+ .ret_ty = ret_gz.instructions.items,
+ .ret_br = ret_br,
.body = fn_gz.instructions.items,
.cc = cc,
.align_inst = .none, // passed in the per-decl data
@@ -3142,8 +3093,6 @@ fn fnDecl(
.is_inferred_error = is_inferred_error,
.is_test = false,
.is_extern = false,
- .cur_bit_bag = cur_bit_bag,
- .bit_bag = bit_bag.items,
});
};
@@ -3479,8 +3428,9 @@ fn testDecl(
const func_inst = try decl_block.addFunc(.{
.src_node = node,
- .ret_ty = .void_type,
- .param_types = &[0]Zir.Inst.Ref{},
+ .param_block = block_inst,
+ .ret_ty = &.{},
+ .ret_br = 0,
.body = fn_block.instructions.items,
.cc = .none,
.align_inst = .none,
@@ -3489,8 +3439,6 @@ fn testDecl(
.is_inferred_error = true,
.is_test = true,
.is_extern = false,
- .cur_bit_bag = 0,
- .bit_bag = &.{},
});
_ = try decl_block.addBreak(.break_inline, block_inst, func_inst);
@@ -4238,7 +4186,7 @@ fn containerDecl(
var fields_data = ArrayListUnmanaged(u32){};
defer fields_data.deinit(gpa);
- try fields_data.ensureCapacity(gpa, counts.total_fields + counts.values);
+ try fields_data.ensureTotalCapacity(gpa, counts.total_fields + counts.values);
// We only need this if there are greater than 32 fields.
var bit_bag = ArrayListUnmanaged(u32){};
@@ -5184,8 +5132,7 @@ fn setCondBrPayload(
) !void {
const astgen = then_scope.astgen;
- try astgen.extra.ensureCapacity(astgen.gpa, astgen.extra.items.len +
- @typeInfo(Zir.Inst.CondBr).Struct.fields.len +
+ try astgen.extra.ensureUnusedCapacity(astgen.gpa, @typeInfo(Zir.Inst.CondBr).Struct.fields.len +
then_scope.instructions.items.len + else_scope.instructions.items.len);
const zir_datas = astgen.instructions.items(.data);
@@ -5476,7 +5423,7 @@ fn forExpr(
const tree = astgen.tree;
const token_tags = tree.tokens.items(.tag);
- const array_ptr = try expr(parent_gz, scope, .ref, for_full.ast.cond_expr);
+ const array_ptr = try expr(parent_gz, scope, .none_or_ref, for_full.ast.cond_expr);
const len = try parent_gz.addUnNode(.indexable_ptr_len, array_ptr, for_full.ast.cond_expr);
const index_ptr = blk: {
@@ -5839,10 +5786,9 @@ fn switchExpr(
_ = try case_scope.addBreak(.@"break", switch_block, case_result);
}
// Documentation for this: `Zir.Inst.SwitchBlock` and `Zir.Inst.SwitchBlockMulti`.
- try scalar_cases_payload.ensureCapacity(gpa, scalar_cases_payload.items.len +
+ try scalar_cases_payload.ensureUnusedCapacity(gpa, case_scope.instructions.items.len +
3 + // operand, scalar_cases_len, else body len
- @boolToInt(multi_cases_len != 0) +
- case_scope.instructions.items.len);
+ @boolToInt(multi_cases_len != 0));
scalar_cases_payload.appendAssumeCapacity(@enumToInt(operand));
scalar_cases_payload.appendAssumeCapacity(scalar_cases_len);
if (multi_cases_len != 0) {
@@ -5852,9 +5798,11 @@ fn switchExpr(
scalar_cases_payload.appendSliceAssumeCapacity(case_scope.instructions.items);
} else {
// Documentation for this: `Zir.Inst.SwitchBlock` and `Zir.Inst.SwitchBlockMulti`.
- try scalar_cases_payload.ensureCapacity(gpa, scalar_cases_payload.items.len +
- 2 + // operand, scalar_cases_len
- @boolToInt(multi_cases_len != 0));
+ try scalar_cases_payload.ensureUnusedCapacity(
+ gpa,
+ @as(usize, 2) + // operand, scalar_cases_len
+ @boolToInt(multi_cases_len != 0),
+ );
scalar_cases_payload.appendAssumeCapacity(@enumToInt(operand));
scalar_cases_payload.appendAssumeCapacity(scalar_cases_len);
if (multi_cases_len != 0) {
@@ -5975,8 +5923,8 @@ fn switchExpr(
block_scope.break_count += 1;
_ = try case_scope.addBreak(.@"break", switch_block, case_result);
}
- try scalar_cases_payload.ensureCapacity(gpa, scalar_cases_payload.items.len +
- 2 + case_scope.instructions.items.len);
+ try scalar_cases_payload.ensureUnusedCapacity(gpa, 2 +
+ case_scope.instructions.items.len);
scalar_cases_payload.appendAssumeCapacity(@enumToInt(item_inst));
scalar_cases_payload.appendAssumeCapacity(@intCast(u32, case_scope.instructions.items.len));
scalar_cases_payload.appendSliceAssumeCapacity(case_scope.instructions.items);
@@ -6012,8 +5960,8 @@ fn switchExpr(
const payload_index = astgen.extra.items.len;
const zir_datas = astgen.instructions.items(.data);
zir_datas[switch_block].pl_node.payload_index = @intCast(u32, payload_index);
- try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len +
- scalar_cases_payload.items.len + multi_cases_payload.items.len);
+ try astgen.extra.ensureUnusedCapacity(gpa, scalar_cases_payload.items.len +
+ multi_cases_payload.items.len);
const strat = rl.strategy(&block_scope);
switch (strat.tag) {
.break_operand => {
@@ -6821,7 +6769,7 @@ fn as(
) InnerError!Zir.Inst.Ref {
const dest_type = try typeExpr(gz, scope, lhs);
switch (rl) {
- .none, .none_or_ref, .discard, .ref, .ty => {
+ .none, .none_or_ref, .discard, .ref, .ty, .coerced_ty => {
const result = try reachableExpr(gz, scope, .{ .ty = dest_type }, rhs, node);
return rvalue(gz, rl, result, node);
},
@@ -6844,7 +6792,7 @@ fn unionInit(
const union_type = try typeExpr(gz, scope, params[0]);
const field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]);
switch (rl) {
- .none, .none_or_ref, .discard, .ref, .ty, .inferred_ptr => {
+ .none, .none_or_ref, .discard, .ref, .ty, .coerced_ty, .inferred_ptr => {
_ = try gz.addPlNode(.field_type_ref, params[1], Zir.Inst.FieldTypeRef{
.container_type = union_type,
.field_name = field_name,
@@ -6930,7 +6878,7 @@ fn bitCast(
const astgen = gz.astgen;
const dest_type = try typeExpr(gz, scope, lhs);
switch (rl) {
- .none, .none_or_ref, .discard, .ty => {
+ .none, .none_or_ref, .discard, .ty, .coerced_ty => {
const operand = try expr(gz, scope, .none, rhs);
const result = try gz.addPlNode(.bitcast, node, Zir.Inst.Bin{
.lhs = dest_type,
@@ -7740,7 +7688,7 @@ fn callExpr(
.param_index = @intCast(u32, i),
} },
});
- args[i] = try expr(gz, scope, .{ .ty = param_type }, param_node);
+ args[i] = try expr(gz, scope, .{ .coerced_ty = param_type }, param_node);
}
const modifier: std.builtin.CallOptions.Modifier = blk: {
@@ -8433,7 +8381,7 @@ fn rvalue(
src_node: ast.Node.Index,
) InnerError!Zir.Inst.Ref {
switch (rl) {
- .none, .none_or_ref => return result,
+ .none, .none_or_ref, .coerced_ty => return result,
.discard => {
// Emit a compile error for discarding error values.
_ = try gz.addUnNode(.ensure_result_non_error, result, src_node);
@@ -8659,7 +8607,7 @@ fn failNodeNotes(
}
const notes_index: u32 = if (notes.len != 0) blk: {
const notes_start = astgen.extra.items.len;
- try astgen.extra.ensureCapacity(astgen.gpa, notes_start + 1 + notes.len);
+ try astgen.extra.ensureTotalCapacity(astgen.gpa, notes_start + 1 + notes.len);
astgen.extra.appendAssumeCapacity(@intCast(u32, notes.len));
astgen.extra.appendSliceAssumeCapacity(notes);
break :blk @intCast(u32, notes_start);
@@ -8700,7 +8648,7 @@ fn failTokNotes(
}
const notes_index: u32 = if (notes.len != 0) blk: {
const notes_start = astgen.extra.items.len;
- try astgen.extra.ensureCapacity(astgen.gpa, notes_start + 1 + notes.len);
+ try astgen.extra.ensureTotalCapacity(astgen.gpa, notes_start + 1 + notes.len);
astgen.extra.appendAssumeCapacity(@intCast(u32, notes.len));
astgen.extra.appendSliceAssumeCapacity(notes);
break :blk @intCast(u32, notes_start);
@@ -8864,7 +8812,7 @@ fn strLitNodeAsString(astgen: *AstGen, node: ast.Node.Index) !IndexSlice {
while (tok_i <= end) : (tok_i += 1) {
const slice = tree.tokenSlice(tok_i);
const line_bytes = slice[2 .. slice.len - 1];
- try string_bytes.ensureCapacity(gpa, string_bytes.items.len + line_bytes.len + 1);
+ try string_bytes.ensureUnusedCapacity(gpa, line_bytes.len + 1);
string_bytes.appendAssumeCapacity('\n');
string_bytes.appendSliceAssumeCapacity(line_bytes);
}
@@ -9105,7 +9053,7 @@ const GenZir = struct {
// we emit ZIR for the block break instructions to have the result values,
// and then rvalue() on that to pass the value to the result location.
switch (parent_rl) {
- .ty => |ty_inst| {
+ .ty, .coerced_ty => |ty_inst| {
gz.rl_ty_inst = ty_inst;
gz.break_result_loc = parent_rl;
},
@@ -9131,8 +9079,8 @@ const GenZir = struct {
fn setBoolBrBody(gz: GenZir, inst: Zir.Inst.Index) !void {
const gpa = gz.astgen.gpa;
- try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len +
- @typeInfo(Zir.Inst.Block).Struct.fields.len + gz.instructions.items.len);
+ try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Block).Struct.fields.len +
+ gz.instructions.items.len);
const zir_datas = gz.astgen.instructions.items(.data);
zir_datas[inst].bool_br.payload_index = gz.astgen.addExtraAssumeCapacity(
Zir.Inst.Block{ .body_len = @intCast(u32, gz.instructions.items.len) },
@@ -9142,8 +9090,8 @@ const GenZir = struct {
fn setBlockBody(gz: GenZir, inst: Zir.Inst.Index) !void {
const gpa = gz.astgen.gpa;
- try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len +
- @typeInfo(Zir.Inst.Block).Struct.fields.len + gz.instructions.items.len);
+ try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Block).Struct.fields.len +
+ gz.instructions.items.len);
const zir_datas = gz.astgen.instructions.items(.data);
zir_datas[inst].pl_node.payload_index = gz.astgen.addExtraAssumeCapacity(
Zir.Inst.Block{ .body_len = @intCast(u32, gz.instructions.items.len) },
@@ -9155,8 +9103,8 @@ const GenZir = struct {
/// `store_to_block_ptr` instructions with lhs set to .none.
fn setBlockBodyEliding(gz: GenZir, inst: Zir.Inst.Index) !void {
const gpa = gz.astgen.gpa;
- try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len +
- @typeInfo(Zir.Inst.Block).Struct.fields.len + gz.instructions.items.len);
+ try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Block).Struct.fields.len +
+ gz.instructions.items.len);
const zir_datas = gz.astgen.instructions.items(.data);
const zir_tags = gz.astgen.instructions.items(.tag);
const block_pl_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Block{
@@ -9177,9 +9125,10 @@ const GenZir = struct {
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,
+ param_block: Zir.Inst.Index,
+ ret_ty: []const Zir.Inst.Index,
+ ret_br: Zir.Inst.Index,
cc: Zir.Inst.Ref,
align_inst: Zir.Inst.Ref,
lib_name: u32,
@@ -9187,11 +9136,8 @@ const GenZir = struct {
is_inferred_error: bool,
is_test: bool,
is_extern: bool,
- cur_bit_bag: u32,
- bit_bag: []const u32,
}) !Zir.Inst.Ref {
assert(args.src_node != 0);
- assert(args.ret_ty != .none);
const astgen = gz.astgen;
const gpa = astgen.gpa;
@@ -9226,27 +9172,22 @@ const GenZir = struct {
src_locs = &src_locs_buffer;
}
- const any_are_comptime = args.cur_bit_bag != 0 or for (args.bit_bag) |x| {
- if (x != 0) break true;
- } else false;
-
if (args.cc != .none or args.lib_name != 0 or
args.is_var_args or args.is_test or args.align_inst != .none or
- args.is_extern or any_are_comptime)
+ args.is_extern)
{
try astgen.extra.ensureUnusedCapacity(
gpa,
@typeInfo(Zir.Inst.ExtendedFunc).Struct.fields.len +
- @boolToInt(any_are_comptime) + args.bit_bag.len +
- args.param_types.len + args.body.len + src_locs.len +
+ args.ret_ty.len + args.body.len + src_locs.len +
@boolToInt(args.lib_name != 0) +
@boolToInt(args.align_inst != .none) +
@boolToInt(args.cc != .none),
);
const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.ExtendedFunc{
.src_node = gz.nodeIndexToRelative(args.src_node),
- .return_type = args.ret_ty,
- .param_types_len = @intCast(u32, args.param_types.len),
+ .param_block = args.param_block,
+ .ret_body_len = @intCast(u32, args.ret_ty.len),
.body_len = @intCast(u32, args.body.len),
});
if (args.lib_name != 0) {
@@ -9258,15 +9199,14 @@ const GenZir = struct {
if (args.align_inst != .none) {
astgen.extra.appendAssumeCapacity(@enumToInt(args.align_inst));
}
- if (any_are_comptime) {
- astgen.extra.appendSliceAssumeCapacity(args.bit_bag); // Likely empty.
- astgen.extra.appendAssumeCapacity(args.cur_bit_bag);
- }
- astgen.appendRefsAssumeCapacity(args.param_types);
+ astgen.extra.appendSliceAssumeCapacity(args.ret_ty);
astgen.extra.appendSliceAssumeCapacity(args.body);
astgen.extra.appendSliceAssumeCapacity(src_locs);
const new_index = @intCast(Zir.Inst.Index, astgen.instructions.len);
+ if (args.ret_br != 0) {
+ astgen.instructions.items(.data)[args.ret_br].@"break".block_inst = new_index;
+ }
astgen.instructions.appendAssumeCapacity(.{
.tag = .extended,
.data = .{ .extended = .{
@@ -9279,7 +9219,6 @@ const GenZir = struct {
.has_align = args.align_inst != .none,
.is_test = args.is_test,
.is_extern = args.is_extern,
- .has_comptime_bits = any_are_comptime,
}),
.operand = payload_index,
} },
@@ -9287,24 +9226,27 @@ const GenZir = struct {
gz.instructions.appendAssumeCapacity(new_index);
return indexToRef(new_index);
} else {
- try gz.astgen.extra.ensureUnusedCapacity(
+ try astgen.extra.ensureUnusedCapacity(
gpa,
@typeInfo(Zir.Inst.Func).Struct.fields.len +
- args.param_types.len + args.body.len + src_locs.len,
+ args.ret_ty.len + args.body.len + src_locs.len,
);
- const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Func{
- .return_type = args.ret_ty,
- .param_types_len = @intCast(u32, args.param_types.len),
+ const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.Func{
+ .param_block = args.param_block,
+ .ret_body_len = @intCast(u32, args.ret_ty.len),
.body_len = @intCast(u32, args.body.len),
});
- gz.astgen.appendRefsAssumeCapacity(args.param_types);
- gz.astgen.extra.appendSliceAssumeCapacity(args.body);
- gz.astgen.extra.appendSliceAssumeCapacity(src_locs);
+ astgen.extra.appendSliceAssumeCapacity(args.ret_ty);
+ astgen.extra.appendSliceAssumeCapacity(args.body);
+ astgen.extra.appendSliceAssumeCapacity(src_locs);
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(.{
+ const new_index = @intCast(Zir.Inst.Index, astgen.instructions.len);
+ if (args.ret_br != 0) {
+ astgen.instructions.items(.data)[args.ret_br].@"break".block_inst = new_index;
+ }
+ astgen.instructions.appendAssumeCapacity(.{
.tag = tag,
.data = .{ .pl_node = .{
.src_node = gz.nodeIndexToRelative(args.src_node),
@@ -9380,10 +9322,10 @@ const GenZir = struct {
assert(callee != .none);
assert(src_node != 0);
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.Call).Struct.fields.len + args.len);
+ try gz.instructions.ensureUnusedCapacity(gpa, 1);
+ try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1);
+ try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Call).Struct.fields.len +
+ args.len);
const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Call{
.callee = callee,
@@ -9412,8 +9354,8 @@ const GenZir = struct {
) !Zir.Inst.Index {
assert(lhs != .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.instructions.ensureUnusedCapacity(gpa, 1);
+ try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1);
const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len);
gz.astgen.instructions.appendAssumeCapacity(.{
@@ -9486,8 +9428,8 @@ const GenZir = struct {
extra: anytype,
) !Zir.Inst.Ref {
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.instructions.ensureUnusedCapacity(gpa, 1);
+ try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1);
const payload_index = try gz.astgen.addExtra(extra);
const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len);
@@ -9502,6 +9444,38 @@ const GenZir = struct {
return indexToRef(new_index);
}
+ fn addParam(
+ gz: *GenZir,
+ tag: Zir.Inst.Tag,
+ /// Absolute token index. This function does the conversion to Decl offset.
+ abs_tok_index: ast.TokenIndex,
+ name: u32,
+ body: []const u32,
+ ) !Zir.Inst.Index {
+ const gpa = gz.astgen.gpa;
+ try gz.instructions.ensureUnusedCapacity(gpa, 1);
+ try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1);
+ try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Param).Struct.fields.len +
+ body.len);
+
+ const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Param{
+ .name = name,
+ .body_len = @intCast(u32, body.len),
+ });
+ gz.astgen.extra.appendSliceAssumeCapacity(body);
+
+ const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len);
+ gz.astgen.instructions.appendAssumeCapacity(.{
+ .tag = tag,
+ .data = .{ .pl_tok = .{
+ .src_tok = gz.tokenIndexToRelative(abs_tok_index),
+ .payload_index = payload_index,
+ } },
+ });
+ gz.instructions.appendAssumeCapacity(new_index);
+ return new_index;
+ }
+
fn addExtendedPayload(
gz: *GenZir,
opcode: Zir.Inst.Extended,
@@ -9509,8 +9483,8 @@ const GenZir = struct {
) !Zir.Inst.Ref {
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.instructions.ensureUnusedCapacity(gpa, 1);
+ try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1);
const payload_index = try gz.astgen.addExtra(extra);
const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len);
@@ -9566,8 +9540,8 @@ const GenZir = struct {
elem_type: Zir.Inst.Ref,
) !Zir.Inst.Ref {
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.instructions.ensureUnusedCapacity(gpa, 1);
+ try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1);
const payload_index = try gz.astgen.addExtra(Zir.Inst.ArrayTypeSentinel{
.sentinel = sentinel,
@@ -9822,7 +9796,7 @@ const GenZir = struct {
/// Leaves the `payload_index` field undefined.
fn addCondBr(gz: *GenZir, tag: Zir.Inst.Tag, node: ast.Node.Index) !Zir.Inst.Index {
const gpa = gz.astgen.gpa;
- try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
+ try gz.instructions.ensureUnusedCapacity(gpa, 1);
const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len);
try gz.astgen.instructions.append(gpa, .{
.tag = tag,
diff --git a/src/Compilation.zig b/src/Compilation.zig
@@ -2116,7 +2116,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
if (builtin.mode == .Debug and self.verbose_air) {
std.debug.print("# Begin Function AIR: {s}:\n", .{decl.name});
@import("print_air.zig").dump(gpa, air, decl.namespace.file_scope.zir, liveness);
- std.debug.print("# End Function AIR: {s}:\n", .{decl.name});
+ std.debug.print("# End Function AIR: {s}\n\n", .{decl.name});
}
self.bin_file.updateFunc(module, func, air, liveness) catch |err| switch (err) {
diff --git a/src/Module.zig b/src/Module.zig
@@ -61,6 +61,11 @@ export_owners: std.AutoArrayHashMapUnmanaged(*Decl, []*Export) = .{},
/// Keys are fully resolved file paths. This table owns the keys and values.
import_table: std.StringArrayHashMapUnmanaged(*Scope.File) = .{},
+/// The set of all the generic function instantiations. This is used so that when a generic
+/// function is called twice with the same comptime parameter arguments, both calls dispatch
+/// to the same function.
+monomorphed_funcs: MonomorphedFuncsSet = .{},
+
/// We optimize memory usage for a compilation with no compile errors by storing the
/// error messages and mapping outside of `Decl`.
/// The ErrorMsg memory is owned by the decl, using Module's general purpose allocator.
@@ -114,6 +119,44 @@ emit_h: ?*GlobalEmitH,
test_functions: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{},
+const MonomorphedFuncsSet = std.HashMapUnmanaged(
+ *Fn,
+ void,
+ MonomorphedFuncsContext,
+ std.hash_map.default_max_load_percentage,
+);
+
+const MonomorphedFuncsContext = struct {
+ pub fn eql(ctx: @This(), a: *Fn, b: *Fn) bool {
+ _ = ctx;
+ return a == b;
+ }
+
+ /// Must match `Sema.GenericCallAdapter.hash`.
+ pub fn hash(ctx: @This(), key: *Fn) u64 {
+ _ = ctx;
+ var hasher = std.hash.Wyhash.init(0);
+
+ // The generic function Decl is guaranteed to be the first dependency
+ // of each of its instantiations.
+ const generic_owner_decl = key.owner_decl.dependencies.keys()[0];
+ const generic_func = generic_owner_decl.val.castTag(.function).?.data;
+ std.hash.autoHash(&hasher, @ptrToInt(generic_func));
+
+ // This logic must be kept in sync with the logic in `analyzeCall` that
+ // computes the hash.
+ const comptime_args = key.comptime_args.?;
+ const generic_ty_info = generic_owner_decl.ty.fnInfo();
+ for (generic_ty_info.param_types) |param_ty, i| {
+ if (generic_ty_info.paramIsComptime(i) and param_ty.tag() != .generic_poison) {
+ comptime_args[i].val.hash(param_ty, &hasher);
+ }
+ }
+
+ return hasher.final();
+ }
+};
+
/// A `Module` has zero or one of these depending on whether `-femit-h` is enabled.
pub const GlobalEmitH = struct {
/// Where to put the output.
@@ -757,6 +800,10 @@ pub const Union = struct {
pub const Fn = struct {
/// The Decl that corresponds to the function itself.
owner_decl: *Decl,
+ /// If this is not null, this function is a generic function instantiation, and
+ /// there is a `Value` here for each parameter of the function. Non-comptime
+ /// parameters are marked with an `unreachable_value`.
+ comptime_args: ?[*]TypedValue = null,
/// The ZIR instruction that is a function instruction. Use this to find
/// the body. We store this rather than the body directly so that when ZIR
/// is regenerated on update(), we can map this to the new corresponding
@@ -795,6 +842,9 @@ pub const Fn = struct {
pub fn getInferredErrorSet(func: *Fn) ?*std.StringHashMapUnmanaged(void) {
const ret_ty = func.owner_decl.ty.fnReturnType();
+ if (ret_ty.tag() == .generic_poison) {
+ return null;
+ }
if (ret_ty.zigTypeTag() == .ErrorUnion) {
if (ret_ty.errorUnionSet().castTag(.error_set_inferred)) |payload| {
return &payload.data.map;
@@ -1169,6 +1219,8 @@ pub const Scope = struct {
/// for the one that will be the same for all Block instances.
src_decl: *Decl,
instructions: ArrayListUnmanaged(Air.Inst.Index),
+ // `param` instructions are collected here to be used by the `func` instruction.
+ params: std.ArrayListUnmanaged(Param) = .{},
label: ?*Label = null,
inlining: ?*Inlining,
/// If runtime_index is not 0 then one of these is guaranteed to be non null.
@@ -1183,6 +1235,12 @@ pub const Scope = struct {
/// when null, it is determined by build mode, changed by @setRuntimeSafety
want_safety: ?bool = null,
+ const Param = struct {
+ /// `noreturn` means `anytype`.
+ ty: Type,
+ is_comptime: bool,
+ };
+
/// This `Block` maps a block ZIR instruction to the corresponding
/// AIR instruction for break instruction analysis.
pub const Label = struct {
@@ -1630,8 +1688,11 @@ pub const SrcLoc = struct {
.@"asm" => tree.asmFull(node),
else => unreachable,
};
+ const asm_output = full.outputs[0];
+ const node_datas = tree.nodes.items(.data);
+ const ret_ty_node = node_datas[asm_output].lhs;
const main_tokens = tree.nodes.items(.main_token);
- const tok_index = main_tokens[full.outputs[0]];
+ const tok_index = main_tokens[ret_ty_node];
const token_starts = tree.tokens.items(.start);
return token_starts[tok_index];
},
@@ -2095,7 +2156,20 @@ pub const LazySrcLoc = union(enum) {
};
pub const SemaError = error{ OutOfMemory, AnalysisFail };
-pub const CompileError = error{ OutOfMemory, AnalysisFail, NeededSourceLocation };
+pub const CompileError = error{
+ OutOfMemory,
+ /// When this is returned, the compile error for the failure has already been recorded.
+ AnalysisFail,
+ /// Returned when a compile error needed to be reported but a provided LazySrcLoc was set
+ /// to the `unneeded` tag. The source location was, in fact, needed. It is expected that
+ /// somewhere up the call stack, the operation will be retried after doing expensive work
+ /// to compute a source location.
+ NeededSourceLocation,
+ /// A Type or Value was needed to be used during semantic analysis, but it was not available
+ /// because the function is generic. This is only seen when analyzing the body of a param
+ /// instruction.
+ GenericPoison,
+};
pub fn deinit(mod: *Module) void {
const gpa = mod.gpa;
@@ -2177,6 +2251,7 @@ pub fn deinit(mod: *Module) void {
mod.error_name_list.deinit(gpa);
mod.test_functions.deinit(gpa);
+ mod.monomorphed_funcs.deinit(gpa);
}
fn freeExportList(gpa: *Allocator, export_list: []*Export) void {
@@ -2792,14 +2867,16 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl: *Decl) SemaError!void {
}
return error.AnalysisFail;
},
- else => {
+ error.NeededSourceLocation => unreachable,
+ error.GenericPoison => unreachable,
+ else => |e| {
decl.analysis = .sema_failure_retryable;
try mod.failed_decls.ensureUnusedCapacity(mod.gpa, 1);
mod.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
mod.gpa,
decl.srcLoc(),
"unable to analyze: {s}",
- .{@errorName(err)},
+ .{@errorName(e)},
));
return error.AnalysisFail;
},
@@ -2899,7 +2976,6 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void {
.namespace = &struct_obj.namespace,
.func = null,
.owner_func = null,
- .param_inst_list = &.{},
};
defer sema.deinit();
var block_scope: Scope.Block = .{
@@ -2954,7 +3030,6 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
.namespace = decl.namespace,
.func = null,
.owner_func = null,
- .param_inst_list = &.{},
};
defer sema.deinit();
@@ -2980,7 +3055,10 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
.inlining = null,
.is_comptime = true,
};
- defer block_scope.instructions.deinit(gpa);
+ defer {
+ block_scope.instructions.deinit(gpa);
+ block_scope.params.deinit(gpa);
+ }
const zir_block_index = decl.zirBlockIndex();
const inst_data = zir_datas[zir_block_index].pl_node;
@@ -3625,8 +3703,6 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air {
defer decl.value_arena.?.* = arena.state;
const fn_ty = decl.ty;
- const param_inst_list = try gpa.alloc(Air.Inst.Ref, fn_ty.fnParamLen());
- defer gpa.free(param_inst_list);
var sema: Sema = .{
.mod = mod,
@@ -3637,7 +3713,6 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air {
.namespace = decl.namespace,
.func = func,
.owner_func = func,
- .param_inst_list = param_inst_list,
};
defer sema.deinit();
@@ -3656,29 +3731,71 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air {
};
defer inner_block.instructions.deinit(gpa);
- // AIR requires the arg parameters to be the first N instructions.
- try inner_block.instructions.ensureTotalCapacity(gpa, param_inst_list.len);
- for (param_inst_list) |*param_inst, param_index| {
- const param_type = fn_ty.fnParamType(param_index);
+ const fn_info = sema.code.getFnInfo(func.zir_body_inst);
+ const zir_tags = sema.code.instructions.items(.tag);
+
+ // Here we are performing "runtime semantic analysis" for a function body, which means
+ // we must map the parameter ZIR instructions to `arg` AIR instructions.
+ // AIR requires the `arg` parameters to be the first N instructions.
+ // This could be a generic function instantiation, however, in which case we need to
+ // map the comptime parameters to constant values and only emit arg AIR instructions
+ // for the runtime ones.
+ const runtime_params_len = @intCast(u32, fn_ty.fnParamLen());
+ try inner_block.instructions.ensureTotalCapacity(gpa, runtime_params_len);
+ try sema.air_instructions.ensureUnusedCapacity(gpa, fn_info.total_params_len * 2); // * 2 for the `addType`
+ try sema.inst_map.ensureUnusedCapacity(gpa, fn_info.total_params_len);
+
+ var runtime_param_index: usize = 0;
+ var total_param_index: usize = 0;
+ for (fn_info.param_body) |inst| {
+ const name = switch (zir_tags[inst]) {
+ .param, .param_comptime => blk: {
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_tok;
+ const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index).data;
+ break :blk extra.name;
+ },
+
+ .param_anytype, .param_anytype_comptime => blk: {
+ const str_tok = sema.code.instructions.items(.data)[inst].str_tok;
+ break :blk str_tok.start;
+ },
+
+ else => continue,
+ };
+ if (func.comptime_args) |comptime_args| {
+ const arg_tv = comptime_args[total_param_index];
+ if (arg_tv.val.tag() != .unreachable_value) {
+ // We have a comptime value for this parameter.
+ const arg = try sema.addConstant(arg_tv.ty, arg_tv.val);
+ sema.inst_map.putAssumeCapacityNoClobber(inst, arg);
+ total_param_index += 1;
+ continue;
+ }
+ }
+ const param_type = fn_ty.fnParamType(runtime_param_index);
const ty_ref = try sema.addType(param_type);
const arg_index = @intCast(u32, sema.air_instructions.len);
inner_block.instructions.appendAssumeCapacity(arg_index);
- param_inst.* = Air.indexToRef(arg_index);
- try sema.air_instructions.append(gpa, .{
+ sema.air_instructions.appendAssumeCapacity(.{
.tag = .arg,
- .data = .{
- .ty_str = .{
- .ty = ty_ref,
- .str = undefined, // Set in the semantic analysis of the arg instruction.
- },
- },
+ .data = .{ .ty_str = .{
+ .ty = ty_ref,
+ .str = name,
+ } },
});
+ sema.inst_map.putAssumeCapacityNoClobber(inst, Air.indexToRef(arg_index));
+ total_param_index += 1;
+ runtime_param_index += 1;
}
func.state = .in_progress;
log.debug("set {s} to in_progress", .{decl.name});
- try sema.analyzeFnBody(&inner_block, func.zir_body_inst);
+ _ = sema.analyzeBody(&inner_block, fn_info.body) catch |err| switch (err) {
+ error.NeededSourceLocation => @panic("zig compiler bug: NeededSourceLocation"),
+ error.GenericPoison => @panic("zig compiler bug: GenericPoison"),
+ else => |e| return e,
+ };
// Copy the block into place and mark that as the main block.
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).Struct.fields.len +
@@ -3714,7 +3831,7 @@ fn markOutdatedDecl(mod: *Module, decl: *Decl) !void {
decl.analysis = .outdated;
}
-fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: ast.Node.Index) !*Decl {
+pub fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: ast.Node.Index) !*Decl {
// If we have emit-h then we must allocate a bigger structure to store the emit-h state.
const new_decl: *Decl = if (mod.emit_h != null) blk: {
const parent_struct = try mod.gpa.create(DeclPlusEmitH);
@@ -4330,7 +4447,6 @@ pub fn analyzeStructFields(mod: *Module, struct_obj: *Struct) CompileError!void
.namespace = &struct_obj.namespace,
.owner_func = null,
.func = null,
- .param_inst_list = &.{},
};
defer sema.deinit();
@@ -4484,7 +4600,6 @@ pub fn analyzeUnionFields(mod: *Module, union_obj: *Union) CompileError!void {
.namespace = &union_obj.namespace,
.owner_func = null,
.func = null,
- .param_inst_list = &.{},
};
defer sema.deinit();
diff --git a/src/Sema.zig b/src/Sema.zig
@@ -29,13 +29,6 @@ owner_func: ?*Module.Fn,
/// This starts out the same as `owner_func` and then diverges in the case of
/// an inline or comptime function call.
func: ?*Module.Fn,
-/// For now, AIR requires arg instructions to be the first N instructions in the
-/// AIR code. We store references here for the purpose of `resolveInst`.
-/// This can get reworked with AIR memory layout changes, into simply:
-/// > Denormalized data to make `resolveInst` faster. This is 0 if not inside a function,
-/// > otherwise it is the number of parameters of the function.
-/// > param_count: u32
-param_inst_list: []const Air.Inst.Ref,
branch_quota: u32 = 1000,
branch_count: u32 = 0,
/// This field is updated when a new source location becomes active, so that
@@ -43,8 +36,22 @@ branch_count: u32 = 0,
/// access to the source location set by the previous instruction which did
/// contain a mapped source location.
src: LazySrcLoc = .{ .token_offset = 0 },
-next_arg_index: usize = 0,
decl_val_table: std.AutoHashMapUnmanaged(*Decl, Air.Inst.Ref) = .{},
+/// When doing a generic function instantiation, this array collects a
+/// `Value` object for each parameter that is comptime known and thus elided
+/// from the generated function. This memory is allocated by a parent `Sema` and
+/// owned by the values arena of the Sema owner_decl.
+comptime_args: []TypedValue = &.{},
+/// Marks the function instruction that `comptime_args` applies to so that we
+/// don't accidentally apply it to a function prototype which is used in the
+/// type expression of a generic function parameter.
+comptime_args_fn_inst: Zir.Inst.Index = 0,
+/// When `comptime_args` is provided, this field is also provided. It was used as
+/// the key in the `monomorphed_funcs` set. The `func` instruction is supposed
+/// to use this instead of allocating a fresh one. This avoids an unnecessary
+/// extra hash table lookup in the `monomorphed_funcs` set.
+/// Sema will set this to null when it takes ownership.
+preallocated_new_func: ?*Module.Fn = null,
const std = @import("std");
const mem = std.mem;
@@ -80,45 +87,6 @@ pub fn deinit(sema: *Sema) void {
sema.* = undefined;
}
-pub fn analyzeFnBody(
- sema: *Sema,
- block: *Scope.Block,
- fn_body_inst: Zir.Inst.Index,
-) SemaError!void {
- const tags = sema.code.instructions.items(.tag);
- const datas = sema.code.instructions.items(.data);
- const body: []const Zir.Inst.Index = switch (tags[fn_body_inst]) {
- .func, .func_inferred => blk: {
- const inst_data = datas[fn_body_inst].pl_node;
- const extra = sema.code.extraData(Zir.Inst.Func, inst_data.payload_index);
- const param_types_len = extra.data.param_types_len;
- const body = sema.code.extra[extra.end + param_types_len ..][0..extra.data.body_len];
- break :blk body;
- },
- .extended => blk: {
- const extended = datas[fn_body_inst].extended;
- assert(extended.opcode == .func);
- const extra = sema.code.extraData(Zir.Inst.ExtendedFunc, extended.operand);
- const small = @bitCast(Zir.Inst.ExtendedFunc.Small, extended.small);
- var extra_index: usize = extra.end;
- extra_index += @boolToInt(small.has_lib_name);
- extra_index += @boolToInt(small.has_cc);
- extra_index += @boolToInt(small.has_align);
- if (small.has_comptime_bits) {
- extra_index += (extra.data.param_types_len + 31) / 32;
- }
- extra_index += extra.data.param_types_len;
- const body = sema.code.extra[extra_index..][0..extra.data.body_len];
- break :blk body;
- },
- else => unreachable,
- };
- _ = sema.analyzeBody(block, body) catch |err| switch (err) {
- error.NeededSourceLocation => unreachable,
- else => |e| return e,
- };
-}
-
/// Returns only the result from the body that is specified.
/// Only appropriate to call when it is determined at comptime that this body
/// has no peers.
@@ -162,7 +130,6 @@ pub fn analyzeBody(
const inst = body[i];
const air_inst: Air.Inst.Ref = switch (tags[inst]) {
// zig fmt: off
- .arg => try sema.zirArg(block, inst),
.alloc => try sema.zirAlloc(block, inst),
.alloc_inferred => try sema.zirAllocInferred(block, inst, Type.initTag(.inferred_alloc_const)),
.alloc_inferred_mut => try sema.zirAllocInferred(block, inst, Type.initTag(.inferred_alloc_mut)),
@@ -499,6 +466,26 @@ pub fn analyzeBody(
i += 1;
continue;
},
+ .param => {
+ try sema.zirParam(block, inst, false);
+ i += 1;
+ continue;
+ },
+ .param_comptime => {
+ try sema.zirParam(block, inst, true);
+ i += 1;
+ continue;
+ },
+ .param_anytype => {
+ try sema.zirParamAnytype(block, inst, false);
+ i += 1;
+ continue;
+ },
+ .param_anytype_comptime => {
+ try sema.zirParamAnytype(block, inst, true);
+ i += 1;
+ continue;
+ },
// Special case instructions to handle comptime control flow.
.repeat_inline => {
@@ -648,6 +635,7 @@ fn resolveValue(
air_ref: Air.Inst.Ref,
) CompileError!Value {
if (try sema.resolveMaybeUndefValAllowVariables(block, src, air_ref)) |val| {
+ if (val.tag() == .generic_poison) return error.GenericPoison;
return val;
}
return sema.failWithNeededComptime(block, src);
@@ -665,6 +653,7 @@ fn resolveConstValue(
switch (val.tag()) {
.undef => return sema.failWithUseOfUndef(block, src),
.variable => return sema.failWithNeededComptime(block, src),
+ .generic_poison => return error.GenericPoison,
else => return val,
}
}
@@ -1044,7 +1033,6 @@ fn zirEnumDecl(
.namespace = &enum_obj.namespace,
.owner_func = null,
.func = null,
- .param_inst_list = &.{},
.branch_quota = sema.branch_quota,
.branch_count = sema.branch_count,
};
@@ -1324,57 +1312,44 @@ fn zirIndexablePtrLen(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
const src = inst_data.src();
- const array_ptr = sema.resolveInst(inst_data.operand);
- const array_ptr_src = src;
+ const array = sema.resolveInst(inst_data.operand);
+ const array_ty = sema.typeOf(array);
- const elem_ty = sema.typeOf(array_ptr).elemType();
- if (elem_ty.isSlice()) {
- const slice_inst = try sema.analyzeLoad(block, src, array_ptr, array_ptr_src);
- return sema.analyzeSliceLen(block, src, slice_inst);
+ if (array_ty.isSlice()) {
+ return sema.analyzeSliceLen(block, src, array);
}
- if (!elem_ty.isIndexable()) {
- const cond_src: LazySrcLoc = .{ .node_offset_for_cond = inst_data.src_node };
- const msg = msg: {
- const msg = try sema.mod.errMsg(
- &block.base,
- cond_src,
- "type '{}' does not support indexing",
- .{elem_ty},
- );
- errdefer msg.destroy(sema.gpa);
- try sema.mod.errNote(
- &block.base,
- cond_src,
- msg,
- "for loop operand must be an array, slice, tuple, or vector",
- .{},
- );
- break :msg msg;
- };
- return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
- }
- const result_ptr = try sema.fieldPtr(block, src, array_ptr, "len", src);
- const result_ptr_src = array_ptr_src;
- return sema.analyzeLoad(block, src, result_ptr, result_ptr_src);
-}
-fn zirArg(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
- const inst_data = sema.code.instructions.items(.data)[inst].str_tok;
- const arg_name = inst_data.get(sema.code);
- const arg_index = sema.next_arg_index;
- sema.next_arg_index += 1;
-
- // TODO check if arg_name shadows a Decl
- _ = arg_name;
-
- if (block.inlining) |_| {
- return sema.param_inst_list[arg_index];
+ if (array_ty.isSinglePointer()) {
+ const elem_ty = array_ty.elemType();
+ if (elem_ty.isSlice()) {
+ const slice_inst = try sema.analyzeLoad(block, src, array, src);
+ return sema.analyzeSliceLen(block, src, slice_inst);
+ }
+ if (!elem_ty.isIndexable()) {
+ const msg = msg: {
+ const msg = try sema.mod.errMsg(
+ &block.base,
+ src,
+ "type '{}' does not support indexing",
+ .{elem_ty},
+ );
+ errdefer msg.destroy(sema.gpa);
+ try sema.mod.errNote(
+ &block.base,
+ src,
+ msg,
+ "for loop operand must be an array, slice, tuple, or vector",
+ .{},
+ );
+ break :msg msg;
+ };
+ return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
+ }
+ const result_ptr = try sema.fieldPtr(block, src, array, "len", src);
+ return sema.analyzeLoad(block, src, result_ptr, src);
}
- // Set the name of the Air.Arg instruction for use by codegen debug info.
- const air_arg = sema.param_inst_list[arg_index];
- sema.air_instructions.items(.data)[Air.refToIndex(air_arg).?].ty_str.str = inst_data.start;
- return air_arg;
+ return sema.mod.fail(&block.base, src, "TODO implement Sema.zirIndexablePtrLen", .{});
}
fn zirAllocExtended(
@@ -2385,6 +2360,40 @@ fn zirCall(
return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args);
}
+const GenericCallAdapter = struct {
+ generic_fn: *Module.Fn,
+ precomputed_hash: u64,
+ func_ty_info: Type.Payload.Function.Data,
+ comptime_vals: []const Value,
+
+ pub fn eql(ctx: @This(), adapted_key: void, other_key: *Module.Fn) bool {
+ _ = adapted_key;
+ // The generic function Decl is guaranteed to be the first dependency
+ // of each of its instantiations.
+ const generic_owner_decl = other_key.owner_decl.dependencies.keys()[0];
+ if (ctx.generic_fn.owner_decl != generic_owner_decl) return false;
+
+ // This logic must be kept in sync with the logic in `analyzeCall` that
+ // computes the hash.
+ const other_comptime_args = other_key.comptime_args.?;
+ for (ctx.func_ty_info.param_types) |param_ty, i| {
+ if (ctx.func_ty_info.paramIsComptime(i) and param_ty.tag() != .generic_poison) {
+ if (!ctx.comptime_vals[i].eql(other_comptime_args[i].val, param_ty)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /// The implementation of the hash is in semantic analysis of function calls, so
+ /// that any errors when computing the hash can be properly reported.
+ pub fn hash(ctx: @This(), adapted_key: void) u64 {
+ _ = adapted_key;
+ return ctx.precomputed_hash;
+ }
+};
+
fn analyzeCall(
sema: *Sema,
block: *Scope.Block,
@@ -2393,41 +2402,44 @@ fn analyzeCall(
call_src: LazySrcLoc,
modifier: std.builtin.CallOptions.Modifier,
ensure_result_used: bool,
- args: []const Air.Inst.Ref,
+ uncasted_args: []const Air.Inst.Ref,
) CompileError!Air.Inst.Ref {
+ const mod = sema.mod;
+
const func_ty = sema.typeOf(func);
if (func_ty.zigTypeTag() != .Fn)
- return sema.mod.fail(&block.base, func_src, "type '{}' not a function", .{func_ty});
+ return mod.fail(&block.base, func_src, "type '{}' not a function", .{func_ty});
- const cc = func_ty.fnCallingConvention();
+ const func_ty_info = func_ty.fnInfo();
+ const cc = func_ty_info.cc;
if (cc == .Naked) {
// TODO add error note: declared here
- return sema.mod.fail(
+ return mod.fail(
&block.base,
func_src,
"unable to call function with naked calling convention",
.{},
);
}
- const fn_params_len = func_ty.fnParamLen();
- if (func_ty.fnIsVarArgs()) {
+ const fn_params_len = func_ty_info.param_types.len;
+ if (func_ty_info.is_var_args) {
assert(cc == .C);
- if (args.len < fn_params_len) {
+ if (uncasted_args.len < fn_params_len) {
// TODO add error note: declared here
- return sema.mod.fail(
+ return mod.fail(
&block.base,
func_src,
"expected at least {d} argument(s), found {d}",
- .{ fn_params_len, args.len },
+ .{ fn_params_len, uncasted_args.len },
);
}
- } else if (fn_params_len != args.len) {
+ } else if (fn_params_len != uncasted_args.len) {
// TODO add error note: declared here
- return sema.mod.fail(
+ return mod.fail(
&block.base,
func_src,
"expected {d} argument(s), found {d}",
- .{ fn_params_len, args.len },
+ .{ fn_params_len, uncasted_args.len },
);
}
@@ -2442,21 +2454,30 @@ fn analyzeCall(
.never_inline,
.no_async,
.always_tail,
- => return sema.mod.fail(&block.base, call_src, "TODO implement call with modifier {}", .{
+ => return mod.fail(&block.base, call_src, "TODO implement call with modifier {}", .{
modifier,
}),
}
const gpa = sema.gpa;
- const is_comptime_call = block.is_comptime or modifier == .compile_time;
+ const is_comptime_call = block.is_comptime or modifier == .compile_time or
+ func_ty_info.return_type.requiresComptime();
const is_inline_call = is_comptime_call or modifier == .always_inline or
- func_ty.fnCallingConvention() == .Inline;
+ func_ty_info.cc == .Inline;
const result: Air.Inst.Ref = if (is_inline_call) res: {
+ // TODO look into not allocating this args array
+ const args = try sema.arena.alloc(Air.Inst.Ref, uncasted_args.len);
+ for (uncasted_args) |uncasted_arg, i| {
+ const param_ty = func_ty.fnParamType(i);
+ const arg_src = call_src; // TODO: better source location
+ args[i] = try sema.coerce(block, param_ty, uncasted_arg, arg_src);
+ }
+
const func_val = try sema.resolveConstValue(block, func_src, func);
const module_fn = switch (func_val.tag()) {
.function => func_val.castTag(.function).?.data,
- .extern_fn => return sema.mod.fail(&block.base, call_src, "{s} call of extern function", .{
+ .extern_fn => return mod.fail(&block.base, call_src, "{s} call of extern function", .{
@as([]const u8, if (is_comptime_call) "comptime" else "inline"),
}),
else => unreachable,
@@ -2502,14 +2523,6 @@ fn analyzeCall(
sema.func = module_fn;
defer sema.func = parent_func;
- const parent_param_inst_list = sema.param_inst_list;
- sema.param_inst_list = args;
- defer sema.param_inst_list = parent_param_inst_list;
-
- const parent_next_arg_index = sema.next_arg_index;
- sema.next_arg_index = 0;
- defer sema.next_arg_index = parent_next_arg_index;
-
var child_block: Scope.Block = .{
.parent = null,
.sema = sema,
@@ -2529,16 +2542,229 @@ fn analyzeCall(
try sema.emitBackwardBranch(&child_block, call_src);
// This will have return instructions analyzed as break instructions to
- // the block_inst above.
- try sema.analyzeFnBody(&child_block, module_fn.zir_body_inst);
+ // the block_inst above. Here we are performing "comptime/inline semantic analysis"
+ // for a function body, which means we must map the parameter ZIR instructions to
+ // the AIR instructions of the callsite.
+ const fn_info = sema.code.getFnInfo(module_fn.zir_body_inst);
+ const zir_tags = sema.code.instructions.items(.tag);
+ var arg_i: usize = 0;
+ try sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, args.len));
+ for (fn_info.param_body) |inst| {
+ switch (zir_tags[inst]) {
+ .param, .param_comptime, .param_anytype, .param_anytype_comptime => {},
+ else => continue,
+ }
+ sema.inst_map.putAssumeCapacityNoClobber(inst, args[arg_i]);
+ arg_i += 1;
+ }
+ _ = try sema.analyzeBody(&child_block, fn_info.body);
+ break :res try sema.analyzeBlockBody(block, call_src, &child_block, merges);
+ } else if (func_ty_info.is_generic) res: {
+ const func_val = try sema.resolveConstValue(block, func_src, func);
+ const module_fn = func_val.castTag(.function).?.data;
+ // Check the Module's generic function map with an adapted context, so that we
+ // can match against `uncasted_args` rather than doing the work below to create a
+ // generic Scope only to junk it if it matches an existing instantiation.
+ const namespace = module_fn.owner_decl.namespace;
+ const fn_zir = namespace.file_scope.zir;
+ const fn_info = fn_zir.getFnInfo(module_fn.zir_body_inst);
+ const zir_tags = fn_zir.instructions.items(.tag);
+ const new_module_func = new_func: {
+ // This hash must match `Module.MonomorphedFuncsContext.hash`.
+ // For parameters explicitly marked comptime and simple parameter type expressions,
+ // we know whether a parameter is elided from a monomorphed function, and can
+ // use it in the hash here. However, for parameter type expressions that are not
+ // explicitly marked comptime and rely on previous parameter comptime values, we
+ // don't find out until after generating a monomorphed function whether the parameter
+ // type ended up being a "must-be-comptime-known" type.
+ var hasher = std.hash.Wyhash.init(0);
+ std.hash.autoHash(&hasher, @ptrToInt(module_fn));
+
+ const comptime_vals = try sema.arena.alloc(Value, func_ty_info.param_types.len);
+
+ for (func_ty_info.param_types) |param_ty, i| {
+ const is_comptime = func_ty_info.paramIsComptime(i);
+ if (is_comptime and param_ty.tag() != .generic_poison) {
+ const arg_src = call_src; // TODO better source location
+ const casted_arg = try sema.coerce(block, param_ty, uncasted_args[i], arg_src);
+ if (try sema.resolveMaybeUndefVal(block, arg_src, casted_arg)) |arg_val| {
+ arg_val.hash(param_ty, &hasher);
+ comptime_vals[i] = arg_val;
+ } else {
+ return sema.failWithNeededComptime(block, arg_src);
+ }
+ }
+ }
+
+ const adapter: GenericCallAdapter = .{
+ .generic_fn = module_fn,
+ .precomputed_hash = hasher.final(),
+ .func_ty_info = func_ty_info,
+ .comptime_vals = comptime_vals,
+ };
+ const gop = try mod.monomorphed_funcs.getOrPutAdapted(gpa, {}, adapter);
+ if (gop.found_existing) {
+ const callee_func = gop.key_ptr.*;
+ break :res try sema.finishGenericCall(
+ block,
+ call_src,
+ callee_func,
+ func_src,
+ uncasted_args,
+ fn_info,
+ zir_tags,
+ );
+ }
+ gop.key_ptr.* = try gpa.create(Module.Fn);
+ break :new_func gop.key_ptr.*;
+ };
+
+ {
+ try namespace.anon_decls.ensureUnusedCapacity(gpa, 1);
+
+ // Create a Decl for the new function.
+ const new_decl = try mod.allocateNewDecl(namespace, module_fn.owner_decl.src_node);
+ // TODO better names for generic function instantiations
+ const name_index = mod.getNextAnonNameIndex();
+ new_decl.name = try std.fmt.allocPrintZ(gpa, "{s}__anon_{d}", .{
+ module_fn.owner_decl.name, name_index,
+ });
+ new_decl.src_line = module_fn.owner_decl.src_line;
+ new_decl.is_pub = module_fn.owner_decl.is_pub;
+ new_decl.is_exported = module_fn.owner_decl.is_exported;
+ new_decl.has_align = module_fn.owner_decl.has_align;
+ new_decl.has_linksection = module_fn.owner_decl.has_linksection;
+ new_decl.zir_decl_index = module_fn.owner_decl.zir_decl_index;
+ new_decl.alive = true; // This Decl is called at runtime.
+ new_decl.has_tv = true;
+ new_decl.owns_tv = true;
+ new_decl.analysis = .in_progress;
+ new_decl.generation = mod.generation;
+
+ namespace.anon_decls.putAssumeCapacityNoClobber(new_decl, {});
+
+ var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
+ errdefer new_decl_arena.deinit();
+
+ // Re-run the block that creates the function, with the comptime parameters
+ // pre-populated inside `inst_map`. This causes `param_comptime` and
+ // `param_anytype_comptime` ZIR instructions to be ignored, resulting in a
+ // new, monomorphized function, with the comptime parameters elided.
+ var child_sema: Sema = .{
+ .mod = mod,
+ .gpa = gpa,
+ .arena = sema.arena,
+ .code = fn_zir,
+ .owner_decl = new_decl,
+ .namespace = namespace,
+ .func = null,
+ .owner_func = null,
+ .comptime_args = try new_decl_arena.allocator.alloc(TypedValue, uncasted_args.len),
+ .comptime_args_fn_inst = module_fn.zir_body_inst,
+ .preallocated_new_func = new_module_func,
+ };
+ defer child_sema.deinit();
+
+ var child_block: Scope.Block = .{
+ .parent = null,
+ .sema = &child_sema,
+ .src_decl = new_decl,
+ .instructions = .{},
+ .inlining = null,
+ .is_comptime = true,
+ };
+ defer {
+ child_block.instructions.deinit(gpa);
+ child_block.params.deinit(gpa);
+ }
+
+ try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, uncasted_args.len));
+ var arg_i: usize = 0;
+ for (fn_info.param_body) |inst| {
+ const is_comptime = switch (zir_tags[inst]) {
+ .param_comptime, .param_anytype_comptime => true,
+ .param, .param_anytype => false,
+ else => continue,
+ } or func_ty_info.paramIsComptime(arg_i);
+ const arg_src = call_src; // TODO: better source location
+ const arg = uncasted_args[arg_i];
+ if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| {
+ const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val);
+ child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg);
+ } else if (is_comptime) {
+ return sema.failWithNeededComptime(block, arg_src);
+ }
+ arg_i += 1;
+ }
+ const new_func_inst = try child_sema.resolveBody(&child_block, fn_info.param_body);
+ const new_func_val = try child_sema.resolveConstValue(&child_block, .unneeded, new_func_inst);
+ const new_func = new_func_val.castTag(.function).?.data;
+ assert(new_func == new_module_func);
+
+ arg_i = 0;
+ for (fn_info.param_body) |inst| {
+ switch (zir_tags[inst]) {
+ .param_comptime, .param_anytype_comptime, .param, .param_anytype => {},
+ else => continue,
+ }
+ const arg = child_sema.inst_map.get(inst).?;
+ const arg_val = (child_sema.resolveMaybeUndefValAllowVariables(&child_block, .unneeded, arg) catch unreachable).?;
+
+ if (arg_val.tag() == .generic_poison) {
+ child_sema.comptime_args[arg_i] = .{
+ .ty = Type.initTag(.noreturn),
+ .val = Value.initTag(.unreachable_value),
+ };
+ } else {
+ child_sema.comptime_args[arg_i] = .{
+ .ty = try child_sema.typeOf(arg).copy(&new_decl_arena.allocator),
+ .val = try arg_val.copy(&new_decl_arena.allocator),
+ };
+ }
+
+ arg_i += 1;
+ }
+
+ // Populate the Decl ty/val with the function and its type.
+ new_decl.ty = try child_sema.typeOf(new_func_inst).copy(&new_decl_arena.allocator);
+ new_decl.val = try Value.Tag.function.create(&new_decl_arena.allocator, new_func);
+ new_decl.analysis = .complete;
- const result = try sema.analyzeBlockBody(block, call_src, &child_block, merges);
+ // The generic function Decl is guaranteed to be the first dependency
+ // of each of its instantiations.
+ assert(new_decl.dependencies.keys().len == 0);
+ try mod.declareDeclDependency(new_decl, module_fn.owner_decl);
- break :res result;
+ // Queue up a `codegen_func` work item for the new Fn. The `comptime_args` field
+ // will be populated, ensuring it will have `analyzeBody` called with the ZIR
+ // parameters mapped appropriately.
+ try mod.comp.bin_file.allocateDeclIndexes(new_decl);
+ try mod.comp.work_queue.writeItem(.{ .codegen_func = new_func });
+
+ try new_decl.finalizeNewArena(&new_decl_arena);
+ }
+
+ break :res try sema.finishGenericCall(
+ block,
+ call_src,
+ new_module_func,
+ func_src,
+ uncasted_args,
+ fn_info,
+ zir_tags,
+ );
} else res: {
- if (func_ty.fnIsGeneric()) {
- return sema.mod.fail(&block.base, func_src, "TODO implement generic fn call", .{});
+ const args = try sema.arena.alloc(Air.Inst.Ref, uncasted_args.len);
+ for (uncasted_args) |uncasted_arg, i| {
+ if (i < fn_params_len) {
+ const param_ty = func_ty.fnParamType(i);
+ const arg_src = call_src; // TODO: better source location
+ args[i] = try sema.coerce(block, param_ty, uncasted_arg, arg_src);
+ } else {
+ args[i] = uncasted_arg;
+ }
}
+
try sema.requireRuntimeBlock(block, call_src);
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len +
args.len);
@@ -2561,6 +2787,75 @@ fn analyzeCall(
return result;
}
+fn finishGenericCall(
+ sema: *Sema,
+ block: *Scope.Block,
+ call_src: LazySrcLoc,
+ callee: *Module.Fn,
+ func_src: LazySrcLoc,
+ uncasted_args: []const Air.Inst.Ref,
+ fn_info: Zir.FnInfo,
+ zir_tags: []const Zir.Inst.Tag,
+) CompileError!Air.Inst.Ref {
+ const callee_inst = try sema.analyzeDeclVal(block, func_src, callee.owner_decl);
+
+ // Make a runtime call to the new function, making sure to omit the comptime args.
+ try sema.requireRuntimeBlock(block, call_src);
+
+ const comptime_args = callee.comptime_args.?;
+ const runtime_args_len = count: {
+ var count: u32 = 0;
+ var arg_i: usize = 0;
+ for (fn_info.param_body) |inst| {
+ switch (zir_tags[inst]) {
+ .param_comptime, .param_anytype_comptime, .param, .param_anytype => {
+ if (comptime_args[arg_i].val.tag() == .unreachable_value) {
+ count += 1;
+ }
+ arg_i += 1;
+ },
+ else => continue,
+ }
+ }
+ break :count count;
+ };
+ const runtime_args = try sema.arena.alloc(Air.Inst.Ref, runtime_args_len);
+ {
+ const new_fn_ty = callee.owner_decl.ty;
+ var runtime_i: u32 = 0;
+ var total_i: u32 = 0;
+ for (fn_info.param_body) |inst| {
+ switch (zir_tags[inst]) {
+ .param_comptime, .param_anytype_comptime, .param, .param_anytype => {},
+ else => continue,
+ }
+ const is_runtime = comptime_args[total_i].val.tag() == .unreachable_value;
+ if (is_runtime) {
+ const param_ty = new_fn_ty.fnParamType(runtime_i);
+ const arg_src = call_src; // TODO: better source location
+ const uncasted_arg = uncasted_args[total_i];
+ const casted_arg = try sema.coerce(block, param_ty, uncasted_arg, arg_src);
+ runtime_args[runtime_i] = casted_arg;
+ runtime_i += 1;
+ }
+ total_i += 1;
+ }
+ }
+ try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Call).Struct.fields.len +
+ runtime_args_len);
+ const func_inst = try block.addInst(.{
+ .tag = .call,
+ .data = .{ .pl_op = .{
+ .operand = callee_inst,
+ .payload = sema.addExtraAssumeCapacity(Air.Call{
+ .args_len = runtime_args_len,
+ }),
+ } },
+ });
+ sema.appendRefsAssumeCapacity(runtime_args);
+ return func_inst;
+}
+
fn zirIntType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
_ = block;
const tracy = trace(@src());
@@ -3186,13 +3481,15 @@ fn zirFunc(
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
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);
+ var extra_index = extra.end;
+ const ret_ty_body = sema.code.extra[extra_index..][0..extra.data.ret_body_len];
+ extra_index += ret_ty_body.len;
var body_inst: Zir.Inst.Index = 0;
var src_locs: Zir.Inst.Func.SrcLocs = undefined;
if (extra.data.body_len != 0) {
body_inst = inst;
- const extra_index = extra.end + extra.data.param_types_len + extra.data.body_len;
+ extra_index += extra.data.body_len;
src_locs = sema.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data;
}
@@ -3204,9 +3501,8 @@ fn zirFunc(
return sema.funcCommon(
block,
inst_data.src_node,
- param_types,
body_inst,
- extra.data.return_type,
+ ret_ty_body,
cc,
Value.initTag(.null_value),
false,
@@ -3214,7 +3510,6 @@ fn zirFunc(
false,
src_locs,
null,
- &.{},
);
}
@@ -3222,9 +3517,8 @@ fn funcCommon(
sema: *Sema,
block: *Scope.Block,
src_node_offset: i32,
- zir_param_types: []const Zir.Inst.Ref,
body_inst: Zir.Inst.Index,
- zir_return_type: Zir.Inst.Ref,
+ ret_ty_body: []const Zir.Inst.Index,
cc: std.builtin.CallingConvention,
align_val: Value,
var_args: bool,
@@ -3232,21 +3526,59 @@ fn funcCommon(
is_extern: bool,
src_locs: Zir.Inst.Func.SrcLocs,
opt_lib_name: ?[]const u8,
- comptime_bits: []const u32,
) CompileError!Air.Inst.Ref {
const src: LazySrcLoc = .{ .node_offset = src_node_offset };
const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = src_node_offset };
- const bare_return_type = try sema.resolveType(block, ret_ty_src, zir_return_type);
+
+ // The return type body might be a type expression that depends on generic parameters.
+ // In such case we need to use a generic_poison value for the return type and mark
+ // the function as generic.
+ var is_generic = false;
+ const bare_return_type: Type = ret_ty: {
+ if (ret_ty_body.len == 0) break :ret_ty Type.initTag(.void);
+
+ const err = err: {
+ // Make sure any nested param instructions don't clobber our work.
+ const prev_params = block.params;
+ block.params = .{};
+ defer {
+ block.params.deinit(sema.gpa);
+ block.params = prev_params;
+ }
+ if (sema.resolveBody(block, ret_ty_body)) |ret_ty_inst| {
+ if (sema.analyzeAsType(block, ret_ty_src, ret_ty_inst)) |ret_ty| {
+ break :ret_ty ret_ty;
+ } else |err| break :err err;
+ } else |err| break :err err;
+ };
+ switch (err) {
+ error.GenericPoison => {
+ // The type is not available until the generic instantiation.
+ is_generic = true;
+ break :ret_ty Type.initTag(.generic_poison);
+ },
+ else => |e| return e,
+ }
+ };
const mod = sema.mod;
- const new_func = if (body_inst == 0) undefined else try sema.gpa.create(Module.Fn);
+ const new_func: *Module.Fn = new_func: {
+ if (body_inst == 0) break :new_func undefined;
+ if (sema.comptime_args_fn_inst == body_inst) {
+ const new_func = sema.preallocated_new_func.?;
+ sema.preallocated_new_func = null; // take ownership
+ break :new_func new_func;
+ }
+ break :new_func try sema.gpa.create(Module.Fn);
+ };
errdefer if (body_inst != 0) sema.gpa.destroy(new_func);
const fn_ty: Type = fn_ty: {
// Hot path for some common function types.
- if (zir_param_types.len == 0 and !var_args and align_val.tag() == .null_value and
- !inferred_error_set)
+ // TODO can we eliminate some of these Type tag values? seems unnecessarily complicated.
+ if (!is_generic and block.params.items.len == 0 and !var_args and
+ align_val.tag() == .null_value and !inferred_error_set)
{
if (bare_return_type.zigTypeTag() == .NoReturn and cc == .Unspecified) {
break :fn_ty Type.initTag(.fn_noreturn_no_args);
@@ -3265,30 +3597,24 @@ fn funcCommon(
}
}
- var any_are_comptime = false;
- const param_types = try sema.arena.alloc(Type, zir_param_types.len);
- for (zir_param_types) |param_type, i| {
- // TODO make a compile error from `resolveType` report the source location
- // of the specific parameter. Will need to take a similar strategy as
- // `resolveSwitchItemVal` to avoid resolving the source location unless
- // we actually need to report an error.
- const param_src = src;
- param_types[i] = try sema.resolveType(block, param_src, param_type);
-
- any_are_comptime = any_are_comptime or blk: {
- if (comptime_bits.len == 0)
- break :blk false;
- const bag = comptime_bits[i / 32];
- const is_comptime = @truncate(u1, bag >> @intCast(u5, i % 32)) != 0;
- break :blk is_comptime;
- };
+ const param_types = try sema.arena.alloc(Type, block.params.items.len);
+ const comptime_params = try sema.arena.alloc(bool, block.params.items.len);
+ for (block.params.items) |param, i| {
+ param_types[i] = param.ty;
+ comptime_params[i] = param.is_comptime;
+ is_generic = is_generic or param.is_comptime or
+ param.ty.tag() == .generic_poison or param.ty.requiresComptime();
}
if (align_val.tag() != .null_value) {
return mod.fail(&block.base, src, "TODO implement support for function prototypes to have alignment specified", .{});
}
- const return_type = if (!inferred_error_set) bare_return_type else blk: {
+ is_generic = is_generic or bare_return_type.requiresComptime();
+
+ const return_type = if (!inferred_error_set or bare_return_type.tag() == .generic_poison)
+ bare_return_type
+ else blk: {
const error_set_ty = try Type.Tag.error_set_inferred.create(sema.arena, .{
.func = new_func,
.map = .{},
@@ -3301,10 +3627,11 @@ fn funcCommon(
break :fn_ty try Type.Tag.function.create(sema.arena, .{
.param_types = param_types,
+ .comptime_params = comptime_params.ptr,
.return_type = return_type,
.cc = cc,
.is_var_args = var_args,
- .is_generic = any_are_comptime,
+ .is_generic = is_generic,
});
};
@@ -3363,11 +3690,16 @@ fn funcCommon(
const is_inline = fn_ty.fnCallingConvention() == .Inline;
const anal_state: Module.Fn.Analysis = if (is_inline) .inline_only else .queued;
+ const comptime_args: ?[*]TypedValue = if (sema.comptime_args_fn_inst == body_inst) blk: {
+ break :blk if (sema.comptime_args.len == 0) null else sema.comptime_args.ptr;
+ } else null;
+
const fn_payload = try sema.arena.create(Value.Payload.Function);
new_func.* = .{
.state = anal_state,
.zir_body_inst = body_inst,
.owner_decl = sema.owner_decl,
+ .comptime_args = comptime_args,
.lbrace_line = src_locs.lbrace_line,
.rbrace_line = src_locs.rbrace_line,
.lbrace_column = @truncate(u16, src_locs.columns),
@@ -3380,6 +3712,113 @@ fn funcCommon(
return sema.addConstant(fn_ty, Value.initPayload(&fn_payload.base));
}
+fn zirParam(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: Zir.Inst.Index,
+ is_comptime: bool,
+) CompileError!void {
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_tok;
+ const src = inst_data.src();
+ const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index);
+ const param_name = sema.code.nullTerminatedString(extra.data.name);
+ const body = sema.code.extra[extra.end..][0..extra.data.body_len];
+
+ // TODO check if param_name shadows a Decl. This only needs to be done if
+ // usingnamespace is implemented.
+ _ = param_name;
+
+ // We could be in a generic function instantiation, or we could be evaluating a generic
+ // function without any comptime args provided.
+ const param_ty = param_ty: {
+ const err = err: {
+ // Make sure any nested param instructions don't clobber our work.
+ const prev_params = block.params;
+ block.params = .{};
+ defer {
+ block.params.deinit(sema.gpa);
+ block.params = prev_params;
+ }
+
+ if (sema.resolveBody(block, body)) |param_ty_inst| {
+ if (sema.analyzeAsType(block, src, param_ty_inst)) |param_ty| {
+ break :param_ty param_ty;
+ } else |err| break :err err;
+ } else |err| break :err err;
+ };
+ switch (err) {
+ error.GenericPoison => {
+ // The type is not available until the generic instantiation.
+ // We result the param instruction with a poison value and
+ // insert an anytype parameter.
+ try block.params.append(sema.gpa, .{
+ .ty = Type.initTag(.generic_poison),
+ .is_comptime = is_comptime,
+ });
+ try sema.inst_map.putNoClobber(sema.gpa, inst, .generic_poison);
+ return;
+ },
+ else => |e| return e,
+ }
+ };
+ if (sema.inst_map.get(inst)) |arg| {
+ if (is_comptime or param_ty.requiresComptime()) {
+ // We have a comptime value for this parameter so it should be elided from the
+ // function type of the function instruction in this block.
+ const coerced_arg = try sema.coerce(block, param_ty, arg, src);
+ sema.inst_map.putAssumeCapacity(inst, coerced_arg);
+ return;
+ }
+ // Even though a comptime argument is provided, the generic function wants to treat
+ // this as a runtime parameter.
+ assert(sema.inst_map.remove(inst));
+ }
+
+ try block.params.append(sema.gpa, .{
+ .ty = param_ty,
+ .is_comptime = is_comptime or param_ty.requiresComptime(),
+ });
+ const result = try sema.addConstant(param_ty, Value.initTag(.generic_poison));
+ try sema.inst_map.putNoClobber(sema.gpa, inst, result);
+}
+
+fn zirParamAnytype(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: Zir.Inst.Index,
+ is_comptime: bool,
+) CompileError!void {
+ const inst_data = sema.code.instructions.items(.data)[inst].str_tok;
+ const param_name = inst_data.get(sema.code);
+
+ // TODO check if param_name shadows a Decl. This only needs to be done if
+ // usingnamespace is implemented.
+ _ = param_name;
+
+ if (sema.inst_map.get(inst)) |air_ref| {
+ const param_ty = sema.typeOf(air_ref);
+ if (is_comptime or param_ty.requiresComptime()) {
+ // We have a comptime value for this parameter so it should be elided from the
+ // function type of the function instruction in this block.
+ return;
+ }
+ // The map is already populated but we do need to add a runtime parameter.
+ try block.params.append(sema.gpa, .{
+ .ty = param_ty,
+ .is_comptime = false,
+ });
+ return;
+ }
+
+ // We are evaluating a generic function without any comptime args provided.
+
+ try block.params.append(sema.gpa, .{
+ .ty = Type.initTag(.generic_poison),
+ .is_comptime = is_comptime,
+ });
+ try sema.inst_map.put(sema.gpa, inst, .generic_poison);
+}
+
fn zirAs(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
@@ -4898,18 +5337,18 @@ fn analyzeArithmetic(
) CompileError!Air.Inst.Ref {
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
- if (lhs_ty.zigTypeTag() == .Vector and rhs_ty.zigTypeTag() == .Vector) {
+ const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison();
+ const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison();
+ if (lhs_zig_ty_tag == .Vector and rhs_zig_ty_tag == .Vector) {
if (lhs_ty.arrayLen() != rhs_ty.arrayLen()) {
return sema.mod.fail(&block.base, src, "vector length mismatch: {d} and {d}", .{
- lhs_ty.arrayLen(),
- rhs_ty.arrayLen(),
+ lhs_ty.arrayLen(), rhs_ty.arrayLen(),
});
}
return sema.mod.fail(&block.base, src, "TODO implement support for vectors in zirBinOp", .{});
- } else if (lhs_ty.zigTypeTag() == .Vector or rhs_ty.zigTypeTag() == .Vector) {
+ } else if (lhs_zig_ty_tag == .Vector or rhs_zig_ty_tag == .Vector) {
return sema.mod.fail(&block.base, src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{
- lhs_ty,
- rhs_ty,
+ lhs_ty, rhs_ty,
});
}
@@ -4929,7 +5368,9 @@ fn analyzeArithmetic(
const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat;
if (!is_int and !(is_float and floatOpAllowed(zir_tag))) {
- return sema.mod.fail(&block.base, src, "invalid operands to binary expression: '{s}' and '{s}'", .{ @tagName(lhs_ty.zigTypeTag()), @tagName(rhs_ty.zigTypeTag()) });
+ return sema.mod.fail(&block.base, src, "invalid operands to binary expression: '{s}' and '{s}'", .{
+ @tagName(lhs_zig_ty_tag), @tagName(rhs_zig_ty_tag),
+ });
}
if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| {
@@ -5728,6 +6169,10 @@ fn analyzeRet(
const casted_operand = if (!need_coercion) operand else op: {
const func = sema.func.?;
const fn_ty = func.owner_decl.ty;
+ // TODO: In the case of a comptime/inline function call of a generic function,
+ // this needs to be the resolved return type based on the function parameter type
+ // expressions being evaluated with comptime arguments passed in. Otherwise, this
+ // ends up being .generic_poison and failing the comptime/inline function call analysis.
const fn_ret_ty = fn_ty.fnReturnType();
break :op try sema.coerce(block, fn_ret_ty, operand, src);
};
@@ -6545,15 +6990,8 @@ fn zirFuncExtended(
break :blk align_tv.val;
} else Value.initTag(.null_value);
- const comptime_bits: []const u32 = if (!small.has_comptime_bits) &.{} else blk: {
- const amt = (extra.data.param_types_len + 31) / 32;
- const bit_bags = sema.code.extra[extra_index..][0..amt];
- extra_index += amt;
- break :blk bit_bags;
- };
-
- const param_types = sema.code.refSlice(extra_index, extra.data.param_types_len);
- extra_index += param_types.len;
+ const ret_ty_body = sema.code.extra[extra_index..][0..extra.data.ret_body_len];
+ extra_index += ret_ty_body.len;
var body_inst: Zir.Inst.Index = 0;
var src_locs: Zir.Inst.Func.SrcLocs = undefined;
@@ -6570,9 +7008,8 @@ fn zirFuncExtended(
return sema.funcCommon(
block,
extra.data.src_node,
- param_types,
body_inst,
- extra.data.return_type,
+ ret_ty_body,
cc,
align_val,
is_var_args,
@@ -6580,7 +7017,6 @@ fn zirFuncExtended(
is_extern,
src_locs,
lib_name,
- comptime_bits,
);
}
@@ -6797,19 +7233,12 @@ fn safetyPanic(
const msg_inst = msg_inst: {
// TODO instead of making a new decl for every panic in the entire compilation,
// introduce the concept of a reference-counted decl for these
- var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
- errdefer new_decl_arena.deinit();
-
- const decl_ty = try Type.Tag.array_u8.create(&new_decl_arena.allocator, msg.len);
- const decl_val = try Value.Tag.bytes.create(&new_decl_arena.allocator, msg);
-
- const new_decl = try sema.mod.createAnonymousDecl(&block.base, .{
- .ty = decl_ty,
- .val = decl_val,
- });
- errdefer sema.mod.deleteAnonDecl(&block.base, new_decl);
- try new_decl.finalizeNewArena(&new_decl_arena);
- break :msg_inst try sema.analyzeDeclRef(new_decl);
+ var anon_decl = try block.startAnonDecl();
+ defer anon_decl.deinit();
+ break :msg_inst try sema.analyzeDeclRef(try anon_decl.finish(
+ try Type.Tag.array_u8.create(anon_decl.arena(), msg.len),
+ try Value.Tag.bytes.create(anon_decl.arena(), msg),
+ ));
};
const casted_msg_inst = try sema.coerce(block, Type.initTag(.const_slice_u8), msg_inst, src);
@@ -7469,8 +7898,10 @@ fn coerce(
inst: Air.Inst.Ref,
inst_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
- if (dest_type_unresolved.tag() == .var_args_param) {
- return sema.coerceVarArgParam(block, inst, inst_src);
+ switch (dest_type_unresolved.tag()) {
+ .var_args_param => return sema.coerceVarArgParam(block, inst, inst_src),
+ .generic_poison => return inst,
+ else => {},
}
const dest_type_src = inst_src; // TODO better source location
const dest_type = try sema.resolveTypeFields(block, dest_type_src, dest_type_unresolved);
@@ -8671,6 +9102,7 @@ fn typeHasOnePossibleValue(
.inferred_alloc_const => unreachable,
.inferred_alloc_mut => unreachable,
+ .generic_poison => return error.GenericPoison,
};
}
@@ -8793,6 +9225,8 @@ pub fn addType(sema: *Sema, ty: Type) !Air.Inst.Ref {
.fn_ccc_void_no_args => return .fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int => return .single_const_pointer_to_comptime_int_type,
.const_slice_u8 => return .const_slice_u8_type,
+ .anyerror_void_error_union => return .anyerror_void_error_union_type,
+ .generic_poison => return .generic_poison_type,
else => {},
}
try sema.air_instructions.append(sema.gpa, .{
@@ -8810,7 +9244,7 @@ fn addConstUndef(sema: *Sema, ty: Type) CompileError!Air.Inst.Ref {
return sema.addConstant(ty, Value.initTag(.undef));
}
-fn addConstant(sema: *Sema, ty: Type, val: Value) CompileError!Air.Inst.Ref {
+pub fn addConstant(sema: *Sema, ty: Type, val: Value) SemaError!Air.Inst.Ref {
const gpa = sema.gpa;
const ty_inst = try sema.addType(ty);
try sema.air_values.append(gpa, val);
diff --git a/src/Zir.zig b/src/Zir.zig
@@ -61,7 +61,7 @@ pub const ExtraIndex = enum(u32) {
_,
};
-pub fn getMainStruct(zir: Zir) Zir.Inst.Index {
+pub fn getMainStruct(zir: Zir) Inst.Index {
return zir.extra[@enumToInt(ExtraIndex.main_struct)] -
@intCast(u32, Inst.Ref.typed_value_map.len);
}
@@ -173,11 +173,22 @@ pub const Inst = struct {
/// Twos complement wrapping integer addition.
/// Uses the `pl_node` union field. Payload is `Bin`.
addwrap,
- /// Declares a parameter of the current function. Used for debug info and
- /// for checking shadowing against declarations in the current namespace.
- /// Uses the `str_tok` field. Token is the parameter name, string is the
- /// parameter name.
- arg,
+ /// Declares a parameter of the current function. Used for:
+ /// * debug info
+ /// * checking shadowing against declarations in the current namespace
+ /// * parameter type expressions referencing other parameters
+ /// These occur in the block outside a function body (the same block as
+ /// contains the func instruction).
+ /// Uses the `pl_tok` field. Token is the parameter name, payload is a `Param`.
+ param,
+ /// Same as `param` except the parameter is marked comptime.
+ param_comptime,
+ /// Same as `param` except the parameter is marked anytype.
+ /// Uses the `str_tok` field. Token is the parameter name. String is the parameter name.
+ param_anytype,
+ /// Same as `param` except the parameter is marked both comptime and anytype.
+ /// Uses the `str_tok` field. Token is the parameter name. String is the parameter name.
+ param_anytype_comptime,
/// Array concatenation. `a ++ b`
/// Uses the `pl_node` union field. Payload is `Bin`.
array_cat,
@@ -971,7 +982,10 @@ pub const Inst = struct {
/// Function calls do not count.
pub fn isNoReturn(tag: Tag) bool {
return switch (tag) {
- .arg,
+ .param,
+ .param_comptime,
+ .param_anytype,
+ .param_anytype_comptime,
.add,
.addwrap,
.alloc,
@@ -1233,7 +1247,10 @@ pub const Inst = struct {
break :list std.enums.directEnumArray(Tag, Data.FieldEnum, 0, .{
.add = .pl_node,
.addwrap = .pl_node,
- .arg = .str_tok,
+ .param = .pl_tok,
+ .param_comptime = .pl_tok,
+ .param_anytype = .str_tok,
+ .param_anytype_comptime = .str_tok,
.array_cat = .pl_node,
.array_mul = .pl_node,
.array_type = .bin,
@@ -1687,6 +1704,8 @@ pub const Inst = struct {
fn_ccc_void_no_args_type,
single_const_pointer_to_comptime_int_type,
const_slice_u8_type,
+ anyerror_void_error_union_type,
+ generic_poison_type,
/// `undefined` (untyped)
undef,
@@ -1714,6 +1733,9 @@ pub const Inst = struct {
calling_convention_c,
/// `std.builtin.CallingConvention.Inline`
calling_convention_inline,
+ /// Used for generic parameters where the type and value
+ /// is not known until generic function instantiation.
+ generic_poison,
_,
@@ -1892,6 +1914,14 @@ pub const Inst = struct {
.ty = Type.initTag(.type),
.val = Value.initTag(.const_slice_u8_type),
},
+ .anyerror_void_error_union_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.anyerror_void_error_union_type),
+ },
+ .generic_poison_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.generic_poison_type),
+ },
.enum_literal_type = .{
.ty = Type.initTag(.type),
.val = Value.initTag(.enum_literal_type),
@@ -1989,6 +2019,10 @@ pub const Inst = struct {
.ty = Type.initTag(.calling_convention),
.val = .{ .ptr_otherwise = &calling_convention_inline_payload.base },
},
+ .generic_poison = .{
+ .ty = Type.initTag(.generic_poison),
+ .val = Value.initTag(.generic_poison),
+ },
});
};
@@ -2047,6 +2081,17 @@ pub const Inst = struct {
return .{ .node_offset = self.src_node };
}
},
+ pl_tok: struct {
+ /// Offset from Decl AST token index.
+ src_tok: ast.TokenIndex,
+ /// index into extra.
+ /// `Tag` determines what lives there.
+ payload_index: u32,
+
+ pub fn src(self: @This()) LazySrcLoc {
+ return .{ .token_offset = self.src_tok };
+ }
+ },
bin: Bin,
/// For strings which may contain null bytes.
str: struct {
@@ -2170,6 +2215,7 @@ pub const Inst = struct {
un_node,
un_tok,
pl_node,
+ pl_tok,
bin,
str,
str_tok,
@@ -2226,17 +2272,15 @@ pub const Inst = struct {
/// 0. lib_name: u32, // null terminated string index, if has_lib_name is set
/// 1. cc: Ref, // if has_cc is set
/// 2. align: Ref, // if has_align is set
- /// 3. comptime_bits: u32 // for every 32 parameters, if has_comptime_bits is set
- /// - sets of 1 bit:
- /// 0bX: whether corresponding parameter is comptime
- /// 4. param_type: Ref // for each param_types_len
- /// - `none` indicates that the param type is `anytype`.
- /// 5. body: Index // for each body_len
- /// 6. src_locs: Func.SrcLocs // if body_len != 0
+ /// 3. return_type: Index // for each ret_body_len
+ /// 4. body: Index // for each body_len
+ /// 5. src_locs: Func.SrcLocs // if body_len != 0
pub const ExtendedFunc = struct {
src_node: i32,
- return_type: Ref,
- param_types_len: u32,
+ /// If this is 0 it means a void return type.
+ ret_body_len: u32,
+ /// Points to the block that contains the param instructions for this function.
+ param_block: Index,
body_len: u32,
pub const Small = packed struct {
@@ -2247,8 +2291,7 @@ pub const Inst = struct {
has_align: bool,
is_test: bool,
is_extern: bool,
- has_comptime_bits: bool,
- _: u8 = undefined,
+ _: u9 = undefined,
};
};
@@ -2271,13 +2314,14 @@ pub const Inst = struct {
};
/// Trailing:
- /// 0. param_type: Ref // for each param_types_len
- /// - `none` indicates that the param type is `anytype`.
+ /// 0. return_type: Index // for each ret_body_len
/// 1. body: Index // for each body_len
/// 2. src_locs: SrcLocs // if body_len != 0
pub const Func = struct {
- return_type: Ref,
- param_types_len: u32,
+ /// If this is 0 it means a void return type.
+ ret_body_len: u32,
+ /// Points to the block that contains the param instructions for this function.
+ param_block: Index,
body_len: u32,
pub const SrcLocs = struct {
@@ -2764,6 +2808,14 @@ pub const Inst = struct {
args: Ref,
};
+ /// Trailing: inst: Index // for every body_len
+ pub const Param = struct {
+ /// Null-terminated string index.
+ name: u32,
+ /// The body contains the type of the parameter.
+ body_len: u32,
+ };
+
/// Trailing:
/// 0. type_inst: Ref, // if small 0b000X is set
/// 1. align_inst: Ref, // if small 0b00X0 is set
@@ -3108,11 +3160,14 @@ const Writer = struct {
.decl_ref,
.decl_val,
.import,
- .arg,
.ret_err_value,
.ret_err_value_code,
+ .param_anytype,
+ .param_anytype_comptime,
=> try self.writeStrTok(stream, inst),
+ .param, .param_comptime => try self.writeParam(stream, inst),
+
.func => try self.writeFunc(stream, inst, false),
.func_inferred => try self.writeFunc(stream, inst, true),
@@ -3314,6 +3369,22 @@ const Writer = struct {
try self.writeSrc(stream, inst_data.src());
}
+ fn writeParam(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_tok;
+ const extra = self.code.extraData(Inst.Param, inst_data.payload_index);
+ const body = self.code.extra[extra.end..][0..extra.data.body_len];
+ try stream.print("\"{}\", ", .{
+ std.zig.fmtEscapes(self.code.nullTerminatedString(extra.data.name)),
+ });
+ try stream.writeAll("{\n");
+ self.indent += 2;
+ try self.writeBody(stream, body);
+ self.indent -= 2;
+ try stream.writeByteNTimes(' ', self.indent);
+ try stream.writeAll(") ");
+ try self.writeSrc(stream, inst_data.src());
+ }
+
fn writePlNodeBin(self: *Writer, stream: anytype, inst: Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const extra = self.code.extraData(Inst.Bin, inst_data.payload_index).data;
@@ -4277,17 +4348,21 @@ const Writer = struct {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
const extra = self.code.extraData(Inst.Func, inst_data.payload_index);
- const param_types = self.code.refSlice(extra.end, extra.data.param_types_len);
- const body = self.code.extra[extra.end + param_types.len ..][0..extra.data.body_len];
+ var extra_index = extra.end;
+
+ const ret_ty_body = self.code.extra[extra_index..][0..extra.data.ret_body_len];
+ extra_index += ret_ty_body.len;
+
+ const body = self.code.extra[extra_index..][0..extra.data.body_len];
+ extra_index += body.len;
+
var src_locs: Zir.Inst.Func.SrcLocs = undefined;
if (body.len != 0) {
- const extra_index = extra.end + param_types.len + body.len;
src_locs = self.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data;
}
return self.writeFuncCommon(
stream,
- param_types,
- extra.data.return_type,
+ ret_ty_body,
inferred_error_set,
false,
false,
@@ -4296,7 +4371,6 @@ const Writer = struct {
body,
src,
src_locs,
- &.{},
);
}
@@ -4323,15 +4397,8 @@ const Writer = struct {
break :blk align_inst;
};
- const comptime_bits: []const u32 = if (!small.has_comptime_bits) &.{} else blk: {
- const amt = (extra.data.param_types_len + 31) / 32;
- const bit_bags = self.code.extra[extra_index..][0..amt];
- extra_index += amt;
- break :blk bit_bags;
- };
-
- const param_types = self.code.refSlice(extra_index, extra.data.param_types_len);
- extra_index += param_types.len;
+ const ret_ty_body = self.code.extra[extra_index..][0..extra.data.ret_body_len];
+ extra_index += ret_ty_body.len;
const body = self.code.extra[extra_index..][0..extra.data.body_len];
extra_index += body.len;
@@ -4342,8 +4409,7 @@ const Writer = struct {
}
return self.writeFuncCommon(
stream,
- param_types,
- extra.data.return_type,
+ ret_ty_body,
small.is_inferred_error,
small.is_var_args,
small.is_extern,
@@ -4352,7 +4418,6 @@ const Writer = struct {
body,
src,
src_locs,
- comptime_bits,
);
}
@@ -4426,8 +4491,7 @@ const Writer = struct {
fn writeFuncCommon(
self: *Writer,
stream: anytype,
- param_types: []const Inst.Ref,
- ret_ty: Inst.Ref,
+ ret_ty_body: []const Inst.Index,
inferred_error_set: bool,
var_args: bool,
is_extern: bool,
@@ -4436,20 +4500,18 @@ const Writer = struct {
body: []const Inst.Index,
src: LazySrcLoc,
src_locs: Zir.Inst.Func.SrcLocs,
- comptime_bits: []const u32,
) !void {
- try stream.writeAll("[");
- for (param_types) |param_type, i| {
- if (i != 0) try stream.writeAll(", ");
- if (comptime_bits.len != 0) {
- const bag = comptime_bits[i / 32];
- const is_comptime = @truncate(u1, bag >> @intCast(u5, i % 32)) != 0;
- try self.writeFlag(stream, "comptime ", is_comptime);
- }
- try self.writeInstRef(stream, param_type);
+ if (ret_ty_body.len == 0) {
+ try stream.writeAll("ret_ty=void");
+ } else {
+ try stream.writeAll("ret_ty={\n");
+ self.indent += 2;
+ try self.writeBody(stream, ret_ty_body);
+ self.indent -= 2;
+ try stream.writeByteNTimes(' ', self.indent);
+ try stream.writeAll("}");
}
- try stream.writeAll("], ");
- try self.writeInstRef(stream, ret_ty);
+
try self.writeOptionalInstRef(stream, ", cc=", cc);
try self.writeOptionalInstRef(stream, ", align=", align_inst);
try self.writeFlag(stream, ", vargs", var_args);
@@ -4457,9 +4519,9 @@ const Writer = struct {
try self.writeFlag(stream, ", inferror", inferred_error_set);
if (body.len == 0) {
- try stream.writeAll(", {}) ");
+ try stream.writeAll(", body={}) ");
} else {
- try stream.writeAll(", {\n");
+ try stream.writeAll(", body={\n");
self.indent += 2;
try self.writeBody(stream, body);
self.indent -= 2;
@@ -4714,8 +4776,7 @@ fn findDeclsInner(
const inst_data = datas[inst].pl_node;
const extra = zir.extraData(Inst.Func, inst_data.payload_index);
- const param_types_len = extra.data.param_types_len;
- const body = zir.extra[extra.end + param_types_len ..][0..extra.data.body_len];
+ const body = zir.extra[extra.end..][0..extra.data.body_len];
return zir.findDeclsBody(list, body);
},
.extended => {
@@ -4730,7 +4791,6 @@ fn findDeclsInner(
extra_index += @boolToInt(small.has_lib_name);
extra_index += @boolToInt(small.has_cc);
extra_index += @boolToInt(small.has_align);
- extra_index += extra.data.param_types_len;
const body = zir.extra[extra_index..][0..extra.data.body_len];
return zir.findDeclsBody(list, body);
},
@@ -4885,10 +4945,83 @@ fn findDeclsSwitchMulti(
fn findDeclsBody(
zir: Zir,
- list: *std.ArrayList(Zir.Inst.Index),
- body: []const Zir.Inst.Index,
+ list: *std.ArrayList(Inst.Index),
+ body: []const Inst.Index,
) Allocator.Error!void {
for (body) |member| {
try zir.findDeclsInner(list, member);
}
}
+
+pub const FnInfo = struct {
+ param_body: []const Inst.Index,
+ ret_ty_body: []const Inst.Index,
+ body: []const Inst.Index,
+ total_params_len: u32,
+};
+
+pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo {
+ const tags = zir.instructions.items(.tag);
+ const datas = zir.instructions.items(.data);
+ const info: struct {
+ param_block: Inst.Index,
+ body: []const Inst.Index,
+ ret_ty_body: []const Inst.Index,
+ } = switch (tags[fn_inst]) {
+ .func, .func_inferred => blk: {
+ const inst_data = datas[fn_inst].pl_node;
+ const extra = zir.extraData(Inst.Func, inst_data.payload_index);
+ var extra_index: usize = extra.end;
+
+ const ret_ty_body = zir.extra[extra_index..][0..extra.data.ret_body_len];
+ extra_index += ret_ty_body.len;
+
+ const body = zir.extra[extra_index..][0..extra.data.body_len];
+ extra_index += body.len;
+
+ break :blk .{
+ .param_block = extra.data.param_block,
+ .ret_ty_body = ret_ty_body,
+ .body = body,
+ };
+ },
+ .extended => blk: {
+ const extended = datas[fn_inst].extended;
+ assert(extended.opcode == .func);
+ const extra = zir.extraData(Inst.ExtendedFunc, extended.operand);
+ const small = @bitCast(Inst.ExtendedFunc.Small, extended.small);
+ var extra_index: usize = extra.end;
+ extra_index += @boolToInt(small.has_lib_name);
+ extra_index += @boolToInt(small.has_cc);
+ extra_index += @boolToInt(small.has_align);
+ const ret_ty_body = zir.extra[extra_index..][0..extra.data.ret_body_len];
+ extra_index += ret_ty_body.len;
+ const body = zir.extra[extra_index..][0..extra.data.body_len];
+ extra_index += body.len;
+ break :blk .{
+ .param_block = extra.data.param_block,
+ .ret_ty_body = ret_ty_body,
+ .body = body,
+ };
+ },
+ else => unreachable,
+ };
+ assert(tags[info.param_block] == .block or tags[info.param_block] == .block_inline);
+ const param_block = zir.extraData(Inst.Block, datas[info.param_block].pl_node.payload_index);
+ const param_body = zir.extra[param_block.end..][0..param_block.data.body_len];
+ var total_params_len: u32 = 0;
+ for (param_body) |inst| {
+ switch (tags[inst]) {
+ .param, .param_comptime, .param_anytype, .param_anytype_comptime => {
+ total_params_len += 1;
+ },
+ else => continue,
+ }
+ }
+ return .{
+ .param_body = param_body,
+ .ret_ty_body = info.ret_ty_body,
+ .body = info.body,
+ .total_params_len = total_params_len,
+ };
+}
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
@@ -575,6 +575,14 @@ pub const DeclGen = struct {
const info = t.intInfo(self.module.getTarget());
return self.context.intType(info.bits);
},
+ .Float => switch (t.floatBits(self.module.getTarget())) {
+ 16 => return self.context.halfType(),
+ 32 => return self.context.floatType(),
+ 64 => return self.context.doubleType(),
+ 80 => return self.context.x86FP80Type(),
+ 128 => return self.context.fp128Type(),
+ else => unreachable,
+ },
.Bool => return self.context.intType(1),
.Pointer => {
if (t.isSlice()) {
@@ -661,7 +669,6 @@ pub const DeclGen = struct {
.BoundFn => @panic("TODO remove BoundFn from the language"),
- .Float,
.Enum,
.Union,
.Opaque,
@@ -699,13 +706,40 @@ pub const DeclGen = struct {
}
return llvm_int;
},
+ .Float => {
+ if (tv.ty.floatBits(self.module.getTarget()) <= 64) {
+ const llvm_ty = try self.llvmType(tv.ty);
+ return llvm_ty.constReal(tv.val.toFloat(f64));
+ }
+ return self.todo("bitcast to f128 from an integer", .{});
+ },
.Pointer => switch (tv.val.tag()) {
.decl_ref => {
- const decl = tv.val.castTag(.decl_ref).?.data;
- decl.alive = true;
- const val = try self.resolveGlobalDecl(decl);
- const llvm_type = try self.llvmType(tv.ty);
- return val.constBitCast(llvm_type);
+ if (tv.ty.isSlice()) {
+ var buf: Type.Payload.ElemType = undefined;
+ const ptr_ty = tv.ty.slicePtrFieldType(&buf);
+ var slice_len: Value.Payload.U64 = .{
+ .base = .{ .tag = .int_u64 },
+ .data = tv.val.sliceLen(),
+ };
+ const fields: [2]*const llvm.Value = .{
+ try self.genTypedValue(.{
+ .ty = ptr_ty,
+ .val = tv.val,
+ }),
+ try self.genTypedValue(.{
+ .ty = Type.initTag(.usize),
+ .val = Value.initPayload(&slice_len.base),
+ }),
+ };
+ return self.context.constStruct(&fields, fields.len, .False);
+ } else {
+ const decl = tv.val.castTag(.decl_ref).?.data;
+ decl.alive = true;
+ const val = try self.resolveGlobalDecl(decl);
+ const llvm_type = try self.llvmType(tv.ty);
+ return val.constBitCast(llvm_type);
+ }
},
.variable => {
const decl = tv.val.castTag(.variable).?.data.owner_decl;
@@ -839,6 +873,10 @@ pub const DeclGen = struct {
.False,
);
},
+ .ComptimeInt => unreachable,
+ .ComptimeFloat => unreachable,
+ .Type => unreachable,
+ .EnumLiteral => unreachable,
else => return self.todo("implement const of type '{}'", .{tv.ty}),
}
}
diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig
@@ -31,6 +31,21 @@ pub const Context = opaque {
pub const intType = LLVMIntTypeInContext;
extern fn LLVMIntTypeInContext(C: *const Context, NumBits: c_uint) *const Type;
+ pub const halfType = LLVMHalfTypeInContext;
+ extern fn LLVMHalfTypeInContext(C: *const Context) *const Type;
+
+ pub const floatType = LLVMFloatTypeInContext;
+ extern fn LLVMFloatTypeInContext(C: *const Context) *const Type;
+
+ pub const doubleType = LLVMDoubleTypeInContext;
+ extern fn LLVMDoubleTypeInContext(C: *const Context) *const Type;
+
+ pub const x86FP80Type = LLVMX86FP80TypeInContext;
+ extern fn LLVMX86FP80TypeInContext(C: *const Context) *const Type;
+
+ pub const fp128Type = LLVMFP128TypeInContext;
+ extern fn LLVMFP128TypeInContext(C: *const Context) *const Type;
+
pub const voidType = LLVMVoidTypeInContext;
extern fn LLVMVoidTypeInContext(C: *const Context) *const Type;
@@ -127,6 +142,9 @@ pub const Type = opaque {
pub const constInt = LLVMConstInt;
extern fn LLVMConstInt(IntTy: *const Type, N: c_ulonglong, SignExtend: Bool) *const Value;
+ pub const constReal = LLVMConstReal;
+ extern fn LLVMConstReal(RealTy: *const Type, N: f64) *const Value;
+
pub const constArray = LLVMConstArray;
extern fn LLVMConstArray(ElementTy: *const Type, ConstantVals: [*]*const Value, Length: c_uint) *const Value;
diff --git a/src/print_air.zig b/src/print_air.zig
@@ -222,7 +222,7 @@ const Writer = struct {
const extra = w.air.extraData(Air.Block, ty_pl.payload);
const body = w.air.extra[extra.end..][0..extra.data.body_len];
- try s.writeAll("{\n");
+ try s.print("{}, {{\n", .{w.air.getRefType(ty_pl.ty)});
const old_indent = w.indent;
w.indent += 2;
try w.writeBody(s, body);
diff --git a/src/type.zig b/src/type.zig
@@ -21,8 +21,14 @@ pub const Type = extern union {
tag_if_small_enough: usize,
ptr_otherwise: *Payload,
- pub fn zigTypeTag(self: Type) std.builtin.TypeId {
- switch (self.tag()) {
+ pub fn zigTypeTag(ty: Type) std.builtin.TypeId {
+ return ty.zigTypeTagOrPoison() catch unreachable;
+ }
+
+ pub fn zigTypeTagOrPoison(ty: Type) error{GenericPoison}!std.builtin.TypeId {
+ switch (ty.tag()) {
+ .generic_poison => return error.GenericPoison,
+
.u1,
.u8,
.i8,
@@ -548,8 +554,13 @@ pub const Type = extern union {
pub fn hash(self: Type) u64 {
var hasher = std.hash.Wyhash.init(0);
+ self.hashWithHasher(&hasher);
+ return hasher.final();
+ }
+
+ pub fn hashWithHasher(self: Type, hasher: *std.hash.Wyhash) void {
const zig_type_tag = self.zigTypeTag();
- std.hash.autoHash(&hasher, zig_type_tag);
+ std.hash.autoHash(hasher, zig_type_tag);
switch (zig_type_tag) {
.Type,
.Void,
@@ -567,34 +578,34 @@ pub const Type = extern union {
.Int => {
// Detect that e.g. u64 != usize, even if the bits match on a particular target.
if (self.isNamedInt()) {
- std.hash.autoHash(&hasher, self.tag());
+ std.hash.autoHash(hasher, self.tag());
} else {
// Remaining cases are arbitrary sized integers.
// The target will not be branched upon, because we handled target-dependent cases above.
const info = self.intInfo(@as(Target, undefined));
- std.hash.autoHash(&hasher, info.signedness);
- std.hash.autoHash(&hasher, info.bits);
+ std.hash.autoHash(hasher, info.signedness);
+ std.hash.autoHash(hasher, info.bits);
}
},
.Array, .Vector => {
- std.hash.autoHash(&hasher, self.arrayLen());
- std.hash.autoHash(&hasher, self.elemType().hash());
+ std.hash.autoHash(hasher, self.arrayLen());
+ std.hash.autoHash(hasher, self.elemType().hash());
// TODO hash array sentinel
},
.Fn => {
- std.hash.autoHash(&hasher, self.fnReturnType().hash());
- std.hash.autoHash(&hasher, self.fnCallingConvention());
+ std.hash.autoHash(hasher, self.fnReturnType().hash());
+ std.hash.autoHash(hasher, self.fnCallingConvention());
const params_len = self.fnParamLen();
- std.hash.autoHash(&hasher, params_len);
+ std.hash.autoHash(hasher, params_len);
var i: usize = 0;
while (i < params_len) : (i += 1) {
- std.hash.autoHash(&hasher, self.fnParamType(i).hash());
+ std.hash.autoHash(hasher, self.fnParamType(i).hash());
}
- std.hash.autoHash(&hasher, self.fnIsVarArgs());
+ std.hash.autoHash(hasher, self.fnIsVarArgs());
},
.Optional => {
var buf: Payload.ElemType = undefined;
- std.hash.autoHash(&hasher, self.optionalChild(&buf).hash());
+ std.hash.autoHash(hasher, self.optionalChild(&buf).hash());
},
.Float,
.Struct,
@@ -611,7 +622,6 @@ pub const Type = extern union {
// TODO implement more type hashing
},
}
- return hasher.final();
}
pub const HashContext64 = struct {
@@ -699,6 +709,7 @@ pub const Type = extern union {
.export_options,
.extern_options,
.@"anyframe",
+ .generic_poison,
=> unreachable,
.array_u8,
@@ -759,12 +770,15 @@ pub const Type = extern union {
for (payload.param_types) |param_type, i| {
param_types[i] = try param_type.copy(allocator);
}
+ const other_comptime_params = payload.comptime_params[0..payload.param_types.len];
+ const comptime_params = try allocator.dupe(bool, other_comptime_params);
return Tag.function.create(allocator, .{
.return_type = try payload.return_type.copy(allocator),
.param_types = param_types,
.cc = payload.cc,
.is_var_args = payload.is_var_args,
.is_generic = payload.is_generic,
+ .comptime_params = comptime_params.ptr,
});
},
.pointer => {
@@ -1080,11 +1094,118 @@ pub const Type = extern union {
},
.inferred_alloc_const => return writer.writeAll("(inferred_alloc_const)"),
.inferred_alloc_mut => return writer.writeAll("(inferred_alloc_mut)"),
+ .generic_poison => return writer.writeAll("(generic poison)"),
}
unreachable;
}
}
+ /// Anything that reports hasCodeGenBits() false returns false here as well.
+ /// `generic_poison` will return false.
+ pub fn requiresComptime(ty: Type) bool {
+ return switch (ty.tag()) {
+ .u1,
+ .u8,
+ .i8,
+ .u16,
+ .i16,
+ .u32,
+ .i32,
+ .u64,
+ .i64,
+ .u128,
+ .i128,
+ .usize,
+ .isize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ .c_longdouble,
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_void,
+ .bool,
+ .void,
+ .anyerror,
+ .noreturn,
+ .@"anyframe",
+ .@"null",
+ .@"undefined",
+ .atomic_ordering,
+ .atomic_rmw_op,
+ .calling_convention,
+ .float_mode,
+ .reduce_op,
+ .call_options,
+ .export_options,
+ .extern_options,
+ .manyptr_u8,
+ .manyptr_const_u8,
+ .fn_noreturn_no_args,
+ .fn_void_no_args,
+ .fn_naked_noreturn_no_args,
+ .fn_ccc_void_no_args,
+ .single_const_pointer_to_comptime_int,
+ .const_slice_u8,
+ .anyerror_void_error_union,
+ .empty_struct_literal,
+ .function,
+ .empty_struct,
+ .error_set,
+ .error_set_single,
+ .error_set_inferred,
+ .@"opaque",
+ .generic_poison,
+ => false,
+
+ .type,
+ .comptime_int,
+ .comptime_float,
+ .enum_literal,
+ => true,
+
+ .var_args_param => unreachable,
+ .inferred_alloc_mut => unreachable,
+ .inferred_alloc_const => unreachable,
+
+ .array_u8,
+ .array_u8_sentinel_0,
+ .array,
+ .array_sentinel,
+ .vector,
+ .pointer,
+ .single_const_pointer,
+ .single_mut_pointer,
+ .many_const_pointer,
+ .many_mut_pointer,
+ .c_const_pointer,
+ .c_mut_pointer,
+ .const_slice,
+ .mut_slice,
+ .int_signed,
+ .int_unsigned,
+ .optional,
+ .optional_single_mut_pointer,
+ .optional_single_const_pointer,
+ .error_union,
+ .anyframe_T,
+ .@"struct",
+ .@"union",
+ .union_tagged,
+ .enum_simple,
+ .enum_full,
+ .enum_nonexhaustive,
+ => false, // TODO some of these should be `true` depending on their child types
+ };
+ }
+
pub fn toValue(self: Type, allocator: *Allocator) Allocator.Error!Value {
switch (self.tag()) {
.u1 => return Value.initTag(.u1_type),
@@ -1179,7 +1300,6 @@ pub const Type = extern union {
.fn_void_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
- .function,
.single_const_pointer_to_comptime_int,
.const_slice_u8,
.array_u8_sentinel_0,
@@ -1204,6 +1324,8 @@ pub const Type = extern union {
.anyframe_T,
=> true,
+ .function => !self.castTag(.function).?.data.is_generic,
+
.@"struct" => {
// TODO introduce lazy value mechanism
const struct_obj = self.castTag(.@"struct").?.data;
@@ -1283,6 +1405,7 @@ pub const Type = extern union {
.inferred_alloc_const => unreachable,
.inferred_alloc_mut => unreachable,
.var_args_param => unreachable,
+ .generic_poison => unreachable,
};
}
@@ -1505,6 +1628,8 @@ pub const Type = extern union {
.@"opaque",
.var_args_param,
=> unreachable,
+
+ .generic_poison => unreachable,
};
}
@@ -1532,6 +1657,7 @@ pub const Type = extern union {
.inferred_alloc_mut => unreachable,
.@"opaque" => unreachable,
.var_args_param => unreachable,
+ .generic_poison => unreachable,
.@"struct" => {
const s = self.castTag(.@"struct").?.data;
@@ -1698,6 +1824,7 @@ pub const Type = extern union {
.inferred_alloc_mut => unreachable,
.@"opaque" => unreachable,
.var_args_param => unreachable,
+ .generic_poison => unreachable,
.@"struct" => {
@panic("TODO bitSize struct");
@@ -2408,14 +2535,41 @@ pub const Type = extern union {
};
}
- /// Asserts the type is a function.
- pub fn fnIsGeneric(self: Type) bool {
- return switch (self.tag()) {
- .fn_noreturn_no_args => false,
- .fn_void_no_args => false,
- .fn_naked_noreturn_no_args => false,
- .fn_ccc_void_no_args => false,
- .function => self.castTag(.function).?.data.is_generic,
+ pub fn fnInfo(ty: Type) Payload.Function.Data {
+ return switch (ty.tag()) {
+ .fn_noreturn_no_args => .{
+ .param_types = &.{},
+ .comptime_params = undefined,
+ .return_type = initTag(.noreturn),
+ .cc = .Unspecified,
+ .is_var_args = false,
+ .is_generic = false,
+ },
+ .fn_void_no_args => .{
+ .param_types = &.{},
+ .comptime_params = undefined,
+ .return_type = initTag(.void),
+ .cc = .Unspecified,
+ .is_var_args = false,
+ .is_generic = false,
+ },
+ .fn_naked_noreturn_no_args => .{
+ .param_types = &.{},
+ .comptime_params = undefined,
+ .return_type = initTag(.noreturn),
+ .cc = .Naked,
+ .is_var_args = false,
+ .is_generic = false,
+ },
+ .fn_ccc_void_no_args => .{
+ .param_types = &.{},
+ .comptime_params = undefined,
+ .return_type = initTag(.void),
+ .cc = .C,
+ .is_var_args = false,
+ .is_generic = false,
+ },
+ .function => ty.castTag(.function).?.data,
else => unreachable,
};
@@ -2595,6 +2749,7 @@ pub const Type = extern union {
.inferred_alloc_const => unreachable,
.inferred_alloc_mut => unreachable,
+ .generic_poison => unreachable,
};
}
@@ -3008,6 +3163,7 @@ pub const Type = extern union {
single_const_pointer_to_comptime_int,
const_slice_u8,
anyerror_void_error_union,
+ generic_poison,
/// 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,
@@ -3105,6 +3261,7 @@ pub const Type = extern union {
.single_const_pointer_to_comptime_int,
.anyerror_void_error_union,
.const_slice_u8,
+ .generic_poison,
.inferred_alloc_const,
.inferred_alloc_mut,
.var_args_param,
@@ -3223,13 +3380,23 @@ pub const Type = extern union {
pub const base_tag = Tag.function;
base: Payload = Payload{ .tag = base_tag },
- data: struct {
+ data: Data,
+
+ // TODO look into optimizing this memory to take fewer bytes
+ pub const Data = struct {
param_types: []Type,
+ comptime_params: [*]bool,
return_type: Type,
cc: std.builtin.CallingConvention,
is_var_args: bool,
is_generic: bool,
- },
+
+ pub fn paramIsComptime(self: @This(), i: usize) bool {
+ if (!self.is_generic) return false;
+ assert(i < self.param_types.len);
+ return self.comptime_params[i];
+ }
+ };
};
pub const ErrorSet = struct {
diff --git a/src/value.zig b/src/value.zig
@@ -76,6 +76,8 @@ pub const Value = extern union {
fn_ccc_void_no_args_type,
single_const_pointer_to_comptime_int_type,
const_slice_u8_type,
+ anyerror_void_error_union_type,
+ generic_poison_type,
undef,
zero,
@@ -85,6 +87,7 @@ pub const Value = extern union {
null_value,
bool_true,
bool_false,
+ generic_poison,
abi_align_default,
empty_struct_value,
@@ -188,6 +191,8 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.anyframe_type,
.const_slice_u8_type,
+ .anyerror_void_error_union_type,
+ .generic_poison_type,
.enum_literal_type,
.undef,
.zero,
@@ -210,6 +215,7 @@ pub const Value = extern union {
.call_options_type,
.export_options_type,
.extern_options_type,
+ .generic_poison,
=> @compileError("Value Tag " ++ @tagName(t) ++ " has no payload"),
.int_big_positive,
@@ -366,6 +372,8 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.anyframe_type,
.const_slice_u8_type,
+ .anyerror_void_error_union_type,
+ .generic_poison_type,
.enum_literal_type,
.undef,
.zero,
@@ -388,6 +396,7 @@ pub const Value = extern union {
.call_options_type,
.export_options_type,
.extern_options_type,
+ .generic_poison,
=> unreachable,
.ty => {
@@ -556,6 +565,9 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"),
.anyframe_type => return out_stream.writeAll("anyframe"),
.const_slice_u8_type => return out_stream.writeAll("[]const u8"),
+ .anyerror_void_error_union_type => return out_stream.writeAll("anyerror!void"),
+ .generic_poison_type => return out_stream.writeAll("(generic poison type)"),
+ .generic_poison => return out_stream.writeAll("(generic poison)"),
.enum_literal_type => return out_stream.writeAll("@Type(.EnumLiteral)"),
.manyptr_u8_type => return out_stream.writeAll("[*]u8"),
.manyptr_const_u8_type => return out_stream.writeAll("[*]const u8"),
@@ -709,6 +721,8 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type => Type.initTag(.single_const_pointer_to_comptime_int),
.anyframe_type => Type.initTag(.@"anyframe"),
.const_slice_u8_type => Type.initTag(.const_slice_u8),
+ .anyerror_void_error_union_type => Type.initTag(.anyerror_void_error_union),
+ .generic_poison_type => Type.initTag(.generic_poison),
.enum_literal_type => Type.initTag(.enum_literal),
.manyptr_u8_type => Type.initTag(.manyptr_u8),
.manyptr_const_u8_type => Type.initTag(.manyptr_const_u8),
@@ -732,46 +746,7 @@ pub const Value = extern union {
return Type.initPayload(&buffer.base);
},
- .undef,
- .zero,
- .one,
- .void_value,
- .unreachable_value,
- .empty_array,
- .bool_true,
- .bool_false,
- .null_value,
- .int_u64,
- .int_i64,
- .int_big_positive,
- .int_big_negative,
- .function,
- .extern_fn,
- .variable,
- .decl_ref,
- .decl_ref_mut,
- .elem_ptr,
- .field_ptr,
- .bytes,
- .repeated,
- .array,
- .slice,
- .float_16,
- .float_32,
- .float_64,
- .float_128,
- .enum_literal,
- .enum_field_index,
- .@"error",
- .error_union,
- .empty_struct_value,
- .@"struct",
- .@"union",
- .inferred_alloc,
- .inferred_alloc_comptime,
- .abi_align_default,
- .eu_payload_ptr,
- => unreachable,
+ else => unreachable,
};
}
@@ -1142,12 +1117,82 @@ pub const Value = extern union {
return order(a, b).compare(.eq);
}
+ pub fn hash(val: Value, ty: Type, hasher: *std.hash.Wyhash) void {
+ switch (ty.zigTypeTag()) {
+ .BoundFn => unreachable, // TODO remove this from the language
+
+ .Void,
+ .NoReturn,
+ .Undefined,
+ .Null,
+ => {},
+
+ .Type => {
+ var buf: ToTypeBuffer = undefined;
+ return val.toType(&buf).hashWithHasher(hasher);
+ },
+ .Bool => {
+ std.hash.autoHash(hasher, val.toBool());
+ },
+ .Int, .ComptimeInt => {
+ var space: BigIntSpace = undefined;
+ const big = val.toBigInt(&space);
+ std.hash.autoHash(hasher, big.positive);
+ for (big.limbs) |limb| {
+ std.hash.autoHash(hasher, limb);
+ }
+ },
+ .Float, .ComptimeFloat => {
+ @panic("TODO implement hashing float values");
+ },
+ .Pointer => {
+ @panic("TODO implement hashing pointer values");
+ },
+ .Array, .Vector => {
+ @panic("TODO implement hashing array/vector values");
+ },
+ .Struct => {
+ @panic("TODO implement hashing struct values");
+ },
+ .Optional => {
+ @panic("TODO implement hashing optional values");
+ },
+ .ErrorUnion => {
+ @panic("TODO implement hashing error union values");
+ },
+ .ErrorSet => {
+ @panic("TODO implement hashing error set values");
+ },
+ .Enum => {
+ @panic("TODO implement hashing enum values");
+ },
+ .Union => {
+ @panic("TODO implement hashing union values");
+ },
+ .Fn => {
+ @panic("TODO implement hashing function values");
+ },
+ .Opaque => {
+ @panic("TODO implement hashing opaque values");
+ },
+ .Frame => {
+ @panic("TODO implement hashing frame values");
+ },
+ .AnyFrame => {
+ @panic("TODO implement hashing anyframe values");
+ },
+ .EnumLiteral => {
+ @panic("TODO implement hashing enum literal values");
+ },
+ }
+ }
+
pub const ArrayHashContext = struct {
ty: Type,
- pub fn hash(self: @This(), v: Value) u32 {
+ pub fn hash(self: @This(), val: Value) u32 {
const other_context: HashContext = .{ .ty = self.ty };
- return @truncate(u32, other_context.hash(v));
+ return @truncate(u32, other_context.hash(val));
}
pub fn eql(self: @This(), a: Value, b: Value) bool {
return a.eql(b, self.ty);
@@ -1157,76 +1202,9 @@ pub const Value = extern union {
pub const HashContext = struct {
ty: Type,
- pub fn hash(self: @This(), v: Value) u64 {
+ pub fn hash(self: @This(), val: Value) u64 {
var hasher = std.hash.Wyhash.init(0);
-
- switch (self.ty.zigTypeTag()) {
- .BoundFn => unreachable, // TODO remove this from the language
-
- .Void,
- .NoReturn,
- .Undefined,
- .Null,
- => {},
-
- .Type => {
- var buf: ToTypeBuffer = undefined;
- return v.toType(&buf).hash();
- },
- .Bool => {
- std.hash.autoHash(&hasher, v.toBool());
- },
- .Int, .ComptimeInt => {
- var space: BigIntSpace = undefined;
- const big = v.toBigInt(&space);
- std.hash.autoHash(&hasher, big.positive);
- for (big.limbs) |limb| {
- std.hash.autoHash(&hasher, limb);
- }
- },
- .Float, .ComptimeFloat => {
- @panic("TODO implement hashing float values");
- },
- .Pointer => {
- @panic("TODO implement hashing pointer values");
- },
- .Array, .Vector => {
- @panic("TODO implement hashing array/vector values");
- },
- .Struct => {
- @panic("TODO implement hashing struct values");
- },
- .Optional => {
- @panic("TODO implement hashing optional values");
- },
- .ErrorUnion => {
- @panic("TODO implement hashing error union values");
- },
- .ErrorSet => {
- @panic("TODO implement hashing error set values");
- },
- .Enum => {
- @panic("TODO implement hashing enum values");
- },
- .Union => {
- @panic("TODO implement hashing union values");
- },
- .Fn => {
- @panic("TODO implement hashing function values");
- },
- .Opaque => {
- @panic("TODO implement hashing opaque values");
- },
- .Frame => {
- @panic("TODO implement hashing frame values");
- },
- .AnyFrame => {
- @panic("TODO implement hashing anyframe values");
- },
- .EnumLiteral => {
- @panic("TODO implement hashing enum literal values");
- },
- }
+ val.hash(self.ty, &hasher);
return hasher.final();
}
diff --git a/test/behavior.zig b/test/behavior.zig
@@ -4,6 +4,7 @@ test {
// Tests that pass for both.
_ = @import("behavior/bool.zig");
_ = @import("behavior/basic.zig");
+ _ = @import("behavior/generics.zig");
if (!builtin.zig_is_stage2) {
// Tests that only pass for stage1.
@@ -94,7 +95,7 @@ test {
_ = @import("behavior/fn_in_struct_in_comptime.zig");
_ = @import("behavior/fn_delegation.zig");
_ = @import("behavior/for.zig");
- _ = @import("behavior/generics.zig");
+ _ = @import("behavior/generics_stage1.zig");
_ = @import("behavior/hasdecl.zig");
_ = @import("behavior/hasfield.zig");
_ = @import("behavior/if.zig");
diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig
@@ -1,4 +1,5 @@
const std = @import("std");
+const mem = std.mem;
const expect = std.testing.expect;
// normal comment
@@ -83,3 +84,81 @@ test "unicode escape in character literal" {
test "unicode character in character literal" {
try expect('💩' == 128169);
}
+
+fn first4KeysOfHomeRow() []const u8 {
+ return "aoeu";
+}
+
+test "return string from function" {
+ try expect(mem.eql(u8, first4KeysOfHomeRow(), "aoeu"));
+}
+
+test "hex escape" {
+ try expect(mem.eql(u8, "\x68\x65\x6c\x6c\x6f", "hello"));
+}
+
+test "multiline string" {
+ const s1 =
+ \\one
+ \\two)
+ \\three
+ ;
+ const s2 = "one\ntwo)\nthree";
+ try expect(mem.eql(u8, s1, s2));
+}
+
+test "multiline string comments at start" {
+ const s1 =
+ //\\one
+ \\two)
+ \\three
+ ;
+ const s2 = "two)\nthree";
+ try expect(mem.eql(u8, s1, s2));
+}
+
+test "multiline string comments at end" {
+ const s1 =
+ \\one
+ \\two)
+ //\\three
+ ;
+ const s2 = "one\ntwo)";
+ try expect(mem.eql(u8, s1, s2));
+}
+
+test "multiline string comments in middle" {
+ const s1 =
+ \\one
+ //\\two)
+ \\three
+ ;
+ const s2 = "one\nthree";
+ try expect(mem.eql(u8, s1, s2));
+}
+
+test "multiline string comments at multiple places" {
+ const s1 =
+ \\one
+ //\\two
+ \\three
+ //\\four
+ \\five
+ ;
+ const s2 = "one\nthree\nfive";
+ try expect(mem.eql(u8, s1, s2));
+}
+
+test "call result of if else expression" {
+ try expect(mem.eql(u8, f2(true), "a"));
+ try expect(mem.eql(u8, f2(false), "b"));
+}
+fn f2(x: bool) []const u8 {
+ return (if (x) fA else fB)();
+}
+fn fA() []const u8 {
+ return "a";
+}
+fn fB() []const u8 {
+ return "b";
+}
diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig
@@ -1,16 +1,43 @@
const std = @import("std");
+const builtin = @import("builtin");
const testing = std.testing;
const expect = testing.expect;
const expectEqual = testing.expectEqual;
+test "one param, explicit comptime" {
+ var x: usize = 0;
+ x += checkSize(i32);
+ x += checkSize(bool);
+ x += checkSize(bool);
+ try expect(x == 6);
+}
+
+fn checkSize(comptime T: type) usize {
+ return @sizeOf(T);
+}
+
test "simple generic fn" {
try expect(max(i32, 3, -1) == 3);
- try expect(max(f32, 0.123, 0.456) == 0.456);
+ try expect(max(u8, 1, 100) == 100);
+ if (!builtin.zig_is_stage2) {
+ // TODO: stage2 is incorrectly emitting the following:
+ // error: cast of value 1.23e-01 to type 'f32' loses information
+ try expect(max(f32, 0.123, 0.456) == 0.456);
+ }
try expect(add(2, 3) == 5);
}
fn max(comptime T: type, a: T, b: T) T {
- return if (a > b) a else b;
+ if (!builtin.zig_is_stage2) {
+ // TODO: stage2 is incorrectly emitting AIR that allocates a result
+ // value, stores to it, but then returns void instead of the result.
+ return if (a > b) a else b;
+ }
+ if (a > b) {
+ return a;
+ } else {
+ return b;
+ }
}
fn add(comptime a: i32, b: i32) i32 {
@@ -37,133 +64,9 @@ fn sameButWithFloats(a: f64, b: f64) f64 {
test "fn with comptime args" {
try expect(gimmeTheBigOne(1234, 5678) == 5678);
try expect(shouldCallSameInstance(34, 12) == 34);
- try expect(sameButWithFloats(0.43, 0.49) == 0.49);
-}
-
-test "var params" {
- try expect(max_i32(12, 34) == 34);
- try expect(max_f64(1.2, 3.4) == 3.4);
-}
-
-test {
- comptime try expect(max_i32(12, 34) == 34);
- comptime try expect(max_f64(1.2, 3.4) == 3.4);
-}
-
-fn max_var(a: anytype, b: anytype) @TypeOf(a + b) {
- return if (a > b) a else b;
-}
-
-fn max_i32(a: i32, b: i32) i32 {
- return max_var(a, b);
-}
-
-fn max_f64(a: f64, b: f64) f64 {
- return max_var(a, b);
-}
-
-pub fn List(comptime T: type) type {
- return SmallList(T, 8);
-}
-
-pub fn SmallList(comptime T: type, comptime STATIC_SIZE: usize) type {
- return struct {
- items: []T,
- length: usize,
- prealloc_items: [STATIC_SIZE]T,
- };
-}
-
-test "function with return type type" {
- var list: List(i32) = undefined;
- var list2: List(i32) = undefined;
- list.length = 10;
- list2.length = 10;
- try expect(list.prealloc_items.len == 8);
- try expect(list2.prealloc_items.len == 8);
-}
-
-test "generic struct" {
- var a1 = GenNode(i32){
- .value = 13,
- .next = null,
- };
- var b1 = GenNode(bool){
- .value = true,
- .next = null,
- };
- try expect(a1.value == 13);
- try expect(a1.value == a1.getVal());
- try expect(b1.getVal());
-}
-fn GenNode(comptime T: type) type {
- return struct {
- value: T,
- next: ?*GenNode(T),
- fn getVal(n: *const GenNode(T)) T {
- return n.value;
- }
- };
-}
-
-test "const decls in struct" {
- try expect(GenericDataThing(3).count_plus_one == 4);
-}
-fn GenericDataThing(comptime count: isize) type {
- return struct {
- const count_plus_one = count + 1;
- };
-}
-
-test "use generic param in generic param" {
- try expect(aGenericFn(i32, 3, 4) == 7);
-}
-fn aGenericFn(comptime T: type, comptime a: T, b: T) T {
- return a + b;
-}
-
-test "generic fn with implicit cast" {
- try expect(getFirstByte(u8, &[_]u8{13}) == 13);
- try expect(getFirstByte(u16, &[_]u16{
- 0,
- 13,
- }) == 0);
-}
-fn getByte(ptr: ?*const u8) u8 {
- return ptr.?.*;
-}
-fn getFirstByte(comptime T: type, mem: []const T) u8 {
- return getByte(@ptrCast(*const u8, &mem[0]));
-}
-
-const foos = [_]fn (anytype) bool{
- foo1,
- foo2,
-};
-
-fn foo1(arg: anytype) bool {
- return arg;
-}
-fn foo2(arg: anytype) bool {
- return !arg;
-}
-
-test "array of generic fns" {
- try expect(foos[0](true));
- try expect(!foos[1](true));
-}
-
-test "generic fn keeps non-generic parameter types" {
- const A = 128;
-
- const S = struct {
- fn f(comptime T: type, s: []T) !void {
- try expect(A != @typeInfo(@TypeOf(s)).Pointer.alignment);
- }
- };
-
- // The compiler monomorphizes `S.f` for `T=u8` on its first use, check that
- // `x` type not affect `s` parameter type.
- var x: [16]u8 align(A) = undefined;
- try S.f(u8, &x);
+ if (!builtin.zig_is_stage2) {
+ // TODO: stage2 llvm backend needs to use fcmp instead of icmp
+ // probably AIR should just have different instructions for floats.
+ try expect(sameButWithFloats(0.43, 0.49) == 0.49);
+ }
}
diff --git a/test/behavior/generics_stage1.zig b/test/behavior/generics_stage1.zig
@@ -0,0 +1,132 @@
+const std = @import("std");
+const testing = std.testing;
+const expect = testing.expect;
+const expectEqual = testing.expectEqual;
+
+test "anytype params" {
+ try expect(max_i32(12, 34) == 34);
+ try expect(max_f64(1.2, 3.4) == 3.4);
+}
+
+test {
+ comptime try expect(max_i32(12, 34) == 34);
+ comptime try expect(max_f64(1.2, 3.4) == 3.4);
+}
+
+fn max_anytype(a: anytype, b: anytype) @TypeOf(a + b) {
+ return if (a > b) a else b;
+}
+
+fn max_i32(a: i32, b: i32) i32 {
+ return max_anytype(a, b);
+}
+
+fn max_f64(a: f64, b: f64) f64 {
+ return max_anytype(a, b);
+}
+
+pub fn List(comptime T: type) type {
+ return SmallList(T, 8);
+}
+
+pub fn SmallList(comptime T: type, comptime STATIC_SIZE: usize) type {
+ return struct {
+ items: []T,
+ length: usize,
+ prealloc_items: [STATIC_SIZE]T,
+ };
+}
+
+test "function with return type type" {
+ var list: List(i32) = undefined;
+ var list2: List(i32) = undefined;
+ list.length = 10;
+ list2.length = 10;
+ try expect(list.prealloc_items.len == 8);
+ try expect(list2.prealloc_items.len == 8);
+}
+
+test "generic struct" {
+ var a1 = GenNode(i32){
+ .value = 13,
+ .next = null,
+ };
+ var b1 = GenNode(bool){
+ .value = true,
+ .next = null,
+ };
+ try expect(a1.value == 13);
+ try expect(a1.value == a1.getVal());
+ try expect(b1.getVal());
+}
+fn GenNode(comptime T: type) type {
+ return struct {
+ value: T,
+ next: ?*GenNode(T),
+ fn getVal(n: *const GenNode(T)) T {
+ return n.value;
+ }
+ };
+}
+
+test "const decls in struct" {
+ try expect(GenericDataThing(3).count_plus_one == 4);
+}
+fn GenericDataThing(comptime count: isize) type {
+ return struct {
+ const count_plus_one = count + 1;
+ };
+}
+
+test "use generic param in generic param" {
+ try expect(aGenericFn(i32, 3, 4) == 7);
+}
+fn aGenericFn(comptime T: type, comptime a: T, b: T) T {
+ return a + b;
+}
+
+test "generic fn with implicit cast" {
+ try expect(getFirstByte(u8, &[_]u8{13}) == 13);
+ try expect(getFirstByte(u16, &[_]u16{
+ 0,
+ 13,
+ }) == 0);
+}
+fn getByte(ptr: ?*const u8) u8 {
+ return ptr.?.*;
+}
+fn getFirstByte(comptime T: type, mem: []const T) u8 {
+ return getByte(@ptrCast(*const u8, &mem[0]));
+}
+
+const foos = [_]fn (anytype) bool{
+ foo1,
+ foo2,
+};
+
+fn foo1(arg: anytype) bool {
+ return arg;
+}
+fn foo2(arg: anytype) bool {
+ return !arg;
+}
+
+test "array of generic fns" {
+ try expect(foos[0](true));
+ try expect(!foos[1](true));
+}
+
+test "generic fn keeps non-generic parameter types" {
+ const A = 128;
+
+ const S = struct {
+ fn f(comptime T: type, s: []T) !void {
+ try expect(A != @typeInfo(@TypeOf(s)).Pointer.alignment);
+ }
+ };
+
+ // The compiler monomorphizes `S.f` for `T=u8` on its first use, check that
+ // `x` type not affect `s` parameter type.
+ var x: [16]u8 align(A) = undefined;
+ try S.f(u8, &x);
+}
diff --git a/test/behavior/misc.zig b/test/behavior/misc.zig
@@ -5,14 +5,6 @@ const expectEqualStrings = std.testing.expectEqualStrings;
const mem = std.mem;
const builtin = @import("builtin");
-fn first4KeysOfHomeRow() []const u8 {
- return "aoeu";
-}
-
-test "return string from function" {
- try expect(mem.eql(u8, first4KeysOfHomeRow(), "aoeu"));
-}
-
test "memcpy and memset intrinsics" {
var foo: [20]u8 = undefined;
var bar: [20]u8 = undefined;
@@ -48,10 +40,6 @@ test "constant equal function pointers" {
fn emptyFn() void {}
-test "hex escape" {
- try expect(mem.eql(u8, "\x68\x65\x6c\x6c\x6f", "hello"));
-}
-
test "string concatenation" {
try expect(mem.eql(u8, "OK" ++ " IT " ++ "WORKED", "OK IT WORKED"));
}
@@ -70,59 +58,7 @@ test "string escapes" {
try expectEqualStrings("\u{1234}\u{069}\u{1}", "\xe1\x88\xb4\x69\x01");
}
-test "multiline string" {
- const s1 =
- \\one
- \\two)
- \\three
- ;
- const s2 = "one\ntwo)\nthree";
- try expect(mem.eql(u8, s1, s2));
-}
-
-test "multiline string comments at start" {
- const s1 =
- //\\one
- \\two)
- \\three
- ;
- const s2 = "two)\nthree";
- try expect(mem.eql(u8, s1, s2));
-}
-
-test "multiline string comments at end" {
- const s1 =
- \\one
- \\two)
- //\\three
- ;
- const s2 = "one\ntwo)";
- try expect(mem.eql(u8, s1, s2));
-}
-
-test "multiline string comments in middle" {
- const s1 =
- \\one
- //\\two)
- \\three
- ;
- const s2 = "one\nthree";
- try expect(mem.eql(u8, s1, s2));
-}
-
-test "multiline string comments at multiple places" {
- const s1 =
- \\one
- //\\two
- \\three
- //\\four
- \\five
- ;
- const s2 = "one\nthree\nfive";
- try expect(mem.eql(u8, s1, s2));
-}
-
-test "multiline C string" {
+test "multiline string literal is null terminated" {
const s1 =
\\one
\\two)
@@ -177,20 +113,6 @@ fn outer() i64 {
return inner();
}
-test "call result of if else expression" {
- try expect(mem.eql(u8, f2(true), "a"));
- try expect(mem.eql(u8, f2(false), "b"));
-}
-fn f2(x: bool) []const u8 {
- return (if (x) fA else fB)();
-}
-fn fA() []const u8 {
- return "a";
-}
-fn fB() []const u8 {
- return "b";
-}
-
test "constant enum initialization with differing sizes" {
try test3_1(test3_foo);
try test3_2(test3_bar);
diff --git a/test/cases.zig b/test/cases.zig
@@ -1572,7 +1572,7 @@ pub fn addCases(ctx: *TestContext) !void {
\\ const x = asm volatile ("syscall"
\\ : [o] "{rax}" (-> number)
\\ : [number] "{rax}" (231),
- \\ [arg1] "{rdi}" (code)
+ \\ [arg1] "{rdi}" (60)
\\ : "rcx", "r11", "memory"
\\ );
\\ _ = x;