diff options
Diffstat (limited to 'src/Module.zig')
| -rw-r--r-- | src/Module.zig | 686 |
1 files changed, 272 insertions, 414 deletions
diff --git a/src/Module.zig b/src/Module.zig index 96be13e768..a91a24987d 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -101,16 +101,6 @@ tmp_hack_arena: std.heap.ArenaAllocator, /// This is currently only used for string literals. memoized_decls: std.AutoHashMapUnmanaged(InternPool.Index, Decl.Index) = .{}, -monomorphed_func_keys: std.ArrayListUnmanaged(InternPool.Index) = .{}, -/// 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 = .{}, -/// Contains the values from `@setAlignStack`. A sparse table is used here -/// instead of a field of `Fn` because usage of `@setAlignStack` is rare, while -/// functions are many. -align_stack_fns: std.AutoHashMapUnmanaged(Fn.Index, SetAlignStack) = .{}, - /// 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. @@ -189,7 +179,8 @@ reference_table: std.AutoHashMapUnmanaged(Decl.Index, struct { }) = .{}, panic_messages: [PanicId.len]Decl.OptionalIndex = .{.none} ** PanicId.len, -panic_func_index: Fn.OptionalIndex = .none, +/// The panic function body. +panic_func_index: InternPool.Index = .none, null_stack_trace: InternPool.Index = .none, pub const PanicId = enum { @@ -239,50 +230,6 @@ pub const CImportError = struct { } }; -pub const MonomorphedFuncKey = struct { func: Fn.Index, args_index: u32, args_len: u32 }; - -pub const MonomorphedFuncAdaptedKey = struct { func: Fn.Index, args: []const InternPool.Index }; - -pub const MonomorphedFuncsSet = std.HashMapUnmanaged( - MonomorphedFuncKey, - InternPool.Index, - MonomorphedFuncsContext, - std.hash_map.default_max_load_percentage, -); - -pub const MonomorphedFuncsContext = struct { - mod: *Module, - - pub fn eql(_: @This(), a: MonomorphedFuncKey, b: MonomorphedFuncKey) bool { - return std.meta.eql(a, b); - } - - pub fn hash(ctx: @This(), key: MonomorphedFuncKey) u64 { - const key_args = ctx.mod.monomorphed_func_keys.items[key.args_index..][0..key.args_len]; - return std.hash.Wyhash.hash(@intFromEnum(key.func), std.mem.sliceAsBytes(key_args)); - } -}; - -pub const MonomorphedFuncsAdaptedContext = struct { - mod: *Module, - - pub fn eql(ctx: @This(), adapted_key: MonomorphedFuncAdaptedKey, other_key: MonomorphedFuncKey) bool { - const other_key_args = ctx.mod.monomorphed_func_keys.items[other_key.args_index..][0..other_key.args_len]; - return adapted_key.func == other_key.func and std.mem.eql(InternPool.Index, adapted_key.args, other_key_args); - } - - pub fn hash(_: @This(), adapted_key: MonomorphedFuncAdaptedKey) u64 { - return std.hash.Wyhash.hash(@intFromEnum(adapted_key.func), std.mem.sliceAsBytes(adapted_key.args)); - } -}; - -pub const SetAlignStack = struct { - alignment: Alignment, - /// TODO: This needs to store a non-lazy source location for the case of an inline function - /// which does `@setAlignStack` (applying it to the caller). - src: LazySrcLoc, -}; - /// A `Module` has zero or one of these depending on whether `-femit-h` is enabled. pub const GlobalEmitH = struct { /// Where to put the output. @@ -625,13 +572,6 @@ pub const Decl = struct { function_body, }; - pub fn clearValues(decl: *Decl, mod: *Module) void { - if (decl.getOwnedFunctionIndex(mod).unwrap()) |func| { - _ = mod.align_stack_fns.remove(func); - mod.destroyFunc(func); - } - } - /// This name is relative to the containing namespace of the decl. /// The memory is owned by the containing File ZIR. pub fn getName(decl: Decl, mod: *Module) ?[:0]const u8 { @@ -816,14 +756,17 @@ pub const Decl = struct { return mod.typeToUnion(decl.val.toType()); } - /// If the Decl owns its value and it is a function, return it, - /// otherwise null. - pub fn getOwnedFunction(decl: Decl, mod: *Module) ?*Fn { - return mod.funcPtrUnwrap(decl.getOwnedFunctionIndex(mod)); + pub fn getOwnedFunction(decl: Decl, mod: *Module) ?InternPool.Key.Func { + const i = decl.getOwnedFunctionIndex(); + if (i == .none) return null; + return switch (mod.intern_pool.indexToKey(i)) { + .func => |func| func, + else => null, + }; } - pub fn getOwnedFunctionIndex(decl: Decl, mod: *Module) Fn.OptionalIndex { - return if (decl.owns_tv) decl.val.getFunctionIndex(mod) else .none; + pub fn getOwnedFunctionIndex(decl: Decl) InternPool.Index { + return if (decl.owns_tv) decl.val.toIntern() else .none; } /// If the Decl owns its value and it is an extern function, returns it, @@ -1385,71 +1328,39 @@ pub const ExternFn = struct { } }; -/// Some Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator. -/// Extern functions do not have this data structure; they are represented by `ExternFn` -/// instead. -pub const Fn = struct { - /// The Decl that corresponds to the function itself. - owner_decl: Decl.Index, - /// 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 - /// ZIR instruction. - zir_body_inst: Zir.Inst.Index, - /// If this is not null, this function is a generic function instantiation, and - /// there is a `TypedValue` here for each parameter of the function. - /// Non-comptime parameters are marked with a `generic_poison` for the value. - /// Non-anytype parameters are marked with a `generic_poison` for the type. - /// These never have .generic_poison for the Type - /// because the Type is needed to pass to `Type.eql` and for inserting comptime arguments - /// into the inst_map when analyzing the body of a generic function instantiation. - /// Instead, the is_anytype knowledge is communicated via `isAnytypeParam`. - comptime_args: ?[*]TypedValue, - - /// Precomputed hash for monomorphed_funcs. - /// This is important because it may be accessed when resizing monomorphed_funcs - /// while this Fn has already been added to the set, but does not have the - /// owner_decl, comptime_args, or other fields populated yet. - /// This field is undefined if comptime_args == null. - hash: u64, - - /// Relative to owner Decl. - lbrace_line: u32, - /// Relative to owner Decl. - rbrace_line: u32, - lbrace_column: u16, - rbrace_column: u16, - - /// When a generic function is instantiated, this value is inherited from the - /// active Sema context. Importantly, this value is also updated when an existing - /// generic function instantiation is found and called. - branch_quota: u32, - - /// If this is not none, this function is a generic function instantiation, and - /// this is the generic function decl from which the instance was derived. - /// This information is redundant with a combination of checking if comptime_args is - /// not null and looking at the first decl dependency of owner_decl. This redundant - /// information is useful for three reasons: - /// 1. Improved perf of monomorphed_funcs when checking the eql() function because it - /// can do two fewer pointer chases by grabbing the info from this field directly - /// instead of accessing the decl and then the dependencies set. - /// 2. While a generic function instantiation is being initialized, we need hash() - /// and eql() to work before the initialization is complete. Completing the - /// insertion into the decl dependency set has more fallible operations than simply - /// setting this field. - /// 3. I forgot what the third thing was while typing up the other two. - generic_owner_decl: Decl.OptionalIndex, - - state: Analysis, - is_cold: bool = false, - is_noinline: bool, - calls_or_awaits_errorable_fn: bool = false, +/// This struct is used to keep track of any dependencies related to functions instances +/// that return inferred error sets. Note that a function may be associated to +/// multiple different error sets, for example an inferred error set which +/// this function returns, but also any inferred error sets of called inline +/// or comptime functions. +pub const InferredErrorSet = struct { + /// The function from which this error set originates. + func: InternPool.Index, + + /// All currently known errors that this error set contains. This includes + /// direct additions via `return error.Foo;`, and possibly also errors that + /// are returned from any dependent functions. When the inferred error set is + /// fully resolved, this map contains all the errors that the function might return. + errors: NameMap = .{}, + + /// Other inferred error sets which this inferred error set should include. + inferred_error_sets: std.AutoArrayHashMapUnmanaged(InferredErrorSet.Index, void) = .{}, + + /// Whether the function returned anyerror. This is true if either of + /// the dependent functions returns anyerror. + is_anyerror: bool = false, + + /// Whether this error set is already fully resolved. If true, resolving + /// can skip resolving any dependents of this inferred error set. + is_resolved: bool = false, + + pub const NameMap = std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void); pub const Index = enum(u32) { _, - pub fn toOptional(i: Index) OptionalIndex { - return @as(OptionalIndex, @enumFromInt(@intFromEnum(i))); + pub fn toOptional(i: InferredErrorSet.Index) InferredErrorSet.OptionalIndex { + return @as(InferredErrorSet.OptionalIndex, @enumFromInt(@intFromEnum(i))); } }; @@ -1457,159 +1368,37 @@ pub const Fn = struct { none = std.math.maxInt(u32), _, - pub fn init(oi: ?Index) OptionalIndex { - return @as(OptionalIndex, @enumFromInt(@intFromEnum(oi orelse return .none))); + pub fn init(oi: ?InferredErrorSet.Index) InferredErrorSet.OptionalIndex { + return @as(InferredErrorSet.OptionalIndex, @enumFromInt(@intFromEnum(oi orelse return .none))); } - pub fn unwrap(oi: OptionalIndex) ?Index { + pub fn unwrap(oi: InferredErrorSet.OptionalIndex) ?InferredErrorSet.Index { if (oi == .none) return null; - return @as(Index, @enumFromInt(@intFromEnum(oi))); + return @as(InferredErrorSet.Index, @enumFromInt(@intFromEnum(oi))); } }; - pub const Analysis = enum { - /// This function has not yet undergone analysis, because we have not - /// seen a potential runtime call. It may be analyzed in future. - none, - /// Analysis for this function has been queued, but not yet completed. - queued, - /// This function intentionally only has ZIR generated because it is marked - /// inline, which means no runtime version of the function will be generated. - inline_only, - in_progress, - /// There will be a corresponding ErrorMsg in Module.failed_decls - sema_failure, - /// This Fn might be OK but it depends on another Decl which did not - /// successfully complete semantic analysis. - dependency_failure, - success, - }; - - /// This struct is used to keep track of any dependencies related to functions instances - /// that return inferred error sets. Note that a function may be associated to - /// multiple different error sets, for example an inferred error set which - /// this function returns, but also any inferred error sets of called inline - /// or comptime functions. - pub const InferredErrorSet = struct { - /// The function from which this error set originates. - func: Fn.Index, - - /// All currently known errors that this error set contains. This includes - /// direct additions via `return error.Foo;`, and possibly also errors that - /// are returned from any dependent functions. When the inferred error set is - /// fully resolved, this map contains all the errors that the function might return. - errors: NameMap = .{}, - - /// Other inferred error sets which this inferred error set should include. - inferred_error_sets: std.AutoArrayHashMapUnmanaged(InferredErrorSet.Index, void) = .{}, - - /// Whether the function returned anyerror. This is true if either of - /// the dependent functions returns anyerror. - is_anyerror: bool = false, - - /// Whether this error set is already fully resolved. If true, resolving - /// can skip resolving any dependents of this inferred error set. - is_resolved: bool = false, - - pub const NameMap = std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void); - - pub const Index = enum(u32) { - _, - - pub fn toOptional(i: InferredErrorSet.Index) InferredErrorSet.OptionalIndex { - return @as(InferredErrorSet.OptionalIndex, @enumFromInt(@intFromEnum(i))); - } - }; - - pub const OptionalIndex = enum(u32) { - none = std.math.maxInt(u32), - _, - - pub fn init(oi: ?InferredErrorSet.Index) InferredErrorSet.OptionalIndex { - return @as(InferredErrorSet.OptionalIndex, @enumFromInt(@intFromEnum(oi orelse return .none))); - } - - pub fn unwrap(oi: InferredErrorSet.OptionalIndex) ?InferredErrorSet.Index { - if (oi == .none) return null; - return @as(InferredErrorSet.Index, @enumFromInt(@intFromEnum(oi))); - } - }; - - pub fn addErrorSet( - self: *InferredErrorSet, - err_set_ty: Type, - ip: *InternPool, - gpa: Allocator, - ) !void { - switch (err_set_ty.toIntern()) { - .anyerror_type => { - self.is_anyerror = true; + pub fn addErrorSet( + self: *InferredErrorSet, + err_set_ty: Type, + ip: *InternPool, + gpa: Allocator, + ) !void { + switch (err_set_ty.toIntern()) { + .anyerror_type => { + self.is_anyerror = true; + }, + else => switch (ip.indexToKey(err_set_ty.toIntern())) { + .error_set_type => |error_set_type| { + for (error_set_type.names) |name| { + try self.errors.put(gpa, name, {}); + } }, - else => switch (ip.indexToKey(err_set_ty.toIntern())) { - .error_set_type => |error_set_type| { - for (error_set_type.names) |name| { - try self.errors.put(gpa, name, {}); - } - }, - .inferred_error_set_type => |ies_index| { - try self.inferred_error_sets.put(gpa, ies_index, {}); - }, - else => unreachable, + .inferred_error_set_type => |ies_index| { + try self.inferred_error_sets.put(gpa, ies_index, {}); }, - } - } - }; - - pub fn isAnytypeParam(func: Fn, mod: *Module, index: u32) bool { - const file = mod.declPtr(func.owner_decl).getFileScope(mod); - - const tags = file.zir.instructions.items(.tag); - - const param_body = file.zir.getParamBody(func.zir_body_inst); - const param = param_body[index]; - - return switch (tags[param]) { - .param, .param_comptime => false, - .param_anytype, .param_anytype_comptime => true, - else => unreachable, - }; - } - - pub fn getParamName(func: Fn, mod: *Module, index: u32) [:0]const u8 { - const file = mod.declPtr(func.owner_decl).getFileScope(mod); - - const tags = file.zir.instructions.items(.tag); - const data = file.zir.instructions.items(.data); - - const param_body = file.zir.getParamBody(func.zir_body_inst); - const param = param_body[index]; - - return switch (tags[param]) { - .param, .param_comptime => blk: { - const extra = file.zir.extraData(Zir.Inst.Param, data[param].pl_tok.payload_index); - break :blk file.zir.nullTerminatedString(extra.data.name); - }, - .param_anytype, .param_anytype_comptime => blk: { - const param_data = data[param].str_tok; - break :blk param_data.get(file.zir); - }, - else => unreachable, - }; - } - - pub fn hasInferredErrorSet(func: Fn, mod: *Module) bool { - const owner_decl = mod.declPtr(func.owner_decl); - const zir = owner_decl.getFileScope(mod).zir; - const zir_tags = zir.instructions.items(.tag); - switch (zir_tags[func.zir_body_inst]) { - .func => return false, - .func_inferred => return true, - .func_fancy => { - const inst_data = zir.instructions.items(.data)[func.zir_body_inst].pl_node; - const extra = zir.extraData(Zir.Inst.FuncFancy, inst_data.payload_index); - return extra.data.bits.is_inferred_error; + else => unreachable, }, - else => unreachable, } } }; @@ -2468,6 +2257,22 @@ pub const SrcLoc = struct { } } else unreachable; }, + .call_arg => |call_arg| { + const tree = try src_loc.file_scope.getTree(gpa); + const node = src_loc.declRelativeToNodeIndex(call_arg.call_node_offset); + var buf: [1]Ast.Node.Index = undefined; + const call_full = tree.fullCall(&buf, node).?; + const src_node = call_full.ast.params[call_arg.arg_index]; + return nodeToSpan(tree, src_node); + }, + .fn_proto_param => |fn_proto_param| { + const tree = try src_loc.file_scope.getTree(gpa); + const node = src_loc.declRelativeToNodeIndex(fn_proto_param.fn_proto_node_offset); + var buf: [1]Ast.Node.Index = undefined; + const fn_proto_full = tree.fullFnProto(&buf, node).?; + const src_node = fn_proto_full.ast.params[fn_proto_param.param_index]; + return nodeToSpan(tree, src_node); + }, .node_offset_bin_lhs => |node_off| { const tree = try src_loc.file_scope.getTree(gpa); const node = src_loc.declRelativeToNodeIndex(node_off); @@ -3146,6 +2951,20 @@ pub const LazySrcLoc = union(enum) { /// Next, navigate to the corresponding capture. /// The Decl is determined contextually. for_capture_from_input: i32, + /// The source location points to the argument node of a function call. + /// The Decl is determined contextually. + call_arg: struct { + /// Points to the function call AST node. + call_node_offset: i32, + /// The index of the argument the source location points to. + arg_index: u32, + }, + fn_proto_param: struct { + /// Points to the function prototype AST node. + fn_proto_node_offset: i32, + /// The index of the parameter the source location points to. + param_index: u32, + }, pub const nodeOffset = if (TracedOffset.want_tracing) nodeOffsetDebug else nodeOffsetRelease; @@ -3235,6 +3054,8 @@ pub const LazySrcLoc = union(enum) { .node_offset_store_operand, .for_input, .for_capture_from_input, + .call_arg, + .fn_proto_param, => .{ .file_scope = decl.getFileScope(mod), .parent_decl_node = decl.src_node, @@ -3373,8 +3194,6 @@ pub fn deinit(mod: *Module) void { mod.global_error_set.deinit(gpa); mod.test_functions.deinit(gpa); - mod.align_stack_fns.deinit(gpa); - mod.monomorphed_funcs.deinit(gpa); mod.decls_free_list.deinit(gpa); mod.allocated_decls.deinit(gpa); @@ -3407,7 +3226,6 @@ pub fn destroyDecl(mod: *Module, decl_index: Decl.Index) void { } } if (decl.src_scope) |scope| scope.decRef(gpa); - decl.clearValues(mod); decl.dependants.deinit(gpa); decl.dependencies.deinit(gpa); decl.* = undefined; @@ -3439,11 +3257,7 @@ pub fn structPtr(mod: *Module, index: Struct.Index) *Struct { return mod.intern_pool.structPtr(index); } -pub fn funcPtr(mod: *Module, index: Fn.Index) *Fn { - return mod.intern_pool.funcPtr(index); -} - -pub fn inferredErrorSetPtr(mod: *Module, index: Fn.InferredErrorSet.Index) *Fn.InferredErrorSet { +pub fn inferredErrorSetPtr(mod: *Module, index: InferredErrorSet.Index) *InferredErrorSet { return mod.intern_pool.inferredErrorSetPtr(index); } @@ -3457,10 +3271,6 @@ pub fn structPtrUnwrap(mod: *Module, index: Struct.OptionalIndex) ?*Struct { return mod.structPtr(index.unwrap() orelse return null); } -pub fn funcPtrUnwrap(mod: *Module, index: Fn.OptionalIndex) ?*Fn { - return mod.funcPtr(index.unwrap() orelse return null); -} - /// Returns true if and only if the Decl is the top level struct associated with a File. pub fn declIsRoot(mod: *Module, decl_index: Decl.Index) bool { const decl = mod.declPtr(decl_index); @@ -3881,6 +3691,8 @@ fn updateZirRefs(mod: *Module, file: *File, old_zir: Zir) !void { // to re-generate ZIR for the File. try file.outdated_decls.append(gpa, root_decl); + const ip = &mod.intern_pool; + while (decl_stack.popOrNull()) |decl_index| { const decl = mod.declPtr(decl_index); // Anonymous decls and the root decl have this set to 0. We still need @@ -3918,7 +3730,7 @@ fn updateZirRefs(mod: *Module, file: *File, old_zir: Zir) !void { } if (decl.getOwnedFunction(mod)) |func| { - func.zir_body_inst = inst_map.get(func.zir_body_inst) orelse { + func.zirBodyInst(ip).* = inst_map.get(func.zir_body_inst) orelse { try file.deleted_decls.append(gpa, decl_index); continue; }; @@ -4101,11 +3913,6 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void { // prior to re-analysis. try mod.deleteDeclExports(decl_index); - // Similarly, `@setAlignStack` invocations will be re-discovered. - if (decl.getOwnedFunctionIndex(mod).unwrap()) |func| { - _ = mod.align_stack_fns.remove(func); - } - // Dependencies will be re-discovered, so we remove them here prior to re-analysis. for (decl.dependencies.keys()) |dep_index| { const dep = mod.declPtr(dep_index); @@ -4189,11 +3996,12 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void { } } -pub fn ensureFuncBodyAnalyzed(mod: *Module, func_index: Fn.Index) SemaError!void { +pub fn ensureFuncBodyAnalyzed(mod: *Module, func_index: InternPool.Index) SemaError!void { const tracy = trace(@src()); defer tracy.end(); - const func = mod.funcPtr(func_index); + const ip = &mod.intern_pool; + const func = mod.funcInfo(func_index); const decl_index = func.owner_decl; const decl = mod.declPtr(decl_index); @@ -4211,7 +4019,7 @@ pub fn ensureFuncBodyAnalyzed(mod: *Module, func_index: Fn.Index) SemaError!void => return error.AnalysisFail, .complete, .codegen_failure_retryable => { - switch (func.state) { + switch (func.analysis(ip).state) { .sema_failure, .dependency_failure => return error.AnalysisFail, .none, .queued => {}, .in_progress => unreachable, @@ -4227,11 +4035,11 @@ pub fn ensureFuncBodyAnalyzed(mod: *Module, func_index: Fn.Index) SemaError!void var air = mod.analyzeFnBody(func_index, sema_arena) catch |err| switch (err) { error.AnalysisFail => { - if (func.state == .in_progress) { + if (func.analysis(ip).state == .in_progress) { // If this decl caused the compile error, the analysis field would // be changed to indicate it was this Decl's fault. Because this // did not happen, we infer here that it was a dependency failure. - func.state = .dependency_failure; + func.analysis(ip).state = .dependency_failure; } return error.AnalysisFail; }, @@ -4251,14 +4059,14 @@ pub fn ensureFuncBodyAnalyzed(mod: *Module, func_index: Fn.Index) SemaError!void if (no_bin_file and !dump_air and !dump_llvm_ir) return; - var liveness = try Liveness.analyze(gpa, air, &mod.intern_pool); + var liveness = try Liveness.analyze(gpa, air, ip); defer liveness.deinit(gpa); if (dump_air) { const fqn = try decl.getFullyQualifiedName(mod); - std.debug.print("# Begin Function AIR: {}:\n", .{fqn.fmt(&mod.intern_pool)}); + std.debug.print("# Begin Function AIR: {}:\n", .{fqn.fmt(ip)}); @import("print_air.zig").dump(mod, air, liveness); - std.debug.print("# End Function AIR: {}\n\n", .{fqn.fmt(&mod.intern_pool)}); + std.debug.print("# End Function AIR: {}\n\n", .{fqn.fmt(ip)}); } if (std.debug.runtime_safety) { @@ -4266,7 +4074,7 @@ pub fn ensureFuncBodyAnalyzed(mod: *Module, func_index: Fn.Index) SemaError!void .gpa = gpa, .air = air, .liveness = liveness, - .intern_pool = &mod.intern_pool, + .intern_pool = ip, }; defer verify.deinit(); @@ -4321,8 +4129,9 @@ pub fn ensureFuncBodyAnalyzed(mod: *Module, func_index: Fn.Index) SemaError!void /// analyzed, and for ensuring it can exist at runtime (see /// `sema.fnHasRuntimeBits`). This function does *not* guarantee that the body /// will be analyzed when it returns: for that, see `ensureFuncBodyAnalyzed`. -pub fn ensureFuncBodyAnalysisQueued(mod: *Module, func_index: Fn.Index) !void { - const func = mod.funcPtr(func_index); +pub fn ensureFuncBodyAnalysisQueued(mod: *Module, func_index: InternPool.Index) !void { + const ip = &mod.intern_pool; + const func = mod.funcInfo(func_index); const decl_index = func.owner_decl; const decl = mod.declPtr(decl_index); @@ -4348,7 +4157,7 @@ pub fn ensureFuncBodyAnalysisQueued(mod: *Module, func_index: Fn.Index) !void { assert(decl.has_tv); - switch (func.state) { + switch (func.analysis(ip).state) { .none => {}, .queued => return, // As above, we don't need to forward errors here. @@ -4366,7 +4175,7 @@ pub fn ensureFuncBodyAnalysisQueued(mod: *Module, func_index: Fn.Index) !void { // since the last update try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl_index }); } - func.state = .queued; + func.analysis(ip).state = .queued; } pub fn updateEmbedFile(mod: *Module, embed_file: *EmbedFile) SemaError!void { @@ -4490,10 +4299,8 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void { .code = file.zir, .owner_decl = new_decl, .owner_decl_index = new_decl_index, - .func = null, .func_index = .none, .fn_ret_ty = Type.void, - .owner_func = null, .owner_func_index = .none, .comptime_mutable_decls = &comptime_mutable_decls, }; @@ -4573,10 +4380,8 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool { .code = zir, .owner_decl = decl, .owner_decl_index = decl_index, - .func = null, .func_index = .none, .fn_ret_ty = Type.void, - .owner_func = null, .owner_func_index = .none, .comptime_mutable_decls = &comptime_mutable_decls, }; @@ -4658,48 +4463,49 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool { return true; } - if (mod.intern_pool.indexToFunc(decl_tv.val.toIntern()).unwrap()) |func_index| { - const func = mod.funcPtr(func_index); - const owns_tv = func.owner_decl == decl_index; - if (owns_tv) { - var prev_type_has_bits = false; - var prev_is_inline = false; - var type_changed = true; - - if (decl.has_tv) { - prev_type_has_bits = decl.ty.isFnOrHasRuntimeBits(mod); - type_changed = !decl.ty.eql(decl_tv.ty, mod); - if (decl.getOwnedFunction(mod)) |prev_func| { - prev_is_inline = prev_func.state == .inline_only; + const ip = &mod.intern_pool; + switch (ip.indexToKey(decl_tv.val.toIntern())) { + .func => |func| { + const owns_tv = func.owner_decl == decl_index; + if (owns_tv) { + var prev_type_has_bits = false; + var prev_is_inline = false; + var type_changed = true; + + if (decl.has_tv) { + prev_type_has_bits = decl.ty.isFnOrHasRuntimeBits(mod); + type_changed = !decl.ty.eql(decl_tv.ty, mod); + if (decl.getOwnedFunction(mod)) |prev_func| { + prev_is_inline = prev_func.analysis(ip).state == .inline_only; + } } - } - decl.clearValues(mod); - - decl.ty = decl_tv.ty; - decl.val = (try decl_tv.val.intern(decl_tv.ty, mod)).toValue(); - // linksection, align, and addrspace were already set by Sema - decl.has_tv = true; - decl.owns_tv = owns_tv; - decl.analysis = .complete; - decl.generation = mod.generation; - - const is_inline = decl.ty.fnCallingConvention(mod) == .Inline; - if (decl.is_exported) { - const export_src: LazySrcLoc = .{ .token_offset = @intFromBool(decl.is_pub) }; - if (is_inline) { - return sema.fail(&block_scope, export_src, "export of inline function", .{}); + + decl.ty = decl_tv.ty; + decl.val = (try decl_tv.val.intern(decl_tv.ty, mod)).toValue(); + // linksection, align, and addrspace were already set by Sema + decl.has_tv = true; + decl.owns_tv = owns_tv; + decl.analysis = .complete; + decl.generation = mod.generation; + + const is_inline = decl.ty.fnCallingConvention(mod) == .Inline; + if (decl.is_exported) { + const export_src: LazySrcLoc = .{ .token_offset = @intFromBool(decl.is_pub) }; + if (is_inline) { + return sema.fail(&block_scope, export_src, "export of inline function", .{}); + } + // The scope needs to have the decl in it. + try sema.analyzeExport(&block_scope, export_src, .{ .name = decl.name }, decl_index); } - // The scope needs to have the decl in it. - try sema.analyzeExport(&block_scope, export_src, .{ .name = decl.name }, decl_index); + return type_changed or is_inline != prev_is_inline; } - return type_changed or is_inline != prev_is_inline; - } + }, + else => {}, } var type_changed = true; if (decl.has_tv) { type_changed = !decl.ty.eql(decl_tv.ty, mod); } - decl.clearValues(mod); decl.owns_tv = false; var queue_linker_work = false; @@ -4707,7 +4513,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool { switch (decl_tv.val.toIntern()) { .generic_poison => unreachable, .unreachable_value => unreachable, - else => switch (mod.intern_pool.indexToKey(decl_tv.val.toIntern())) { + else => switch (ip.indexToKey(decl_tv.val.toIntern())) { .variable => |variable| if (variable.decl == decl_index) { decl.owns_tv = true; queue_linker_work = true; @@ -4743,11 +4549,11 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool { } else if (bytes.len == 0) { return sema.fail(&block_scope, section_src, "linksection cannot be empty", .{}); } - const section = try mod.intern_pool.getOrPutString(gpa, bytes); + const section = try ip.getOrPutString(gpa, bytes); break :blk section.toOptional(); }; decl.@"addrspace" = blk: { - const addrspace_ctx: Sema.AddressSpaceContext = switch (mod.intern_pool.indexToKey(decl_tv.val.toIntern())) { + const addrspace_ctx: Sema.AddressSpaceContext = switch (ip.indexToKey(decl_tv.val.toIntern())) { .variable => .variable, .extern_func, .func => .function, else => .constant, @@ -5309,7 +5115,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err decl.has_align = has_align; decl.has_linksection_or_addrspace = has_linksection_or_addrspace; decl.zir_decl_index = @as(u32, @intCast(decl_sub_index)); - if (decl.getOwnedFunctionIndex(mod) != .none) { + if (decl.getOwnedFunctionIndex() != .none) { switch (comp.bin_file.tag) { .coff, .elf, .macho, .plan9 => { // TODO Look into detecting when this would be unnecessary by storing enough state @@ -5386,7 +5192,6 @@ pub fn clearDecl( try namespace.deleteAllDecls(mod, outdated_decls); } } - decl.clearValues(mod); if (decl.deletion_flag) { decl.deletion_flag = false; @@ -5497,19 +5302,26 @@ fn deleteDeclExports(mod: *Module, decl_index: Decl.Index) Allocator.Error!void export_owners.deinit(mod.gpa); } -pub fn analyzeFnBody(mod: *Module, func_index: Fn.Index, arena: Allocator) SemaError!Air { +pub fn analyzeFnBody(mod: *Module, func_index: InternPool.Index, arena: Allocator) SemaError!Air { const tracy = trace(@src()); defer tracy.end(); const gpa = mod.gpa; - const func = mod.funcPtr(func_index); + const ip = &mod.intern_pool; + const func = mod.funcInfo(func_index); const decl_index = func.owner_decl; const decl = mod.declPtr(decl_index); var comptime_mutable_decls = std.ArrayList(Decl.Index).init(gpa); defer comptime_mutable_decls.deinit(); + // In the case of a generic function instance, this is the type of the + // instance, which has comptime parameters elided. In other words, it is + // the runtime-known parameters only, not to be confused with the + // generic_owner function type, which potentially has more parameters, + // including comptime parameters. const fn_ty = decl.ty; + const fn_ty_info = mod.typeToFunc(fn_ty).?; var sema: Sema = .{ .mod = mod, @@ -5518,18 +5330,16 @@ pub fn analyzeFnBody(mod: *Module, func_index: Fn.Index, arena: Allocator) SemaE .code = decl.getFileScope(mod).zir, .owner_decl = decl, .owner_decl_index = decl_index, - .func = func, - .func_index = func_index.toOptional(), - .fn_ret_ty = mod.typeToFunc(fn_ty).?.return_type.toType(), - .owner_func = func, - .owner_func_index = func_index.toOptional(), - .branch_quota = @max(func.branch_quota, Sema.default_branch_quota), + .func_index = func_index, + .fn_ret_ty = fn_ty_info.return_type.toType(), + .owner_func_index = func_index, + .branch_quota = @max(func.branchQuota(ip).*, Sema.default_branch_quota), .comptime_mutable_decls = &comptime_mutable_decls, }; defer sema.deinit(); // reset in case calls to errorable functions are removed. - func.calls_or_awaits_errorable_fn = false; + func.analysis(ip).calls_or_awaits_errorable_fn = false; // First few indexes of extra are reserved and set at the end. const reserved_count = @typeInfo(Air.ExtraIndex).Enum.fields.len; @@ -5551,8 +5361,7 @@ pub fn analyzeFnBody(mod: *Module, func_index: Fn.Index, arena: Allocator) SemaE }; defer inner_block.instructions.deinit(gpa); - const fn_info = sema.code.getFnInfo(func.zir_body_inst); - const zir_tags = sema.code.instructions.items(.tag); + const fn_info = sema.code.getFnInfo(func.zirBodyInst(ip).*); // Here we are performing "runtime semantic analysis" for a function body, which means // we must map the parameter ZIR instructions to `arg` AIR instructions. @@ -5560,35 +5369,36 @@ pub fn analyzeFnBody(mod: *Module, func_index: Fn.Index, arena: Allocator) SemaE // 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 = @as(u32, @intCast(mod.typeToFunc(fn_ty).?.param_types.len)); + const runtime_params_len = fn_ty_info.param_types.len; try inner_block.instructions.ensureTotalCapacityPrecise(gpa, runtime_params_len); - try sema.air_instructions.ensureUnusedCapacity(gpa, fn_info.total_params_len * 2); // * 2 for the `addType` + try sema.air_instructions.ensureUnusedCapacity(gpa, fn_info.total_params_len); try sema.inst_map.ensureSpaceForInstructions(gpa, fn_info.param_body); - var runtime_param_index: usize = 0; - var total_param_index: usize = 0; - for (fn_info.param_body) |inst| { - switch (zir_tags[inst]) { - .param, .param_comptime, .param_anytype, .param_anytype_comptime => {}, - else => continue, + // In the case of a generic function instance, pre-populate all the comptime args. + if (func.comptime_args.len != 0) { + for ( + fn_info.param_body[0..func.comptime_args.len], + func.comptime_args.get(ip), + ) |inst, comptime_arg| { + if (comptime_arg == .none) continue; + sema.inst_map.putAssumeCapacityNoClobber(inst, Air.internedToRef(comptime_arg)); } - const param_ty = if (func.comptime_args) |comptime_args| t: { - const arg_tv = comptime_args[total_param_index]; - - const arg_val = if (!arg_tv.val.isGenericPoison()) - arg_tv.val - else if (try arg_tv.ty.onePossibleValue(mod)) |opv| - opv - else - break :t arg_tv.ty; - - const arg = try sema.addConstant(arg_val); - sema.inst_map.putAssumeCapacityNoClobber(inst, arg); - total_param_index += 1; - continue; - } else mod.typeToFunc(fn_ty).?.param_types[runtime_param_index].toType(); + } + + const src_params_len = if (func.comptime_args.len != 0) + func.comptime_args.len + else + runtime_params_len; + + var runtime_param_index: usize = 0; + for (fn_info.param_body[0..src_params_len], 0..) |inst, src_param_index| { + const gop = sema.inst_map.getOrPutAssumeCapacity(inst); + if (gop.found_existing) continue; // provided above by comptime arg - const opt_opv = sema.typeHasOnePossibleValue(param_ty) catch |err| switch (err) { + const param_ty = fn_ty_info.param_types.get(ip)[runtime_param_index]; + runtime_param_index += 1; + + const opt_opv = sema.typeHasOnePossibleValue(param_ty.toType()) catch |err| switch (err) { error.NeededSourceLocation => unreachable, error.GenericPoison => unreachable, error.ComptimeReturn => unreachable, @@ -5596,28 +5406,22 @@ pub fn analyzeFnBody(mod: *Module, func_index: Fn.Index, arena: Allocator) SemaE else => |e| return e, }; if (opt_opv) |opv| { - const arg = try sema.addConstant(opv); - sema.inst_map.putAssumeCapacityNoClobber(inst, arg); - total_param_index += 1; - runtime_param_index += 1; + gop.value_ptr.* = Air.internedToRef(opv.toIntern()); continue; } - const air_ty = try sema.addType(param_ty); - const arg_index = @as(u32, @intCast(sema.air_instructions.len)); + const arg_index: u32 = @intCast(sema.air_instructions.len); + gop.value_ptr.* = Air.indexToRef(arg_index); inner_block.instructions.appendAssumeCapacity(arg_index); sema.air_instructions.appendAssumeCapacity(.{ .tag = .arg, .data = .{ .arg = .{ - .ty = air_ty, - .src_index = @as(u32, @intCast(total_param_index)), + .ty = Air.internedToRef(param_ty), + .src_index = @intCast(src_param_index), } }, }); - sema.inst_map.putAssumeCapacityNoClobber(inst, Air.indexToRef(arg_index)); - total_param_index += 1; - runtime_param_index += 1; } - func.state = .in_progress; + func.analysis(ip).state = .in_progress; const last_arg_index = inner_block.instructions.items.len; @@ -5648,7 +5452,7 @@ pub fn analyzeFnBody(mod: *Module, func_index: Fn.Index, arena: Allocator) SemaE } // If we don't get an error return trace from a caller, create our own. - if (func.calls_or_awaits_errorable_fn and + if (func.analysis(ip).calls_or_awaits_errorable_fn and mod.comp.bin_file.options.error_return_tracing and !sema.fn_ret_ty.isError(mod)) { @@ -5677,7 +5481,7 @@ pub fn analyzeFnBody(mod: *Module, func_index: Fn.Index, arena: Allocator) SemaE sema.air_extra.appendSliceAssumeCapacity(inner_block.instructions.items); sema.air_extra.items[@intFromEnum(Air.ExtraIndex.main_block)] = main_block_index; - func.state = .success; + func.analysis(ip).state = .success; // Finally we must resolve the return type and parameter types so that backends // have full access to type information. @@ -5716,7 +5520,7 @@ pub fn analyzeFnBody(mod: *Module, func_index: Fn.Index, arena: Allocator) SemaE }; } - return Air{ + return .{ .instructions = sema.air_instructions.toOwnedSlice(), .extra = try sema.air_extra.toOwnedSlice(gpa), }; @@ -5731,9 +5535,6 @@ fn markOutdatedDecl(mod: *Module, decl_index: Decl.Index) !void { if (mod.cimport_errors.fetchSwapRemove(decl_index)) |kv| { for (kv.value) |err| err.deinit(mod.gpa); } - if (decl.getOwnedFunctionIndex(mod).unwrap()) |func| { - _ = mod.align_stack_fns.remove(func); - } if (mod.emit_h) |emit_h| { if (emit_h.failed_decls.fetchSwapRemove(decl_index)) |kv| { kv.value.destroy(mod.gpa); @@ -5777,14 +5578,6 @@ pub fn destroyUnion(mod: *Module, index: Union.Index) void { return mod.intern_pool.destroyUnion(mod.gpa, index); } -pub fn createFunc(mod: *Module, initialization: Fn) Allocator.Error!Fn.Index { - return mod.intern_pool.createFunc(mod.gpa, initialization); -} - -pub fn destroyFunc(mod: *Module, index: Fn.Index) void { - return mod.intern_pool.destroyFunc(mod.gpa, index); -} - pub fn allocateNewDecl( mod: *Module, namespace: Namespace.Index, @@ -6578,7 +6371,6 @@ pub fn populateTestFunctions( // Since we are replacing the Decl's value we must perform cleanup on the // previous value. - decl.clearValues(mod); decl.ty = new_ty; decl.val = new_val; decl.has_tv = true; @@ -6657,7 +6449,7 @@ pub fn markReferencedDeclsAlive(mod: *Module, val: Value) Allocator.Error!void { switch (mod.intern_pool.indexToKey(val.toIntern())) { .variable => |variable| try mod.markDeclIndexAlive(variable.decl), .extern_func => |extern_func| try mod.markDeclIndexAlive(extern_func.decl), - .func => |func| try mod.markDeclIndexAlive(mod.funcPtr(func.index).owner_decl), + .func => |func| try mod.markDeclIndexAlive(func.owner_decl), .error_union => |error_union| switch (error_union.val) { .err_name => {}, .payload => |payload| try mod.markReferencedDeclsAlive(payload.toValue()), @@ -6851,8 +6643,8 @@ pub fn adjustPtrTypeChild(mod: *Module, ptr_ty: Type, new_child: Type) Allocator return mod.ptrType(info); } -pub fn funcType(mod: *Module, info: InternPool.Key.FuncType) Allocator.Error!Type { - return (try intern(mod, .{ .func_type = info })).toType(); +pub fn funcType(mod: *Module, key: InternPool.GetFuncTypeKey) Allocator.Error!Type { + return (try mod.intern_pool.getFuncType(mod.gpa, key)).toType(); } /// Use this for `anyframe->T` only. @@ -7231,16 +7023,28 @@ pub fn typeToFunc(mod: *Module, ty: Type) ?InternPool.Key.FuncType { return mod.intern_pool.indexToFuncType(ty.toIntern()); } -pub fn typeToInferredErrorSet(mod: *Module, ty: Type) ?*Fn.InferredErrorSet { +pub fn typeToInferredErrorSet(mod: *Module, ty: Type) ?*InferredErrorSet { const index = typeToInferredErrorSetIndex(mod, ty).unwrap() orelse return null; return mod.inferredErrorSetPtr(index); } -pub fn typeToInferredErrorSetIndex(mod: *Module, ty: Type) Fn.InferredErrorSet.OptionalIndex { +pub fn typeToInferredErrorSetIndex(mod: *Module, ty: Type) InferredErrorSet.OptionalIndex { if (ty.ip_index == .none) return .none; return mod.intern_pool.indexToInferredErrorSetType(ty.toIntern()); } +pub fn funcOwnerDeclPtr(mod: *Module, func_index: InternPool.Index) *Decl { + return mod.declPtr(mod.funcOwnerDeclIndex(func_index)); +} + +pub fn funcOwnerDeclIndex(mod: *Module, func_index: InternPool.Index) Decl.Index { + return mod.funcInfo(func_index).owner_decl; +} + +pub fn funcInfo(mod: *Module, func_index: InternPool.Index) InternPool.Key.Func { + return mod.intern_pool.indexToKey(func_index).func; +} + pub fn fieldSrcLoc(mod: *Module, owner_decl_index: Decl.Index, query: FieldSrcQuery) SrcLoc { @setCold(true); const owner_decl = mod.declPtr(owner_decl_index); @@ -7265,3 +7069,57 @@ pub fn fieldSrcLoc(mod: *Module, owner_decl_index: Decl.Index, query: FieldSrcQu pub fn toEnum(mod: *Module, comptime E: type, val: Value) E { return mod.intern_pool.toEnum(E, val.toIntern()); } + +pub fn isAnytypeParam(mod: *Module, func: InternPool.Index, index: u32) bool { + const file = mod.declPtr(func.owner_decl).getFileScope(mod); + + const tags = file.zir.instructions.items(.tag); + + const param_body = file.zir.getParamBody(func.zir_body_inst); + const param = param_body[index]; + + return switch (tags[param]) { + .param, .param_comptime => false, + .param_anytype, .param_anytype_comptime => true, + else => unreachable, + }; +} + +pub fn getParamName(mod: *Module, func_index: InternPool.Index, index: u32) [:0]const u8 { + const func = mod.funcInfo(func_index); + const file = mod.declPtr(func.owner_decl).getFileScope(mod); + + const tags = file.zir.instructions.items(.tag); + const data = file.zir.instructions.items(.data); + + const param_body = file.zir.getParamBody(func.zir_body_inst); + const param = param_body[index]; + + return switch (tags[param]) { + .param, .param_comptime => blk: { + const extra = file.zir.extraData(Zir.Inst.Param, data[param].pl_tok.payload_index); + break :blk file.zir.nullTerminatedString(extra.data.name); + }, + .param_anytype, .param_anytype_comptime => blk: { + const param_data = data[param].str_tok; + break :blk param_data.get(file.zir); + }, + else => unreachable, + }; +} + +pub fn hasInferredErrorSet(mod: *Module, func: InternPool.Key.Func) bool { + const owner_decl = mod.declPtr(func.owner_decl); + const zir = owner_decl.getFileScope(mod).zir; + const zir_tags = zir.instructions.items(.tag); + switch (zir_tags[func.zir_body_inst]) { + .func => return false, + .func_inferred => return true, + .func_fancy => { + const inst_data = zir.instructions.items(.data)[func.zir_body_inst].pl_node; + const extra = zir.extraData(Zir.Inst.FuncFancy, inst_data.payload_index); + return extra.data.bits.is_inferred_error; + }, + else => unreachable, + } +} |
