From 1472dc3ddb6fd7932ff530e7a2fd3f0185c7353f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 2 Aug 2021 20:35:55 -0700 Subject: stage2: update ZIR for generic functions ZIR encoding for functions is changed in preparation for generic function support. As an example: ```zig const std = @import("std"); const expect = std.testing.expect; test "example" { var x: usize = 0; x += checkSize(i32, 1); x += checkSize(bool, true); try expect(x == 5); } fn checkSize(comptime T: type, x: T) usize { _ = x; return @sizeOf(T); } ``` Previous ZIR for the `checkSize` function: ```zir [165] checkSize line(10) hash(0226f62e189fd0b1c5fca02cf4617562): %55 = block_inline({ %56 = decl_val("T") token_offset:11:35 %57 = as_node(@Ref.type_type, %56) node_offset:11:35 %69 = extended(func([comptime @Ref.type_type, %57], @Ref.usize_type, { %58 = arg("T") token_offset:11:23 %59 = as_node(@Ref.type_type, %58) node_offset:11:35 %60 = arg("x") token_offset:11:32 %61 = dbg_stmt(11, 4) ``` ZIR for the `checkSize` function after this commit: ```zir [157] checkSize line(10) hash(0226f62e189fd0b1c5fca02cf4617562): %55 = block_inline({ %56 = param_comptime("T", @Ref.type_type) token_offset:11:23 %57 = as_node(@Ref.type_type, %56) node_offset:11:35 %58 = param("x", %57) token_offset:11:32 %67 = func(@Ref.usize_type, { %59 = dbg_stmt(11, 4) ``` Noted differences: * Previously the type expression was redundantly repeated. * Previously the parameter names were redundantly stored in the ZIR extra array. * Instead of `arg` ZIR instructions as the first instructions within a function body, they are now outside the function body, in the same block as the `func` instruction. There are variants: - param - param_comptime - param_anytype - param_anytype_comptime * The param instructions additionally encode the type. * Because of the param instructions, the `func` instruction no longer encodes the list of parameter types or the comptime bits. It's implied that Sema will collect the parameters so that when a `func` instruction is encountered, they will be implicitly used to construct the function's type. This is so that we can satisfy all 3 ways of performing semantic analysis on a function: 1. runtime: Sema will insert AIR arg instructions for each parameter, and insert into the Sema inst_map ZIR param => AIR arg. 2. comptime/inline: Sema will insert into the inst_map ZIR param => callsite arguments. 3. generic: Sema will map *only the comptime* ZIR param instructions to the AIR instructions for the comptime arguments at the callsite, and then re-run Sema for the function's Decl. This will produce a new function which is the monomorphized function. Additionally: * AstGen: Update usage of deprecated `ensureCapacity` to `ensureUnusedCapacity` or `ensureTotalCapacity`. * Introduce `Type.fnInfo` for getting a bunch of data about a function type at once, and use it in `analyzeCall`. This commit starts a branch to implement generic functions in stage2. Test regressions have not been addressed yet. --- src/Module.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/Module.zig') diff --git a/src/Module.zig b/src/Module.zig index 84b721369d..fa8b4ca768 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3714,7 +3714,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); -- cgit v1.2.3 From 609b84611dcde382af5d9fbc2345ede468d31a6f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 3 Aug 2021 17:29:59 -0700 Subject: stage2: rework runtime, comptime, inline function calls * ZIR function instructions encode the index of the block that contains the function instruction. This allows Zig to later scan the block and find the parameter instructions, which is needed for semantically analyzing function bodies. * Runtime function calls insert AIR arg instructions and then inserts Sema inst_map entries mapping the ZIR param instructions to them. * comptime/inline function call inserts Sema inst_map entries mapping the ZIR param instructions to the AIR callsite arguments. With this commit we are back to the tests passing. --- BRANCH_TODO | 2 -- src/AstGen.zig | 7 ++++++ src/Module.zig | 57 +++++++++++++++++++++++++++++--------------- src/Sema.zig | 75 ++++++++++++++++------------------------------------------ src/Zir.zig | 54 +++++++++++++++++++++++++++++++++++++++--- 5 files changed, 117 insertions(+), 78 deletions(-) (limited to 'src/Module.zig') diff --git a/BRANCH_TODO b/BRANCH_TODO index bc0a67f799..1cdd8362e7 100644 --- a/BRANCH_TODO +++ b/BRANCH_TODO @@ -1,6 +1,4 @@ * update arg instructions: - - runtime function call inserts AIR arg instructions and Sema map items for them - - comptime/inline function call inserts Sema map items for the args - generic instantiation inserts Sema map items for the comptime args only, re-runs the Decl ZIR to get the new Fn. * generic function call where it makes a new function diff --git a/src/AstGen.zig b/src/AstGen.zig index 0b78c839a0..f88b59d211 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1125,6 +1125,7 @@ fn fnProtoExpr( const result = try gz.addFunc(.{ .src_node = fn_proto.ast.proto_node, + .param_block = 0, .ret_ty = return_type_inst, .body = &[0]Zir.Inst.Index{}, .cc = cc, @@ -3035,6 +3036,7 @@ fn fnDecl( break :func try decl_gz.addFunc(.{ .src_node = decl_node, .ret_ty = return_type_inst, + .param_block = block_inst, .body = &[0]Zir.Inst.Index{}, .cc = cc, .align_inst = .none, // passed in the per-decl data @@ -3071,6 +3073,7 @@ fn fnDecl( break :func try decl_gz.addFunc(.{ .src_node = decl_node, + .param_block = block_inst, .ret_ty = return_type_inst, .body = fn_gz.instructions.items, .cc = cc, @@ -3415,6 +3418,7 @@ fn testDecl( const func_inst = try decl_block.addFunc(.{ .src_node = node, + .param_block = block_inst, .ret_ty = .void_type, .body = fn_block.instructions.items, .cc = .none, @@ -9111,6 +9115,7 @@ const GenZir = struct { fn addFunc(gz: *GenZir, args: struct { src_node: ast.Node.Index, body: []const Zir.Inst.Index, + param_block: Zir.Inst.Index, ret_ty: Zir.Inst.Ref, cc: Zir.Inst.Ref, align_inst: Zir.Inst.Ref, @@ -9170,6 +9175,7 @@ const GenZir = struct { ); const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.ExtendedFunc{ .src_node = gz.nodeIndexToRelative(args.src_node), + .param_block = args.param_block, .return_type = args.ret_ty, .body_len = @intCast(u32, args.body.len), }); @@ -9212,6 +9218,7 @@ const GenZir = struct { ); const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Func{ + .param_block = args.param_block, .return_type = args.ret_ty, .body_len = @intCast(u32, args.body.len), }); diff --git a/src/Module.zig b/src/Module.zig index fa8b4ca768..6253e2808d 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2899,7 +2899,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 +2953,6 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { .namespace = decl.namespace, .func = null, .owner_func = null, - .param_inst_list = &.{}, }; defer sema.deinit(); @@ -3625,8 +3623,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 +3633,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 +3651,55 @@ 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 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. + const params_len = @intCast(u32, fn_ty.fnParamLen()); + try inner_block.instructions.ensureTotalCapacity(gpa, params_len); + try sema.air_instructions.ensureUnusedCapacity(gpa, params_len * 2); // * 2 for the `addType` + try sema.inst_map.ensureUnusedCapacity(gpa, params_len); + + var 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, + }; const param_type = fn_ty.fnParamType(param_index); + param_index += 1; 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)); } 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 => unreachable, + 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 + @@ -4330,7 +4351,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 +4504,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 index 5fd3c149a2..923295069d 100644 --- 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 @@ -85,43 +78,10 @@ pub fn deinit(sema: *Sema) void { sema.air_values.deinit(gpa); sema.inst_map.deinit(gpa); sema.decl_val_table.deinit(gpa); + sema.params.deinit(gpa); 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 body = sema.code.extra[extra.end..][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); - 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. @@ -1066,7 +1026,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, }; @@ -2538,10 +2497,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; @@ -2565,12 +2520,23 @@ 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); - - const result = try sema.analyzeBlockBody(block, call_src, &child_block, merges); - - break :res result; + // 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) { const func_val = try sema.resolveConstValue(block, func_src, func); const module_fn = func_val.castTag(.function).?.data; @@ -2601,7 +2567,7 @@ fn analyzeCall( // TODO // Queue up a `codegen_func` work item for the new Fn, making sure it will have - // `analyzeFnBody` called with the Scope which contains the comptime parameters. + // `analyzeBody` called with the ZIR parameters mapped appropriately. // TODO // Save it into the Module's generic function map. @@ -3344,11 +3310,12 @@ fn funcCommon( // `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.ty); + param_types[i] = try sema.analyzeAsType(block, param_src, param.ty); } comptime_params[i] = param.is_comptime; any_are_comptime = any_are_comptime or param.is_comptime; } + sema.params.clearRetainingCapacity(); if (align_val.tag() != .null_value) { return mod.fail(&block.base, src, "TODO implement support for function prototypes to have alignment specified", .{}); diff --git a/src/Zir.zig b/src/Zir.zig index 6445d73af5..b7f4c28161 100644 --- 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); } @@ -2260,6 +2260,8 @@ pub const Inst = struct { pub const ExtendedFunc = struct { src_node: i32, return_type: Ref, + /// Points to the block that contains the param instructions for this function. + param_block: Index, body_len: u32, pub const Small = packed struct { @@ -2297,6 +2299,8 @@ pub const Inst = struct { /// 1. src_locs: SrcLocs // if body_len != 0 pub const Func = struct { return_type: Ref, + /// Points to the block that contains the param instructions for this function. + param_block: Index, body_len: u32, pub const SrcLocs = struct { @@ -4894,10 +4898,54 @@ 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 fn getFnInfo(zir: Zir, fn_inst: Inst.Index) struct { + param_body: []const Inst.Index, + body: []const Inst.Index, +} { + const tags = zir.instructions.items(.tag); + const datas = zir.instructions.items(.data); + const info: struct { + param_block: Inst.Index, + 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); + const body = zir.extra[extra.end..][0..extra.data.body_len]; + break :blk .{ + .param_block = extra.data.param_block, + .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 body = zir.extra[extra_index..][0..extra.data.body_len]; + break :blk .{ + .param_block = extra.data.param_block, + .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); + return .{ + .param_body = zir.extra[param_block.end..][0..param_block.data.body_len], + .body = info.body, + }; +} -- cgit v1.2.3 From 382d201781eb57d9e950ad07ce814adc5a68b329 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 3 Aug 2021 22:34:22 -0700 Subject: stage2: basic generic functions are working The general strategy is that Sema will pre-map comptime arguments into the inst_map, and then re-run the block body that contains the `param` and `func` instructions. This re-runs all the parameter type expressions except with comptime values populated. In Sema, param instructions are now handled specially: they detect whether they are comptime-elided or not. If so, they skip putting a value in the inst_map, since it is already pre-populated. If not, then they append to the `fields` field of `Sema` for use with the `func` instruction. So when the block body is re-run, a new function is generated with all the comptime arguments elided, and the new function type has only runtime parameters in it. TODO: give the generated Decls better names than "foo__anon_x". The new function is then added to the work queue to have its body analyzed and a runtime call AIR instruction to the new function is emitted. When the new function gets semantically analyzed, comptime parameters are pre-mapped to the corresponding `comptime_args` values rather than mapped to an `arg` AIR instruction. `comptime_args` is a new field that `Fn` has which is a `TypedValue` for each parameter. This field is non-null for generic function instantiations only. The values are the comptime arguments. For non-comptime parameters, a sentinel value is used. This is because we need to know the information of which parameters are comptime-known. Additionally: * AstGen: align and section expressions are evaluated in the scope that has comptime parameters in it. There are still some TODO items left; see the BRANCH_TODO file. --- BRANCH_TODO | 7 +- src/AstGen.zig | 16 +-- src/Module.zig | 27 ++++- src/Sema.zig | 346 +++++++++++++++++++++++++++++++++++++++------------------ src/Zir.zig | 14 ++- src/type.zig | 3 +- 6 files changed, 287 insertions(+), 126 deletions(-) (limited to 'src/Module.zig') diff --git a/BRANCH_TODO b/BRANCH_TODO index 1cdd8362e7..e8606332d7 100644 --- a/BRANCH_TODO +++ b/BRANCH_TODO @@ -1,7 +1,4 @@ -* update arg instructions: - - generic instantiation inserts Sema map items for the comptime args only, re-runs the - Decl ZIR to get the new Fn. -* generic function call where it makes a new function * memoize the instantiation in a table -* anytype with next parameter expression using it +* expressions that depend on comptime stuff need a poison value to use for + types when generating the generic function type * comptime anytype diff --git a/src/AstGen.zig b/src/AstGen.zig index f88b59d211..7534afe961 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2906,14 +2906,7 @@ 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); + try wip_decls.next(gpa, is_pub, is_export, fn_proto.ast.align_expr != 0, fn_proto.ast.section_expr != 0); var params_scope = &fn_gz.base; const is_var_args = is_var_args: { @@ -2994,6 +2987,13 @@ fn fnDecl( const maybe_bang = tree.firstToken(fn_proto.ast.return_type) - 1; const is_inferred_error = token_tags[maybe_bang] == .bang; + 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); + }; + const return_type_inst = try AstGen.expr( &decl_gz, params_scope, diff --git a/src/Module.zig b/src/Module.zig index 6253e2808d..184ea617b1 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -757,6 +757,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 @@ -3657,10 +3661,13 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { // 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. - const params_len = @intCast(u32, fn_ty.fnParamLen()); - try inner_block.instructions.ensureTotalCapacity(gpa, params_len); - try sema.air_instructions.ensureUnusedCapacity(gpa, params_len * 2); // * 2 for the `addType` - try sema.inst_map.ensureUnusedCapacity(gpa, params_len); + // 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 param_index: usize = 0; for (fn_info.param_body) |inst| { @@ -3678,8 +3685,17 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { else => continue, }; + if (func.comptime_args) |comptime_args| { + const arg_tv = comptime_args[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); + param_index += 1; + continue; + } + } const param_type = fn_ty.fnParamType(param_index); - param_index += 1; const ty_ref = try sema.addType(param_type); const arg_index = @intCast(u32, sema.air_instructions.len); inner_block.instructions.appendAssumeCapacity(arg_index); @@ -3691,6 +3707,7 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { } }, }); sema.inst_map.putAssumeCapacityNoClobber(inst, Air.indexToRef(arg_index)); + param_index += 1; } func.state = .in_progress; diff --git a/src/Sema.zig b/src/Sema.zig index 923295069d..9a7eb6fc40 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -36,9 +36,14 @@ 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, -params: std.ArrayListUnmanaged(Param) = .{}, decl_val_table: std.AutoHashMapUnmanaged(*Decl, Air.Inst.Ref) = .{}, +/// `param` instructions are collected here to be used by the `func` instruction. +params: std.ArrayListUnmanaged(Param) = .{}, +/// 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 owner_decl. +comptime_args: []TypedValue = &.{}, +next_arg_index: usize = 0, const std = @import("std"); const mem = std.mem; @@ -64,8 +69,8 @@ const target_util = @import("target.zig"); const Param = struct { name: [:0]const u8, - /// `none` means `anytype`. - ty: Air.Inst.Ref, + /// `noreturn` means `anytype`. + ty: Type, is_comptime: bool, }; @@ -366,26 +371,6 @@ pub fn analyzeBody( // continue the loop. // We also know that they cannot be referenced later, so we avoid // putting them into the map. - .param => { - try sema.zirParam(inst, false); - i += 1; - continue; - }, - .param_comptime => { - try sema.zirParam(inst, true); - i += 1; - continue; - }, - .param_anytype => { - try sema.zirParamAnytype(inst, false); - i += 1; - continue; - }, - .param_anytype_comptime => { - try sema.zirParamAnytype(inst, true); - i += 1; - continue; - }, .breakpoint => { try sema.zirBreakpoint(block, inst); i += 1; @@ -519,6 +504,88 @@ pub fn analyzeBody( return break_inst; } }, + .param => blk: { + 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).data; + const param_name = sema.code.nullTerminatedString(extra.name); + + if (sema.nextArgIsComptimeElided()) { + i += 1; + continue; + } + + // TODO check if param_name shadows a Decl. This only needs to be done if + // usingnamespace is implemented. + + const param_ty = try sema.resolveType(block, src, extra.ty); + try sema.params.append(sema.gpa, .{ + .name = param_name, + .ty = param_ty, + .is_comptime = false, + }); + break :blk try sema.addConstUndef(param_ty); + }, + .param_comptime => blk: { + 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).data; + const param_name = sema.code.nullTerminatedString(extra.name); + + if (sema.nextArgIsComptimeElided()) { + i += 1; + continue; + } + + // TODO check if param_name shadows a Decl. This only needs to be done if + // usingnamespace is implemented. + + const param_ty = try sema.resolveType(block, src, extra.ty); + try sema.params.append(sema.gpa, .{ + .name = param_name, + .ty = param_ty, + .is_comptime = true, + }); + break :blk try sema.addConstUndef(param_ty); + }, + .param_anytype => blk: { + const inst_data = sema.code.instructions.items(.data)[inst].str_tok; + const param_name = inst_data.get(sema.code); + + if (sema.nextArgIsComptimeElided()) { + i += 1; + continue; + } + + // TODO check if param_name shadows a Decl. This only needs to be done if + // usingnamespace is implemented. + + try sema.params.append(sema.gpa, .{ + .name = param_name, + .ty = Type.initTag(.noreturn), + .is_comptime = false, + }); + break :blk try sema.addConstUndef(Type.initTag(.@"undefined")); + }, + .param_anytype_comptime => blk: { + const inst_data = sema.code.instructions.items(.data)[inst].str_tok; + const param_name = inst_data.get(sema.code); + + if (sema.nextArgIsComptimeElided()) { + i += 1; + continue; + } + + // TODO check if param_name shadows a Decl. This only needs to be done if + // usingnamespace is implemented. + + try sema.params.append(sema.gpa, .{ + .name = param_name, + .ty = Type.initTag(.noreturn), + .is_comptime = true, + }); + break :blk try sema.addConstUndef(Type.initTag(.@"undefined")); + }, }; if (sema.typeOf(air_inst).isNoReturn()) return always_noreturn; @@ -1339,36 +1406,6 @@ fn zirIndexablePtrLen(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co return sema.analyzeLoad(block, src, result_ptr, result_ptr_src); } -fn zirParam(sema: *Sema, inst: Zir.Inst.Index, is_comptime: bool) CompileError!void { - const inst_data = sema.code.instructions.items(.data)[inst].pl_tok; - const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index).data; - const param_name = sema.code.nullTerminatedString(extra.name); - - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - - const param_ty = sema.resolveInst(extra.ty); - try sema.params.append(sema.gpa, .{ - .name = param_name, - .ty = param_ty, - .is_comptime = is_comptime, - }); -} - -fn zirParamAnytype(sema: *Sema, 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. - - try sema.params.append(sema.gpa, .{ - .name = param_name, - .ty = .none, - .is_comptime = is_comptime, - }); -} - fn zirAllocExtended( sema: *Sema, block: *Scope.Block, @@ -2497,10 +2534,6 @@ fn analyzeCall( sema.func = module_fn; defer sema.func = parent_func; - 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, @@ -2537,7 +2570,7 @@ fn analyzeCall( } _ = 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) { + } 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 @@ -2545,37 +2578,142 @@ fn analyzeCall( // only to junk it if it matches an existing instantiation. // TODO - // Create a Decl for the new function. - const generic_namespace = try sema.arena.create(Module.Scope.Namespace); - generic_namespace.* = .{ - .parent = block.src_decl.namespace, - .file_scope = block.src_decl.namespace.file_scope, - .ty = func_ty, + const fn_info = sema.code.getFnInfo(module_fn.zir_body_inst); + const zir_tags = sema.code.instructions.items(.tag); + var non_comptime_args_len: u32 = 0; + const new_func = new_func: { + const namespace = module_fn.owner_decl.namespace; + 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 = sema.code, + .owner_decl = new_decl, + .namespace = namespace, + .func = null, + .owner_func = null, + .comptime_args = try new_decl_arena.allocator.alloc(TypedValue, args.len), + }; + 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); + + try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, 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, // TODO make true for always comptime types + else => continue, + }; + if (is_comptime) { + // TODO: pass .unneeded to resolveConstValue and then if we get + // error.NeededSourceLocation resolve the arg source location and + // try again. + const arg_src = call_src; + const arg = args[arg_i]; + const arg_val = try sema.resolveConstValue(block, arg_src, arg); + child_sema.comptime_args[arg_i] = .{ + .ty = try sema.typeOf(arg).copy(&new_decl_arena.allocator), + .val = try arg_val.copy(&new_decl_arena.allocator), + }; + const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val); + child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); + } else { + non_comptime_args_len += 1; + child_sema.comptime_args[arg_i] = .{ + .ty = Type.initTag(.noreturn), + .val = Value.initTag(.unreachable_value), + }; + } + 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; + + // 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; + + // 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 :new_func try sema.analyzeDeclVal(block, func_src, new_decl); }; - const new_decl = try mod.allocateNewDecl(generic_namespace, module_fn.owner_decl.src_node); - _ = new_decl; - - // Iterate over the parameters that are comptime, evaluating their type expressions - // inside a Scope which contains the previous parameters. - //for (args) |arg, arg_i| { - //} - - // Create a new Fn with only the runtime-known parameters. - // TODO - - // Populate the Decl ty/val with the function and its type. - // TODO - - // Queue up a `codegen_func` work item for the new Fn, making sure it will have - // `analyzeBody` called with the ZIR parameters mapped appropriately. - // TODO // Save it into the Module's generic function map. // TODO - // Call it the same as a runtime function. - // TODO - return mod.fail(&block.base, func_src, "TODO implement generic fn call", .{}); + // Make a runtime call to the new function, making sure to omit the comptime args. + try sema.requireRuntimeBlock(block, call_src); + try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len + + non_comptime_args_len); + const func_inst = try block.addInst(.{ + .tag = .call, + .data = .{ .pl_op = .{ + .operand = new_func, + .payload = sema.addExtraAssumeCapacity(Air.Call{ + .args_len = non_comptime_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, // TODO make true for always comptime types + else => continue, + }; + if (is_comptime) { + sema.air_extra.appendAssumeCapacity(@enumToInt(args[arg_i])); + } + arg_i += 1; + } + break :res func_inst; } else res: { try sema.requireRuntimeBlock(block, call_src); try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len + @@ -3302,15 +3440,10 @@ fn funcCommon( const param_types = try sema.arena.alloc(Type, sema.params.items.len); const comptime_params = try sema.arena.alloc(bool, sema.params.items.len); for (sema.params.items) |param, i| { - if (param.ty == .none) { + if (param.ty.tag() == .noreturn) { param_types[i] = Type.initTag(.noreturn); // indicates anytype } else { - // 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.analyzeAsType(block, param_src, param.ty); + param_types[i] = param.ty; } comptime_params[i] = param.is_comptime; any_are_comptime = any_are_comptime or param.is_comptime; @@ -3402,6 +3535,7 @@ fn funcCommon( .state = anal_state, .zir_body_inst = body_inst, .owner_decl = sema.owner_decl, + .comptime_args = if (sema.comptime_args.len == 0) null else sema.comptime_args.ptr, .lbrace_line = src_locs.lbrace_line, .rbrace_line = src_locs.rbrace_line, .lbrace_column = @truncate(u16, src_locs.columns), @@ -6819,19 +6953,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); @@ -8832,7 +8959,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); @@ -8888,3 +9015,10 @@ fn isComptimeKnown( ) !bool { return (try sema.resolveMaybeUndefVal(block, src, inst)) != null; } + +fn nextArgIsComptimeElided(sema: *Sema) bool { + if (sema.comptime_args.len == 0) return false; + const result = sema.comptime_args[sema.next_arg_index].val.tag() != .unreachable_value; + sema.next_arg_index += 1; + return result; +} diff --git a/src/Zir.zig b/src/Zir.zig index b7f4c28161..0b93208564 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -4909,6 +4909,7 @@ fn findDeclsBody( pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) struct { param_body: []const Inst.Index, body: []const Inst.Index, + total_params_len: u32, } { const tags = zir.instructions.items(.tag); const datas = zir.instructions.items(.data); @@ -4944,8 +4945,19 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) struct { }; 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 = zir.extra[param_block.end..][0..param_block.data.body_len], + .param_body = param_body, .body = info.body, + .total_params_len = total_params_len, }; } diff --git a/src/type.zig b/src/type.zig index feb16fd47c..237614e372 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1182,7 +1182,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, @@ -1207,6 +1206,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; -- cgit v1.2.3 From d4468affb751668e156230c32b29c84684825b4f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 4 Aug 2021 21:11:31 -0700 Subject: stage2 generics improvements: anytype and param type exprs AstGen result locations now have a `coerced_ty` tag which is the same as `ty` except it assumes that Sema will do a coercion, so it does not redundantly add an `as` instruction into the ZIR code. This results in cleaner ZIR and about a 14% reduction of ZIR bytes. param and param_comptime ZIR instructions now have a block body for their type expressions. This allows Sema to skip evaluation of the block in the case that the parameter is comptime-provided. It also allows a new mechanism to function: when evaluating type expressions of generic functions, if it would depend on another parameter, it returns `error.GenericPoison` which bubbles up and then is caught by the param/param_comptime instruction and then handled. This allows parameters to be evaluated independently so that the type info for functions which have comptime or anytype parameters will still have types populated for parameters that do not depend on values of previous parameters (because evaluation of their param blocks will return successfully instead of `error.GenericPoison`). It also makes iteration over the block that contains function parameters slightly more efficient since it now only contains the param instructions. Finally, it fixes the case where a generic function type expression contains a function prototype. Formerly, this situation would cause shared state to clobber each other; now it is in a proper tree structure so that can't happen. This fix also required adding a field to Sema `comptime_args_fn_inst` to make sure that the `comptime_args` field passed into Sema is applied to the correct `func` instruction. Source location for `node_offset_asm_ret_ty` is fixed; it was pointing at the asm output name rather than the return type as intended. Generic function instantiation is fixed, notably with respect to parameter type expressions that depend on previous parameters, and with respect to types which must be always comptime-known. This involves passing all the comptime arguments at a callsite of a generic function, and allowing the generic function semantic analysis to coerce the values to the proper types (since it has access to the evaluated parameter type expressions) and then decide based on the type whether the parameter is runtime known or not. In the case of explicitly marked `comptime` parameters, there is a check at the semantic analysis of the `call` instruction. Semantic analysis of `call` instructions does type coercion on the arguments, which is needed both for generic functions and to make up for using `coerced_ty` result locations (mentioned above). Tasks left in this branch: * Implement the memoization table. * Add test coverage. * Improve error reporting and source locations for compile errors. --- BRANCH_TODO | 4 - src/AstGen.zig | 71 +++++---- src/Compilation.zig | 2 +- src/Module.zig | 52 +++++-- src/Sema.zig | 428 ++++++++++++++++++++++++++++++++------------------- src/Zir.zig | 32 +++- src/codegen/llvm.zig | 4 + src/type.zig | 116 ++++++++++++++ src/value.zig | 55 ++----- test/cases.zig | 2 +- 10 files changed, 519 insertions(+), 247 deletions(-) delete mode 100644 BRANCH_TODO (limited to 'src/Module.zig') diff --git a/BRANCH_TODO b/BRANCH_TODO deleted file mode 100644 index e8606332d7..0000000000 --- a/BRANCH_TODO +++ /dev/null @@ -1,4 +0,0 @@ -* memoize the instantiation in a table -* expressions that depend on comptime stuff need a poison value to use for - types when generating the generic function type -* comptime anytype diff --git a/src/AstGen.zig b/src/AstGen.zig index 7534afe961..493e0a75f4 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -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, }, @@ -260,13 +263,14 @@ 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`. @@ -1079,16 +1083,19 @@ fn fnProtoExpr( .param_anytype; _ = try gz.addStrTok(tag, param_name, name_token); } else { + const gpa = astgen.gpa; const param_type_node = param.type_expr; assert(param_type_node != 0); - const param_type = try expr(gz, scope, type_rl, param_type_node); + 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; - _ = try gz.addPlTok(tag, name_token, Zir.Inst.Param{ - .name = param_name, - .ty = param_type, - }); + 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; @@ -1219,7 +1226,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); @@ -1388,7 +1395,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); } @@ -2617,7 +2624,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, @@ -2953,14 +2960,18 @@ fn fnDecl( } else param: { const param_type_node = param.type_expr; assert(param_type_node != 0); - const param_type = try expr(&decl_gz, params_scope, type_rl, param_type_node); + 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; - break :param try decl_gz.addPlTok(tag, name_token, Zir.Inst.Param{ - .name = param_name, - .ty = param_type, - }); + 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; @@ -6758,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); }, @@ -6781,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, @@ -6867,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, @@ -7677,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: { @@ -8370,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); @@ -9042,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; }, @@ -9425,18 +9436,26 @@ const GenZir = struct { return indexToRef(new_index); } - fn addPlTok( + fn addParam( gz: *GenZir, tag: Zir.Inst.Tag, /// Absolute token index. This function does the conversion to Decl offset. abs_tok_index: ast.TokenIndex, - extra: anytype, - ) !Zir.Inst.Ref { + 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 payload_index = try gz.astgen.addExtra(extra); const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len); gz.astgen.instructions.appendAssumeCapacity(.{ .tag = tag, @@ -9446,7 +9465,7 @@ const GenZir = struct { } }, }); gz.instructions.appendAssumeCapacity(new_index); - return indexToRef(new_index); + return new_index; } fn addExtendedPayload( diff --git a/src/Compilation.zig b/src/Compilation.zig index f8f8cea328..adb4940243 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2118,7 +2118,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 index 184ea617b1..2c3e745c11 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1173,6 +1173,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. @@ -1187,6 +1189,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 { @@ -1634,8 +1642,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]; }, @@ -2099,7 +2110,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; @@ -2796,14 +2820,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; }, @@ -2982,7 +3008,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; @@ -3669,7 +3698,8 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { 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 param_index: usize = 0; + 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: { @@ -3686,16 +3716,16 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { else => continue, }; if (func.comptime_args) |comptime_args| { - const arg_tv = comptime_args[param_index]; + 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); - param_index += 1; + total_param_index += 1; continue; } } - const param_type = fn_ty.fnParamType(param_index); + 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); @@ -3707,7 +3737,8 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { } }, }); sema.inst_map.putAssumeCapacityNoClobber(inst, Air.indexToRef(arg_index)); - param_index += 1; + total_param_index += 1; + runtime_param_index += 1; } func.state = .in_progress; @@ -3715,6 +3746,7 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { _ = sema.analyzeBody(&inner_block, fn_info.body) catch |err| switch (err) { error.NeededSourceLocation => unreachable, + error.GenericPoison => unreachable, else => |e| return e, }; diff --git a/src/Sema.zig b/src/Sema.zig index 9a7eb6fc40..8bcdcb63c9 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -37,13 +37,15 @@ branch_count: u32 = 0, /// contain a mapped source location. src: LazySrcLoc = .{ .token_offset = 0 }, decl_val_table: std.AutoHashMapUnmanaged(*Decl, Air.Inst.Ref) = .{}, -/// `param` instructions are collected here to be used by the `func` instruction. -params: std.ArrayListUnmanaged(Param) = .{}, -/// 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 owner_decl. +/// 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 = &.{}, -next_arg_index: usize = 0, +/// 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, const std = @import("std"); const mem = std.mem; @@ -67,13 +69,6 @@ const LazySrcLoc = Module.LazySrcLoc; const RangeSet = @import("RangeSet.zig"); const target_util = @import("target.zig"); -const Param = struct { - name: [:0]const u8, - /// `noreturn` means `anytype`. - ty: Type, - is_comptime: bool, -}; - pub const InstMap = std.AutoHashMapUnmanaged(Zir.Inst.Index, Air.Inst.Ref); pub fn deinit(sema: *Sema) void { @@ -83,7 +78,6 @@ pub fn deinit(sema: *Sema) void { sema.air_values.deinit(gpa); sema.inst_map.deinit(gpa); sema.decl_val_table.deinit(gpa); - sema.params.deinit(gpa); sema.* = undefined; } @@ -466,6 +460,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 => { @@ -504,88 +518,6 @@ pub fn analyzeBody( return break_inst; } }, - .param => blk: { - 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).data; - const param_name = sema.code.nullTerminatedString(extra.name); - - if (sema.nextArgIsComptimeElided()) { - i += 1; - continue; - } - - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - - const param_ty = try sema.resolveType(block, src, extra.ty); - try sema.params.append(sema.gpa, .{ - .name = param_name, - .ty = param_ty, - .is_comptime = false, - }); - break :blk try sema.addConstUndef(param_ty); - }, - .param_comptime => blk: { - 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).data; - const param_name = sema.code.nullTerminatedString(extra.name); - - if (sema.nextArgIsComptimeElided()) { - i += 1; - continue; - } - - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - - const param_ty = try sema.resolveType(block, src, extra.ty); - try sema.params.append(sema.gpa, .{ - .name = param_name, - .ty = param_ty, - .is_comptime = true, - }); - break :blk try sema.addConstUndef(param_ty); - }, - .param_anytype => blk: { - const inst_data = sema.code.instructions.items(.data)[inst].str_tok; - const param_name = inst_data.get(sema.code); - - if (sema.nextArgIsComptimeElided()) { - i += 1; - continue; - } - - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - - try sema.params.append(sema.gpa, .{ - .name = param_name, - .ty = Type.initTag(.noreturn), - .is_comptime = false, - }); - break :blk try sema.addConstUndef(Type.initTag(.@"undefined")); - }, - .param_anytype_comptime => blk: { - const inst_data = sema.code.instructions.items(.data)[inst].str_tok; - const param_name = inst_data.get(sema.code); - - if (sema.nextArgIsComptimeElided()) { - i += 1; - continue; - } - - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - - try sema.params.append(sema.gpa, .{ - .name = param_name, - .ty = Type.initTag(.noreturn), - .is_comptime = true, - }); - break :blk try sema.addConstUndef(Type.initTag(.@"undefined")); - }, }; if (sema.typeOf(air_inst).isNoReturn()) return always_noreturn; @@ -697,6 +629,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); @@ -714,6 +647,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, } } @@ -2422,7 +2356,7 @@ 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; @@ -2444,22 +2378,22 @@ fn analyzeCall( 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 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 mod.fail( &block.base, func_src, "expected {d} argument(s), found {d}", - .{ fn_params_len, args.len }, + .{ fn_params_len, uncasted_args.len }, ); } @@ -2485,6 +2419,14 @@ fn analyzeCall( const is_inline_call = is_comptime_call or modifier == .always_inline or 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, @@ -2574,13 +2516,12 @@ fn analyzeCall( 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 `args` rather than doing the work below to create a generic Scope - // only to junk it if it matches an existing instantiation. + // 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. // TODO const fn_info = sema.code.getFnInfo(module_fn.zir_body_inst); const zir_tags = sema.code.instructions.items(.tag); - var non_comptime_args_len: u32 = 0; const new_func = new_func: { const namespace = module_fn.owner_decl.namespace; try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); @@ -2622,7 +2563,8 @@ fn analyzeCall( .namespace = namespace, .func = null, .owner_func = null, - .comptime_args = try new_decl_arena.allocator.alloc(TypedValue, args.len), + .comptime_args = try new_decl_arena.allocator.alloc(TypedValue, uncasted_args.len), + .comptime_args_fn_inst = module_fn.zir_body_inst, }; defer child_sema.deinit(); @@ -2634,41 +2576,59 @@ fn analyzeCall( .inlining = null, .is_comptime = true, }; - defer child_block.instructions.deinit(gpa); + defer { + child_block.instructions.deinit(gpa); + child_block.params.deinit(gpa); + } - try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, args.len)); + 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, // TODO make true for always comptime types + .param, .param_anytype => false, else => continue, }; - if (is_comptime) { - // TODO: pass .unneeded to resolveConstValue and then if we get - // error.NeededSourceLocation resolve the arg source location and - // try again. - const arg_src = call_src; - const arg = args[arg_i]; - const arg_val = try sema.resolveConstValue(block, arg_src, arg); - child_sema.comptime_args[arg_i] = .{ - .ty = try sema.typeOf(arg).copy(&new_decl_arena.allocator), - .val = try arg_val.copy(&new_decl_arena.allocator), - }; + // TODO: pass .unneeded to resolveConstValue and then if we get + // error.NeededSourceLocation resolve the arg source location and + // try again. + const arg_src = call_src; + 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 { - non_comptime_args_len += 1; + } 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; + + 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; } - 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; // 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); @@ -2690,31 +2650,72 @@ fn analyzeCall( // Make a runtime call to the new function, making sure to omit the comptime args. try sema.requireRuntimeBlock(block, call_src); + const new_func_val = sema.resolveConstValue(block, .unneeded, new_func) catch unreachable; + const new_module_func = new_func_val.castTag(.function).?.data; + const comptime_args = new_module_func.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 = new_module_func.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(gpa, @typeInfo(Air.Call).Struct.fields.len + - non_comptime_args_len); + runtime_args_len); const func_inst = try block.addInst(.{ .tag = .call, .data = .{ .pl_op = .{ .operand = new_func, .payload = sema.addExtraAssumeCapacity(Air.Call{ - .args_len = non_comptime_args_len, + .args_len = runtime_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, // TODO make true for always comptime types - else => continue, - }; - if (is_comptime) { - sema.air_extra.appendAssumeCapacity(@enumToInt(args[arg_i])); - } - arg_i += 1; - } + sema.appendRefsAssumeCapacity(runtime_args); break :res func_inst; } else res: { + 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); @@ -3416,7 +3417,7 @@ fn funcCommon( const fn_ty: Type = fn_ty: { // Hot path for some common function types. - if (sema.params.items.len == 0 and !var_args and align_val.tag() == .null_value and + if (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) { @@ -3436,19 +3437,15 @@ fn funcCommon( } } - var any_are_comptime = false; - const param_types = try sema.arena.alloc(Type, sema.params.items.len); - const comptime_params = try sema.arena.alloc(bool, sema.params.items.len); - for (sema.params.items) |param, i| { - if (param.ty.tag() == .noreturn) { - param_types[i] = Type.initTag(.noreturn); // indicates anytype - } else { - param_types[i] = param.ty; - } + var is_generic = false; + 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; - any_are_comptime = any_are_comptime or param.is_comptime; + is_generic = is_generic or param.is_comptime or + param.ty.tag() == .generic_poison or param.ty.requiresComptime(); } - sema.params.clearRetainingCapacity(); if (align_val.tag() != .null_value) { return mod.fail(&block.base, src, "TODO implement support for function prototypes to have alignment specified", .{}); @@ -3471,7 +3468,7 @@ fn funcCommon( .return_type = return_type, .cc = cc, .is_var_args = var_args, - .is_generic = any_are_comptime, + .is_generic = is_generic, }); }; @@ -3530,12 +3527,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 = if (sema.comptime_args.len == 0) null else sema.comptime_args.ptr, + .comptime_args = comptime_args, .lbrace_line = src_locs.lbrace_line, .rbrace_line = src_locs.rbrace_line, .lbrace_column = @truncate(u16, src_locs.columns), @@ -3548,6 +3549,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, + }); + 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(); @@ -7618,8 +7726,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); @@ -8820,6 +8930,7 @@ fn typeHasOnePossibleValue( .inferred_alloc_const => unreachable, .inferred_alloc_mut => unreachable, + .generic_poison => unreachable, }; } @@ -8942,6 +9053,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, .{ @@ -9015,10 +9128,3 @@ fn isComptimeKnown( ) !bool { return (try sema.resolveMaybeUndefVal(block, src, inst)) != null; } - -fn nextArgIsComptimeElided(sema: *Sema) bool { - if (sema.comptime_args.len == 0) return false; - const result = sema.comptime_args[sema.next_arg_index].val.tag() != .unreachable_value; - sema.next_arg_index += 1; - return result; -} diff --git a/src/Zir.zig b/src/Zir.zig index 0b93208564..b4cbd9c875 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -1704,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, @@ -1731,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, _, @@ -1909,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), @@ -2006,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), + }, }); }; @@ -2787,10 +2804,12 @@ pub const Inst = struct { args: Ref, }; + /// Trailing: inst: Index // for every body_len pub const Param = struct { /// Null-terminated string index. name: u32, - ty: Ref, + /// The body contains the type of the parameter. + body_len: u32, }; /// Trailing: @@ -3348,11 +3367,16 @@ const Writer = struct { 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).data; + 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.name)), + std.zig.fmtEscapes(self.code.nullTerminatedString(extra.data.name)), }); - try self.writeInstRef(stream, extra.ty); + 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()); } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 8b3edcfe23..4a589ea66d 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -839,6 +839,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/type.zig b/src/type.zig index 237614e372..0e99a929f0 100644 --- a/src/type.zig +++ b/src/type.zig @@ -130,6 +130,7 @@ pub const Type = extern union { => return .Union, .var_args_param => unreachable, // can be any type + .generic_poison => unreachable, // must be handled earlier } } @@ -699,6 +700,7 @@ pub const Type = extern union { .export_options, .extern_options, .@"anyframe", + .generic_poison, => unreachable, .array_u8, @@ -1083,11 +1085,117 @@ 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. + 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", + => false, + + .type, + .comptime_int, + .comptime_float, + .enum_literal, + => true, + + .var_args_param => unreachable, + .inferred_alloc_mut => unreachable, + .inferred_alloc_const => unreachable, + .generic_poison => 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), @@ -1287,6 +1395,7 @@ pub const Type = extern union { .inferred_alloc_const => unreachable, .inferred_alloc_mut => unreachable, .var_args_param => unreachable, + .generic_poison => unreachable, }; } @@ -1509,6 +1618,8 @@ pub const Type = extern union { .@"opaque", .var_args_param, => unreachable, + + .generic_poison => unreachable, }; } @@ -1536,6 +1647,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; @@ -1702,6 +1814,7 @@ pub const Type = extern union { .inferred_alloc_mut => unreachable, .@"opaque" => unreachable, .var_args_param => unreachable, + .generic_poison => unreachable, .@"struct" => { @panic("TODO bitSize struct"); @@ -2626,6 +2739,7 @@ pub const Type = extern union { .inferred_alloc_const => unreachable, .inferred_alloc_mut => unreachable, + .generic_poison => unreachable, }; } @@ -3039,6 +3153,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, @@ -3136,6 +3251,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, diff --git a/src/value.zig b/src/value.zig index 134b51e494..bd1cc57416 100644 --- 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, }; } diff --git a/test/cases.zig b/test/cases.zig index 840ee7a4ac..a342b91a6f 100644 --- 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; -- cgit v1.2.3 From e9e3a2994696a3131125ebc4b1f0eec7ca5306d9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 5 Aug 2021 16:37:21 -0700 Subject: stage2: implement generic function memoization Module has a new field `monomorphed_funcs` which stores the set of `*Module.Fn` objects which are generic function instantiations. The hash is based on hashes of comptime values of parameters known to be comptime based on an explicit comptime keyword or must-be-comptime type expressions that can be evaluated without performing monomorphization. This allows function calls to be semantically analyzed cheaply for generic functions which are already instantiated. The table is updated with a single `getOrPutAdapted` in the semantic analysis of `call` instructions, by pre-allocating the `Fn` object and passing it to the child `Sema`. --- src/Module.zig | 44 ++++ src/Sema.zig | 460 ++++++++++++++++++++++++-------------- src/Zir.zig | 6 +- src/type.zig | 34 +-- src/value.zig | 145 ++++++------ test/behavior.zig | 3 +- test/behavior/basic.zig | 70 ++++++ test/behavior/generics.zig | 169 +------------- test/behavior/generics_stage1.zig | 169 ++++++++++++++ test/behavior/misc.zig | 72 +----- 10 files changed, 679 insertions(+), 493 deletions(-) create mode 100644 test/behavior/generics_stage1.zig (limited to 'src/Module.zig') diff --git a/src/Module.zig b/src/Module.zig index 2c3e745c11..2556ad3f0e 100644 --- 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. @@ -2205,6 +2248,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 { diff --git a/src/Sema.zig b/src/Sema.zig index dff11301db..0557587fb5 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -46,6 +46,12 @@ comptime_args: []TypedValue = &.{}, /// 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; @@ -2354,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, @@ -2524,193 +2564,192 @@ fn analyzeCall( // 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. - // TODO - 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_func = new_func: { - 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, - }; - 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, - }; - // TODO: pass .unneeded to resolveConstValue and then if we get - // error.NeededSourceLocation resolve the arg source location and - // try again. - const arg_src = call_src; - 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); + 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); + } } - 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; - - 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; + 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.*; + }; - // 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; + try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); - // 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 }); + // 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(); - try new_decl.finalizeNewArena(&new_decl_arena); - break :new_func try sema.analyzeDeclVal(block, func_src, new_decl); + 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); + } - // Save it into the Module's generic function map. - // TODO + 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); - // Make a runtime call to the new function, making sure to omit the comptime args. - try sema.requireRuntimeBlock(block, call_src); - const new_func_val = sema.resolveConstValue(block, .unneeded, new_func) catch unreachable; - const new_module_func = new_func_val.castTag(.function).?.data; - const comptime_args = new_module_func.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, - } + arg_i = 0; + for (fn_info.param_body) |inst| { + switch (zir_tags[inst]) { + .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, + else => continue, } - break :count count; - }; - const runtime_args = try sema.arena.alloc(Air.Inst.Ref, runtime_args_len); - { - const new_fn_ty = new_module_func.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; + 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; } - try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len + - runtime_args_len); - const func_inst = try block.addInst(.{ - .tag = .call, - .data = .{ .pl_op = .{ - .operand = new_func, - .payload = sema.addExtraAssumeCapacity(Air.Call{ - .args_len = runtime_args_len, - }), - } }, - }); - sema.appendRefsAssumeCapacity(runtime_args); - break :res func_inst; + + // 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; + + // 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); + + // 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 try sema.finishGenericCall( + block, + call_src, + new_module_func, + func_src, + uncasted_args, + fn_info, + zir_tags, + ); } else res: { const args = try sema.arena.alloc(Air.Inst.Ref, uncasted_args.len); for (uncasted_args) |uncasted_arg, i| { @@ -2745,6 +2784,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()); @@ -3419,7 +3527,15 @@ fn funcCommon( 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: { @@ -3620,7 +3736,7 @@ fn zirParam( try block.params.append(sema.gpa, .{ .ty = param_ty, - .is_comptime = is_comptime, + .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); diff --git a/src/Zir.zig b/src/Zir.zig index b4cbd9c875..862af7b033 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -4930,11 +4930,13 @@ fn findDeclsBody( } } -pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) struct { +pub const FnInfo = struct { param_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 { diff --git a/src/type.zig b/src/type.zig index 0e99a929f0..180fb92bf0 100644 --- a/src/type.zig +++ b/src/type.zig @@ -549,8 +549,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, @@ -568,34 +573,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, @@ -612,7 +617,6 @@ pub const Type = extern union { // TODO implement more type hashing }, } - return hasher.final(); } pub const HashContext64 = struct { @@ -3373,7 +3377,7 @@ pub const Type = extern union { data: Data, // TODO look into optimizing this memory to take fewer bytes - const Data = struct { + pub const Data = struct { param_types: []Type, comptime_params: [*]bool, return_type: Type, @@ -3381,7 +3385,7 @@ pub const Type = extern union { is_var_args: bool, is_generic: bool, - fn paramIsComptime(self: @This(), i: usize) 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]; diff --git a/src/value.zig b/src/value.zig index bd1cc57416..bf80c9d831 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1117,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); @@ -1132,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 index 4cc4d7fb8b..26272cb2fd 100644 --- 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 index ac1dc3889c..1372dfaeeb 100644 --- a/test/behavior/basic.zig +++ b/test/behavior/basic.zig @@ -92,3 +92,73 @@ fn first4KeysOfHomeRow() []const u8 { 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 index 104752607a..e3fed907df 100644 --- a/test/behavior/generics.zig +++ b/test/behavior/generics.zig @@ -3,167 +3,14 @@ const testing = std.testing; const expect = testing.expect; const expectEqual = testing.expectEqual; -test "simple generic fn" { - try expect(max(i32, 3, -1) == 3); - try expect(max(f32, 0.123, 0.456) == 0.456); - try expect(add(2, 3) == 5); +test "one param, explicit comptime" { + var x: usize = 0; + x += checkSize(i32); + x += checkSize(bool); + x += checkSize(bool); + try expect(x == 6); } -fn max(comptime T: type, a: T, b: T) T { - return if (a > b) a else b; -} - -fn add(comptime a: i32, b: i32) i32 { - return (comptime a) + b; -} - -const the_max = max(u32, 1234, 5678); -test "compile time generic eval" { - try expect(the_max == 5678); -} - -fn gimmeTheBigOne(a: u32, b: u32) u32 { - return max(u32, a, b); -} - -fn shouldCallSameInstance(a: u32, b: u32) u32 { - return max(u32, a, b); -} - -fn sameButWithFloats(a: f64, b: f64) f64 { - return max(f64, a, b); -} - -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); +fn checkSize(comptime T: type) usize { + return @sizeOf(T); } diff --git a/test/behavior/generics_stage1.zig b/test/behavior/generics_stage1.zig new file mode 100644 index 0000000000..104752607a --- /dev/null +++ b/test/behavior/generics_stage1.zig @@ -0,0 +1,169 @@ +const std = @import("std"); +const testing = std.testing; +const expect = testing.expect; +const expectEqual = testing.expectEqual; + +test "simple generic fn" { + try expect(max(i32, 3, -1) == 3); + 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; +} + +fn add(comptime a: i32, b: i32) i32 { + return (comptime a) + b; +} + +const the_max = max(u32, 1234, 5678); +test "compile time generic eval" { + try expect(the_max == 5678); +} + +fn gimmeTheBigOne(a: u32, b: u32) u32 { + return max(u32, a, b); +} + +fn shouldCallSameInstance(a: u32, b: u32) u32 { + return max(u32, a, b); +} + +fn sameButWithFloats(a: f64, b: f64) f64 { + return max(f64, a, b); +} + +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); +} diff --git a/test/behavior/misc.zig b/test/behavior/misc.zig index 8a0761dfd8..466be00bd3 100644 --- a/test/behavior/misc.zig +++ b/test/behavior/misc.zig @@ -40,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")); } @@ -62,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) @@ -169,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); -- cgit v1.2.3 From c03a04a58942446b48e9294df991a17a3a6f7b48 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 5 Aug 2021 19:15:59 -0700 Subject: stage2: return type expressions of generic functions * ZIR encoding for function instructions have a body for the return type. This lets Sema for generic functions do the same thing it does for parameters, handling `error.GenericPoison` in the evaluation of the return type by marking the function as generic. * Sema: fix missing block around the new Decl arena finalization. This led to a memory corruption. * Added some floating point support to the LLVM backend but didn't get far enough to pass any new tests. --- src/AstGen.zig | 70 +++++----- src/Module.zig | 3 + src/Sema.zig | 284 ++++++++++++++++++++++---------------- src/Zir.zig | 64 +++++++-- src/codegen/llvm.zig | 16 ++- src/codegen/llvm/bindings.zig | 18 +++ src/print_air.zig | 2 +- test/behavior/generics.zig | 56 ++++++++ test/behavior/generics_stage1.zig | 39 +----- 9 files changed, 344 insertions(+), 208 deletions(-) (limited to 'src/Module.zig') diff --git a/src/AstGen.zig b/src/AstGen.zig index 9886e4d809..febe581e35 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1041,6 +1041,7 @@ fn fnProtoExpr( fn_proto: ast.full.FnProto, ) InnerError!Zir.Inst.Ref { const astgen = gz.astgen; + const gpa = astgen.gpa; const tree = astgen.tree; const token_tags = tree.tokens.items(.tag); @@ -1083,7 +1084,6 @@ fn fnProtoExpr( .param_anytype; _ = try gz.addStrTok(tag, param_name, name_token); } else { - const gpa = astgen.gpa; const param_type_node = param.type_expr; assert(param_type_node != 0); var param_gz = gz.makeSubBlock(scope); @@ -1113,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 }, @@ -1133,7 +1131,8 @@ fn fnProtoExpr( const result = try gz.addFunc(.{ .src_node = fn_proto.ast.proto_node, .param_block = 0, - .ret_ty = return_type_inst, + .ret_ty = ret_gz.instructions.items, + .ret_br = ret_br, .body = &[0]Zir.Inst.Index{}, .cc = cc, .align_inst = align_inst, @@ -3005,12 +3004,10 @@ fn fnDecl( break :inst try comptimeExpr(&decl_gz, params_scope, .{ .ty = .const_slice_u8_type }, fn_proto.ast.section_expr); }; - const return_type_inst = try AstGen.expr( - &decl_gz, - params_scope, - .{ .ty = .type_type }, - fn_proto.ast.return_type, - ); + var ret_gz = gz.makeSubBlock(params_scope); + defer ret_gz.instructions.deinit(gpa); + const ret_ty = try expr(&decl_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) { @@ -3021,7 +3018,7 @@ fn fnDecl( .{}, ); } - break :blk try AstGen.expr( + break :blk try expr( &decl_gz, params_scope, .{ .ty = .calling_convention_type }, @@ -3046,7 +3043,8 @@ fn fnDecl( } break :func try decl_gz.addFunc(.{ .src_node = decl_node, - .ret_ty = return_type_inst, + .ret_ty = ret_gz.instructions.items, + .ret_br = ret_br, .param_block = block_inst, .body = &[0]Zir.Inst.Index{}, .cc = cc, @@ -3085,7 +3083,8 @@ fn fnDecl( break :func try decl_gz.addFunc(.{ .src_node = decl_node, .param_block = block_inst, - .ret_ty = return_type_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 @@ -3430,7 +3429,8 @@ fn testDecl( const func_inst = try decl_block.addFunc(.{ .src_node = node, .param_block = block_inst, - .ret_ty = .void_type, + .ret_ty = &.{}, + .ret_br = 0, .body = fn_block.instructions.items, .cc = .none, .align_inst = .none, @@ -9127,7 +9127,8 @@ const GenZir = struct { src_node: ast.Node.Index, body: []const Zir.Inst.Index, param_block: Zir.Inst.Index, - ret_ty: Zir.Inst.Ref, + ret_ty: []const Zir.Inst.Index, + ret_br: Zir.Inst.Index, cc: Zir.Inst.Ref, align_inst: Zir.Inst.Ref, lib_name: u32, @@ -9137,7 +9138,6 @@ const GenZir = struct { is_extern: bool, }) !Zir.Inst.Ref { assert(args.src_node != 0); - assert(args.ret_ty != .none); const astgen = gz.astgen; const gpa = astgen.gpa; @@ -9179,7 +9179,7 @@ const GenZir = struct { try astgen.extra.ensureUnusedCapacity( gpa, @typeInfo(Zir.Inst.ExtendedFunc).Struct.fields.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), @@ -9187,7 +9187,7 @@ const GenZir = struct { const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.ExtendedFunc{ .src_node = gz.nodeIndexToRelative(args.src_node), .param_block = args.param_block, - .return_type = args.ret_ty, + .ret_body_len = @intCast(u32, args.ret_ty.len), .body_len = @intCast(u32, args.body.len), }); if (args.lib_name != 0) { @@ -9199,10 +9199,14 @@ const GenZir = struct { if (args.align_inst != .none) { astgen.extra.appendAssumeCapacity(@enumToInt(args.align_inst)); } + 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 = .{ @@ -9222,23 +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.body.len + src_locs.len, + args.ret_ty.len + args.body.len + src_locs.len, ); - const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Func{ + const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.Func{ .param_block = args.param_block, - .return_type = args.ret_ty, + .ret_body_len = @intCast(u32, args.ret_ty.len), .body_len = @intCast(u32, args.body.len), }); - 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), diff --git a/src/Module.zig b/src/Module.zig index 2556ad3f0e..da11bc1c3c 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -842,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; diff --git a/src/Sema.zig b/src/Sema.zig index 0557587fb5..73f22aa845 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2618,128 +2618,130 @@ fn analyzeCall( 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); + { + 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); } - 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, + 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 arg = child_sema.inst_map.get(inst).?; - const arg_val = (child_sema.resolveMaybeUndefValAllowVariables(&child_block, .unneeded, arg) catch unreachable).?; + 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), - }; + 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; } - 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; - // 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; + // 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); - // 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 }); + // 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); - - // 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); + try new_decl.finalizeNewArena(&new_decl_arena); + } break :res try sema.finishGenericCall( block, @@ -3478,12 +3480,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); + 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.body_len; + extra_index += extra.data.body_len; src_locs = sema.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data; } @@ -3496,7 +3501,7 @@ fn zirFunc( block, inst_data.src_node, body_inst, - extra.data.return_type, + ret_ty_body, cc, Value.initTag(.null_value), false, @@ -3512,7 +3517,7 @@ fn funcCommon( block: *Scope.Block, src_node_offset: i32, 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, @@ -3523,7 +3528,37 @@ fn funcCommon( ) 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; @@ -3540,8 +3575,9 @@ fn funcCommon( const fn_ty: Type = fn_ty: { // Hot path for some common function types. - if (block.params.items.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); @@ -3560,7 +3596,6 @@ fn funcCommon( } } - var is_generic = false; 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| { @@ -3574,7 +3609,9 @@ fn funcCommon( 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: { + 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 = .{}, @@ -6944,6 +6981,9 @@ fn zirFuncExtended( break :blk align_tv.val; } else Value.initTag(.null_value); + 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) { @@ -6960,7 +7000,7 @@ fn zirFuncExtended( block, extra.data.src_node, body_inst, - extra.data.return_type, + ret_ty_body, cc, align_val, is_var_args, diff --git a/src/Zir.zig b/src/Zir.zig index 862af7b033..094cef3393 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -2272,11 +2272,13 @@ 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. body: Index // for each body_len - /// 4. 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, + /// 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, @@ -2312,10 +2314,12 @@ pub const Inst = struct { }; /// Trailing: - /// 0. body: Index // for each body_len - /// 1. src_locs: SrcLocs // if body_len != 0 + /// 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, + /// 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, @@ -4344,15 +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 body = self.code.extra[extra.end..][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 + body.len; src_locs = self.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data; } return self.writeFuncCommon( stream, - extra.data.return_type, + ret_ty_body, inferred_error_set, false, false, @@ -4387,6 +4397,9 @@ const Writer = struct { break :blk align_inst; }; + 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; @@ -4396,7 +4409,7 @@ const Writer = struct { } return self.writeFuncCommon( stream, - extra.data.return_type, + ret_ty_body, small.is_inferred_error, small.is_var_args, small.is_extern, @@ -4478,7 +4491,7 @@ const Writer = struct { fn writeFuncCommon( self: *Writer, stream: anytype, - ret_ty: Inst.Ref, + ret_ty_body: []const Inst.Index, inferred_error_set: bool, var_args: bool, is_extern: bool, @@ -4488,7 +4501,13 @@ const Writer = struct { src: LazySrcLoc, src_locs: Zir.Inst.Func.SrcLocs, ) !void { - try self.writeInstRef(stream, ret_ty); + 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 self.writeOptionalInstRef(stream, ", cc=", cc); try self.writeOptionalInstRef(stream, ", align=", align_inst); try self.writeFlag(stream, ", vargs", var_args); @@ -4496,9 +4515,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; @@ -4932,6 +4951,7 @@ fn findDeclsBody( pub const FnInfo = struct { param_body: []const Inst.Index, + ret_ty_body: []const Inst.Index, body: []const Inst.Index, total_params_len: u32, }; @@ -4942,13 +4962,22 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { 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); - const body = zir.extra[extra.end..][0..extra.data.body_len]; + 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, }; }, @@ -4961,9 +4990,13 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { 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, }; }, @@ -4983,6 +5016,7 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { } 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 index 0e73469687..91b48ffcfb 100644 --- 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,6 +706,13 @@ 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 => { if (tv.ty.isSlice()) { diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 4af3cadd84..675c5539fb 100644 --- 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 index 00317b26e8..11f2982fc3 100644 --- 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/test/behavior/generics.zig b/test/behavior/generics.zig index e3fed907df..a3c5668b39 100644 --- a/test/behavior/generics.zig +++ b/test/behavior/generics.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const testing = std.testing; const expect = testing.expect; const expectEqual = testing.expectEqual; @@ -14,3 +15,58 @@ test "one param, explicit comptime" { fn checkSize(comptime T: type) usize { return @sizeOf(T); } + +test "simple generic fn" { + try expect(max(i32, 3, -1) == 3); + 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 { + 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 { + return (comptime a) + b; +} + +const the_max = max(u32, 1234, 5678); +test "compile time generic eval" { + try expect(the_max == 5678); +} + +fn gimmeTheBigOne(a: u32, b: u32) u32 { + return max(u32, a, b); +} + +fn shouldCallSameInstance(a: u32, b: u32) u32 { + return max(u32, a, b); +} + +fn sameButWithFloats(a: f64, b: f64) f64 { + return max(f64, a, b); +} + +test "fn with comptime args" { + try expect(gimmeTheBigOne(1234, 5678) == 5678); + try expect(shouldCallSameInstance(34, 12) == 34); + 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 index 104752607a..c9f274c7c1 100644 --- a/test/behavior/generics_stage1.zig +++ b/test/behavior/generics_stage1.zig @@ -3,44 +3,7 @@ const testing = std.testing; const expect = testing.expect; const expectEqual = testing.expectEqual; -test "simple generic fn" { - try expect(max(i32, 3, -1) == 3); - 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; -} - -fn add(comptime a: i32, b: i32) i32 { - return (comptime a) + b; -} - -const the_max = max(u32, 1234, 5678); -test "compile time generic eval" { - try expect(the_max == 5678); -} - -fn gimmeTheBigOne(a: u32, b: u32) u32 { - return max(u32, a, b); -} - -fn shouldCallSameInstance(a: u32, b: u32) u32 { - return max(u32, a, b); -} - -fn sameButWithFloats(a: f64, b: f64) f64 { - return max(f64, a, b); -} - -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" { +test "anytype params" { try expect(max_i32(12, 34) == 34); try expect(max_f64(1.2, 3.4) == 3.4); } -- cgit v1.2.3 From c7dc451a2a06a0ade0bb44a48cb6e5cde6e237df Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 5 Aug 2021 23:20:53 -0700 Subject: stage2: more debuggable panics For now these errors are handled via `@panic` rather than `unreachable`. These are relatively likely bugs to occur at this early stage of development, and handling them as panics lets us ship release builds of the compiler without worrying about undefined behavior. Furthermore, in stage1, `@panic` is implemented to include an error return trace, while `unreachable` is not. In this case, the error return traces are extremely helpful in debugging the compiler. --- src/Module.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/Module.zig') diff --git a/src/Module.zig b/src/Module.zig index da11bc1c3c..a694f775da 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3792,8 +3792,8 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { log.debug("set {s} to in_progress", .{decl.name}); _ = sema.analyzeBody(&inner_block, fn_info.body) catch |err| switch (err) { - error.NeededSourceLocation => unreachable, - error.GenericPoison => unreachable, + error.NeededSourceLocation => @panic("zig compiler bug: NeededSourceLocation"), + error.GenericPoison => @panic("zig compiler bug: GenericPoison"), else => |e| return e, }; -- cgit v1.2.3