| author | |
| committer | |
| log | a58ceb3d554a9565a6cc0443f6384149ae2b3145 |
| tree | af66e289a0ae028be92389722979ed270b42ee42 |
| parent | a9d544575d5bd2a939b09b8f088bee5d8ff7b0d9 |
| parent | 7dbd2a6bb549afa6dc3c95df46f40bf144db23a6 |
| signature |
frontend: fix updateZirRefs8 files changed, 194 insertions(+), 164 deletions(-)
lib/std/zig/Zir.zig+6-4| ... | @@ -1,5 +1,7 @@ | ... | @@ -1,5 +1,7 @@ |
| 1 | //! Zig Intermediate Representation. Astgen.zig converts AST nodes to these | 1 | //! Zig Intermediate Representation. |
| 2 | //! untyped IR instructions. Next, Sema.zig processes these into AIR. | 2 | //! |
| 3 | //! Astgen.zig converts AST nodes to these untyped IR instructions. Next, | ||
| 4 | //! Sema.zig processes these into AIR. | ||
| 3 | //! The minimum amount of information needed to represent a list of ZIR instructions. | 5 | //! The minimum amount of information needed to represent a list of ZIR instructions. |
| 4 | //! Once this structure is completed, it can be used to generate AIR, followed by | 6 | //! Once this structure is completed, it can be used to generate AIR, followed by |
| 5 | //! machine code, without any memory access into the AST tree token list, node list, | 7 | //! machine code, without any memory access into the AST tree token list, node list, |
| ... | @@ -4024,8 +4026,8 @@ pub fn getAssociatedSrcHash(zir: Zir, inst: Zir.Inst.Index) ?std.zig.SrcHash { | ... | @@ -4024,8 +4026,8 @@ pub fn getAssociatedSrcHash(zir: Zir, inst: Zir.Inst.Index) ?std.zig.SrcHash { |
| 4024 | const data = zir.instructions.items(.data); | 4026 | const data = zir.instructions.items(.data); |
| 4025 | switch (tag[@intFromEnum(inst)]) { | 4027 | switch (tag[@intFromEnum(inst)]) { |
| 4026 | .declaration => { | 4028 | .declaration => { |
| 4027 | const pl_node = data[@intFromEnum(inst)].pl_node; | 4029 | const declaration = data[@intFromEnum(inst)].declaration; |
| 4028 | const extra = zir.extraData(Inst.Declaration, pl_node.payload_index); | 4030 | const extra = zir.extraData(Inst.Declaration, declaration.payload_index); |
| 4029 | return @bitCast([4]u32{ | 4031 | return @bitCast([4]u32{ |
| 4030 | extra.data.src_hash_0, | 4032 | extra.data.src_hash_0, |
| 4031 | extra.data.src_hash_1, | 4033 | extra.data.src_hash_1, |
src/Air.zig+1-2| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | //! Analyzed Intermediate Representation. | 1 | //! Analyzed Intermediate Representation. |
| 2 | //! | ||
| 2 | //! This data is produced by Sema and consumed by codegen. | 3 | //! This data is produced by Sema and consumed by codegen. |
| 3 | //! Unlike ZIR where there is one instance for an entire source file, each function | 4 | //! Unlike ZIR where there is one instance for an entire source file, each function |
| 4 | //! gets its own `Air` instance. | 5 | //! gets its own `Air` instance. |
| ... | @@ -12,8 +13,6 @@ const Value = @import("Value.zig"); | ... | @@ -12,8 +13,6 @@ const Value = @import("Value.zig"); |
| 12 | const Type = @import("Type.zig"); | 13 | const Type = @import("Type.zig"); |
| 13 | const InternPool = @import("InternPool.zig"); | 14 | const InternPool = @import("InternPool.zig"); |
| 14 | const Zcu = @import("Zcu.zig"); | 15 | const Zcu = @import("Zcu.zig"); |
| 15 | /// Deprecated. | ||
| 16 | const Module = Zcu; | ||
| 17 | 16 | ||
| 18 | instructions: std.MultiArrayList(Inst).Slice, | 17 | instructions: std.MultiArrayList(Inst).Slice, |
| 19 | /// The meaning of this data is determined by `Inst.Tag` value. | 18 | /// The meaning of this data is determined by `Inst.Tag` value. |
src/Compilation.zig+7-2| ... | @@ -3595,7 +3595,12 @@ fn performAllTheWorkInner( | ... | @@ -3595,7 +3595,12 @@ fn performAllTheWorkInner( |
| 3595 | } | 3595 | } |
| 3596 | 3596 | ||
| 3597 | if (comp.module) |zcu| { | 3597 | if (comp.module) |zcu| { |
| 3598 | const pt: Zcu.PerThread = .{ .zcu = comp.module.?, .tid = .main }; | 3598 | const pt: Zcu.PerThread = .{ .zcu = zcu, .tid = .main }; |
| 3599 | if (comp.incremental) { | ||
| 3600 | const update_zir_refs_node = main_progress_node.start("Update ZIR References", 0); | ||
| 3601 | defer update_zir_refs_node.end(); | ||
| 3602 | try pt.updateZirRefs(); | ||
| 3603 | } | ||
| 3599 | try reportMultiModuleErrors(pt); | 3604 | try reportMultiModuleErrors(pt); |
| 3600 | try zcu.flushRetryableFailures(); | 3605 | try zcu.flushRetryableFailures(); |
| 3601 | zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0); | 3606 | zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0); |
| ... | @@ -4306,7 +4311,7 @@ fn workerAstGenFile( | ... | @@ -4306,7 +4311,7 @@ fn workerAstGenFile( |
| 4306 | defer child_prog_node.end(); | 4311 | defer child_prog_node.end(); |
| 4307 | 4312 | ||
| 4308 | const pt: Zcu.PerThread = .{ .zcu = comp.module.?, .tid = @enumFromInt(tid) }; | 4313 | const pt: Zcu.PerThread = .{ .zcu = comp.module.?, .tid = @enumFromInt(tid) }; |
| 4309 | pt.astGenFile(file, file_index, path_digest, root_decl) catch |err| switch (err) { | 4314 | pt.astGenFile(file, path_digest, root_decl) catch |err| switch (err) { |
| 4310 | error.AnalysisFail => return, | 4315 | error.AnalysisFail => return, |
| 4311 | else => { | 4316 | else => { |
| 4312 | file.status = .retryable_failure; | 4317 | file.status = .retryable_failure; |
src/InternPool.zig+6-4| ... | @@ -283,10 +283,12 @@ pub const DependencyIterator = struct { | ... | @@ -283,10 +283,12 @@ pub const DependencyIterator = struct { |
| 283 | ip: *const InternPool, | 283 | ip: *const InternPool, |
| 284 | next_entry: DepEntry.Index.Optional, | 284 | next_entry: DepEntry.Index.Optional, |
| 285 | pub fn next(it: *DependencyIterator) ?AnalUnit { | 285 | pub fn next(it: *DependencyIterator) ?AnalUnit { |
| 286 | const idx = it.next_entry.unwrap() orelse return null; | 286 | while (true) { |
| 287 | const entry = it.ip.dep_entries.items[@intFromEnum(idx)]; | 287 | const idx = it.next_entry.unwrap() orelse return null; |
| 288 | it.next_entry = entry.next; | 288 | const entry = it.ip.dep_entries.items[@intFromEnum(idx)]; |
| 289 | return entry.depender.unwrap().?; | 289 | it.next_entry = entry.next; |
| 290 | if (entry.depender.unwrap()) |depender| return depender; | ||
| 291 | } | ||
| 290 | } | 292 | } |
| 291 | }; | 293 | }; |
| 292 | 294 |
src/Sema.zig+1-1| ... | @@ -6065,7 +6065,7 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr | ... | @@ -6065,7 +6065,7 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr |
| 6065 | 6065 | ||
| 6066 | const path_digest = zcu.filePathDigest(result.file_index); | 6066 | const path_digest = zcu.filePathDigest(result.file_index); |
| 6067 | const root_decl = zcu.fileRootDecl(result.file_index); | 6067 | const root_decl = zcu.fileRootDecl(result.file_index); |
| 6068 | pt.astGenFile(result.file, result.file_index, path_digest, root_decl) catch |err| | 6068 | pt.astGenFile(result.file, path_digest, root_decl) catch |err| |
| 6069 | return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)}); | 6069 | return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)}); |
| 6070 | 6070 | ||
| 6071 | try pt.ensureFileAnalyzed(result.file_index); | 6071 | try pt.ensureFileAnalyzed(result.file_index); |
src/Zcu.zig+29-28| ... | @@ -1,5 +1,8 @@ | ... | @@ -1,5 +1,8 @@ |
| 1 | //! Compilation of all Zig source code is represented by one `Module`. | 1 | //! Zig Compilation Unit |
| 2 | //! Each `Compilation` has exactly one or zero `Module`, depending on whether | 2 | //! |
| 3 | //! Compilation of all Zig source code is represented by one `Zcu`. | ||
| 4 | //! | ||
| 5 | //! Each `Compilation` has exactly one or zero `Zcu`, depending on whether | ||
| 3 | //! there is or is not any zig source code, respectively. | 6 | //! there is or is not any zig source code, respectively. |
| 4 | 7 | ||
| 5 | const std = @import("std"); | 8 | const std = @import("std"); |
| ... | @@ -13,8 +16,6 @@ const BigIntMutable = std.math.big.int.Mutable; | ... | @@ -13,8 +16,6 @@ const BigIntMutable = std.math.big.int.Mutable; |
| 13 | const Target = std.Target; | 16 | const Target = std.Target; |
| 14 | const Ast = std.zig.Ast; | 17 | const Ast = std.zig.Ast; |
| 15 | 18 | ||
| 16 | /// Deprecated, use `Zcu`. | ||
| 17 | const Module = Zcu; | ||
| 18 | const Zcu = @This(); | 19 | const Zcu = @This(); |
| 19 | const Compilation = @import("Compilation.zig"); | 20 | const Compilation = @import("Compilation.zig"); |
| 20 | const Cache = std.Build.Cache; | 21 | const Cache = std.Build.Cache; |
| ... | @@ -2393,7 +2394,7 @@ pub const CompileError = error{ | ... | @@ -2393,7 +2394,7 @@ pub const CompileError = error{ |
| 2393 | ComptimeBreak, | 2394 | ComptimeBreak, |
| 2394 | }; | 2395 | }; |
| 2395 | 2396 | ||
| 2396 | pub fn init(mod: *Module, thread_count: usize) !void { | 2397 | pub fn init(mod: *Zcu, thread_count: usize) !void { |
| 2397 | const gpa = mod.gpa; | 2398 | const gpa = mod.gpa; |
| 2398 | try mod.intern_pool.init(gpa, thread_count); | 2399 | try mod.intern_pool.init(gpa, thread_count); |
| 2399 | } | 2400 | } |
| ... | @@ -2487,20 +2488,20 @@ pub fn deinit(zcu: *Zcu) void { | ... | @@ -2487,20 +2488,20 @@ pub fn deinit(zcu: *Zcu) void { |
| 2487 | zcu.intern_pool.deinit(gpa); | 2488 | zcu.intern_pool.deinit(gpa); |
| 2488 | } | 2489 | } |
| 2489 | 2490 | ||
| 2490 | pub fn declPtr(mod: *Module, index: Decl.Index) *Decl { | 2491 | pub fn declPtr(mod: *Zcu, index: Decl.Index) *Decl { |
| 2491 | return mod.intern_pool.declPtr(index); | 2492 | return mod.intern_pool.declPtr(index); |
| 2492 | } | 2493 | } |
| 2493 | 2494 | ||
| 2494 | pub fn namespacePtr(mod: *Module, index: Namespace.Index) *Namespace { | 2495 | pub fn namespacePtr(mod: *Zcu, index: Namespace.Index) *Namespace { |
| 2495 | return mod.intern_pool.namespacePtr(index); | 2496 | return mod.intern_pool.namespacePtr(index); |
| 2496 | } | 2497 | } |
| 2497 | 2498 | ||
| 2498 | pub fn namespacePtrUnwrap(mod: *Module, index: Namespace.OptionalIndex) ?*Namespace { | 2499 | pub fn namespacePtrUnwrap(mod: *Zcu, index: Namespace.OptionalIndex) ?*Namespace { |
| 2499 | return mod.namespacePtr(index.unwrap() orelse return null); | 2500 | return mod.namespacePtr(index.unwrap() orelse return null); |
| 2500 | } | 2501 | } |
| 2501 | 2502 | ||
| 2502 | /// Returns true if and only if the Decl is the top level struct associated with a File. | 2503 | /// Returns true if and only if the Decl is the top level struct associated with a File. |
| 2503 | pub fn declIsRoot(mod: *Module, decl_index: Decl.Index) bool { | 2504 | pub fn declIsRoot(mod: *Zcu, decl_index: Decl.Index) bool { |
| 2504 | const decl = mod.declPtr(decl_index); | 2505 | const decl = mod.declPtr(decl_index); |
| 2505 | const namespace = mod.namespacePtr(decl.src_namespace); | 2506 | const namespace = mod.namespacePtr(decl.src_namespace); |
| 2506 | if (namespace.parent != .none) return false; | 2507 | if (namespace.parent != .none) return false; |
| ... | @@ -2940,7 +2941,7 @@ pub fn mapOldZirToNew( | ... | @@ -2940,7 +2941,7 @@ pub fn mapOldZirToNew( |
| 2940 | /// analyzed, and for ensuring it can exist at runtime (see | 2941 | /// analyzed, and for ensuring it can exist at runtime (see |
| 2941 | /// `sema.fnHasRuntimeBits`). This function does *not* guarantee that the body | 2942 | /// `sema.fnHasRuntimeBits`). This function does *not* guarantee that the body |
| 2942 | /// will be analyzed when it returns: for that, see `ensureFuncBodyAnalyzed`. | 2943 | /// will be analyzed when it returns: for that, see `ensureFuncBodyAnalyzed`. |
| 2943 | pub fn ensureFuncBodyAnalysisQueued(mod: *Module, func_index: InternPool.Index) !void { | 2944 | pub fn ensureFuncBodyAnalysisQueued(mod: *Zcu, func_index: InternPool.Index) !void { |
| 2944 | const ip = &mod.intern_pool; | 2945 | const ip = &mod.intern_pool; |
| 2945 | const func = mod.funcInfo(func_index); | 2946 | const func = mod.funcInfo(func_index); |
| 2946 | const decl_index = func.owner_decl; | 2947 | const decl_index = func.owner_decl; |
| ... | @@ -3102,13 +3103,13 @@ pub fn addUnitReference(zcu: *Zcu, src_unit: AnalUnit, referenced_unit: AnalUnit | ... | @@ -3102,13 +3103,13 @@ pub fn addUnitReference(zcu: *Zcu, src_unit: AnalUnit, referenced_unit: AnalUnit |
| 3102 | gop.value_ptr.* = @intCast(ref_idx); | 3103 | gop.value_ptr.* = @intCast(ref_idx); |
| 3103 | } | 3104 | } |
| 3104 | 3105 | ||
| 3105 | pub fn errorSetBits(mod: *Module) u16 { | 3106 | pub fn errorSetBits(mod: *Zcu) u16 { |
| 3106 | if (mod.error_limit == 0) return 0; | 3107 | if (mod.error_limit == 0) return 0; |
| 3107 | return std.math.log2_int_ceil(ErrorInt, mod.error_limit + 1); // +1 for no error | 3108 | return std.math.log2_int_ceil(ErrorInt, mod.error_limit + 1); // +1 for no error |
| 3108 | } | 3109 | } |
| 3109 | 3110 | ||
| 3110 | pub fn errNote( | 3111 | pub fn errNote( |
| 3111 | mod: *Module, | 3112 | mod: *Zcu, |
| 3112 | src_loc: LazySrcLoc, | 3113 | src_loc: LazySrcLoc, |
| 3113 | parent: *ErrorMsg, | 3114 | parent: *ErrorMsg, |
| 3114 | comptime format: []const u8, | 3115 | comptime format: []const u8, |
| ... | @@ -3138,7 +3139,7 @@ pub fn optimizeMode(zcu: *const Zcu) std.builtin.OptimizeMode { | ... | @@ -3138,7 +3139,7 @@ pub fn optimizeMode(zcu: *const Zcu) std.builtin.OptimizeMode { |
| 3138 | return zcu.root_mod.optimize_mode; | 3139 | return zcu.root_mod.optimize_mode; |
| 3139 | } | 3140 | } |
| 3140 | 3141 | ||
| 3141 | fn lockAndClearFileCompileError(mod: *Module, file: *File) void { | 3142 | fn lockAndClearFileCompileError(mod: *Zcu, file: *File) void { |
| 3142 | switch (file.status) { | 3143 | switch (file.status) { |
| 3143 | .success_zir, .retryable_failure => {}, | 3144 | .success_zir, .retryable_failure => {}, |
| 3144 | .never_loaded, .parse_failure, .astgen_failure => { | 3145 | .never_loaded, .parse_failure, .astgen_failure => { |
| ... | @@ -3172,7 +3173,7 @@ pub fn handleUpdateExports( | ... | @@ -3172,7 +3173,7 @@ pub fn handleUpdateExports( |
| 3172 | }; | 3173 | }; |
| 3173 | } | 3174 | } |
| 3174 | 3175 | ||
| 3175 | pub fn addGlobalAssembly(mod: *Module, decl_index: Decl.Index, source: []const u8) !void { | 3176 | pub fn addGlobalAssembly(mod: *Zcu, decl_index: Decl.Index, source: []const u8) !void { |
| 3176 | const gop = try mod.global_assembly.getOrPut(mod.gpa, decl_index); | 3177 | const gop = try mod.global_assembly.getOrPut(mod.gpa, decl_index); |
| 3177 | if (gop.found_existing) { | 3178 | if (gop.found_existing) { |
| 3178 | const new_value = try std.fmt.allocPrint(mod.gpa, "{s}\n{s}", .{ gop.value_ptr.*, source }); | 3179 | const new_value = try std.fmt.allocPrint(mod.gpa, "{s}\n{s}", .{ gop.value_ptr.*, source }); |
| ... | @@ -3226,7 +3227,7 @@ pub const AtomicPtrAlignmentDiagnostics = struct { | ... | @@ -3226,7 +3227,7 @@ pub const AtomicPtrAlignmentDiagnostics = struct { |
| 3226 | // TODO this function does not take into account CPU features, which can affect | 3227 | // TODO this function does not take into account CPU features, which can affect |
| 3227 | // this value. Audit this! | 3228 | // this value. Audit this! |
| 3228 | pub fn atomicPtrAlignment( | 3229 | pub fn atomicPtrAlignment( |
| 3229 | mod: *Module, | 3230 | mod: *Zcu, |
| 3230 | ty: Type, | 3231 | ty: Type, |
| 3231 | diags: *AtomicPtrAlignmentDiagnostics, | 3232 | diags: *AtomicPtrAlignmentDiagnostics, |
| 3232 | ) AtomicPtrAlignmentError!Alignment { | 3233 | ) AtomicPtrAlignmentError!Alignment { |
| ... | @@ -3332,7 +3333,7 @@ pub fn atomicPtrAlignment( | ... | @@ -3332,7 +3333,7 @@ pub fn atomicPtrAlignment( |
| 3332 | return error.BadType; | 3333 | return error.BadType; |
| 3333 | } | 3334 | } |
| 3334 | 3335 | ||
| 3335 | pub fn declFileScope(mod: *Module, decl_index: Decl.Index) *File { | 3336 | pub fn declFileScope(mod: *Zcu, decl_index: Decl.Index) *File { |
| 3336 | return mod.declPtr(decl_index).getFileScope(mod); | 3337 | return mod.declPtr(decl_index).getFileScope(mod); |
| 3337 | } | 3338 | } |
| 3338 | 3339 | ||
| ... | @@ -3340,7 +3341,7 @@ pub fn declFileScope(mod: *Module, decl_index: Decl.Index) *File { | ... | @@ -3340,7 +3341,7 @@ pub fn declFileScope(mod: *Module, decl_index: Decl.Index) *File { |
| 3340 | /// * `@TypeOf(.{})` | 3341 | /// * `@TypeOf(.{})` |
| 3341 | /// * A struct which has no fields (`struct {}`). | 3342 | /// * A struct which has no fields (`struct {}`). |
| 3342 | /// * Not a struct. | 3343 | /// * Not a struct. |
| 3343 | pub fn typeToStruct(mod: *Module, ty: Type) ?InternPool.LoadedStructType { | 3344 | pub fn typeToStruct(mod: *Zcu, ty: Type) ?InternPool.LoadedStructType { |
| 3344 | if (ty.ip_index == .none) return null; | 3345 | if (ty.ip_index == .none) return null; |
| 3345 | const ip = &mod.intern_pool; | 3346 | const ip = &mod.intern_pool; |
| 3346 | return switch (ip.indexToKey(ty.ip_index)) { | 3347 | return switch (ip.indexToKey(ty.ip_index)) { |
| ... | @@ -3349,13 +3350,13 @@ pub fn typeToStruct(mod: *Module, ty: Type) ?InternPool.LoadedStructType { | ... | @@ -3349,13 +3350,13 @@ pub fn typeToStruct(mod: *Module, ty: Type) ?InternPool.LoadedStructType { |
| 3349 | }; | 3350 | }; |
| 3350 | } | 3351 | } |
| 3351 | 3352 | ||
| 3352 | pub fn typeToPackedStruct(mod: *Module, ty: Type) ?InternPool.LoadedStructType { | 3353 | pub fn typeToPackedStruct(mod: *Zcu, ty: Type) ?InternPool.LoadedStructType { |
| 3353 | const s = mod.typeToStruct(ty) orelse return null; | 3354 | const s = mod.typeToStruct(ty) orelse return null; |
| 3354 | if (s.layout != .@"packed") return null; | 3355 | if (s.layout != .@"packed") return null; |
| 3355 | return s; | 3356 | return s; |
| 3356 | } | 3357 | } |
| 3357 | 3358 | ||
| 3358 | pub fn typeToUnion(mod: *Module, ty: Type) ?InternPool.LoadedUnionType { | 3359 | pub fn typeToUnion(mod: *Zcu, ty: Type) ?InternPool.LoadedUnionType { |
| 3359 | if (ty.ip_index == .none) return null; | 3360 | if (ty.ip_index == .none) return null; |
| 3360 | const ip = &mod.intern_pool; | 3361 | const ip = &mod.intern_pool; |
| 3361 | return switch (ip.indexToKey(ty.ip_index)) { | 3362 | return switch (ip.indexToKey(ty.ip_index)) { |
| ... | @@ -3364,32 +3365,32 @@ pub fn typeToUnion(mod: *Module, ty: Type) ?InternPool.LoadedUnionType { | ... | @@ -3364,32 +3365,32 @@ pub fn typeToUnion(mod: *Module, ty: Type) ?InternPool.LoadedUnionType { |
| 3364 | }; | 3365 | }; |
| 3365 | } | 3366 | } |
| 3366 | 3367 | ||
| 3367 | pub fn typeToFunc(mod: *Module, ty: Type) ?InternPool.Key.FuncType { | 3368 | pub fn typeToFunc(mod: *Zcu, ty: Type) ?InternPool.Key.FuncType { |
| 3368 | if (ty.ip_index == .none) return null; | 3369 | if (ty.ip_index == .none) return null; |
| 3369 | return mod.intern_pool.indexToFuncType(ty.toIntern()); | 3370 | return mod.intern_pool.indexToFuncType(ty.toIntern()); |
| 3370 | } | 3371 | } |
| 3371 | 3372 | ||
| 3372 | pub fn funcOwnerDeclPtr(mod: *Module, func_index: InternPool.Index) *Decl { | 3373 | pub fn funcOwnerDeclPtr(mod: *Zcu, func_index: InternPool.Index) *Decl { |
| 3373 | return mod.declPtr(mod.funcOwnerDeclIndex(func_index)); | 3374 | return mod.declPtr(mod.funcOwnerDeclIndex(func_index)); |
| 3374 | } | 3375 | } |
| 3375 | 3376 | ||
| 3376 | pub fn funcOwnerDeclIndex(mod: *Module, func_index: InternPool.Index) Decl.Index { | 3377 | pub fn funcOwnerDeclIndex(mod: *Zcu, func_index: InternPool.Index) Decl.Index { |
| 3377 | return mod.funcInfo(func_index).owner_decl; | 3378 | return mod.funcInfo(func_index).owner_decl; |
| 3378 | } | 3379 | } |
| 3379 | 3380 | ||
| 3380 | pub fn iesFuncIndex(mod: *const Module, ies_index: InternPool.Index) InternPool.Index { | 3381 | pub fn iesFuncIndex(mod: *const Zcu, ies_index: InternPool.Index) InternPool.Index { |
| 3381 | return mod.intern_pool.iesFuncIndex(ies_index); | 3382 | return mod.intern_pool.iesFuncIndex(ies_index); |
| 3382 | } | 3383 | } |
| 3383 | 3384 | ||
| 3384 | pub fn funcInfo(mod: *Module, func_index: InternPool.Index) InternPool.Key.Func { | 3385 | pub fn funcInfo(mod: *Zcu, func_index: InternPool.Index) InternPool.Key.Func { |
| 3385 | return mod.intern_pool.indexToKey(func_index).func; | 3386 | return mod.intern_pool.indexToKey(func_index).func; |
| 3386 | } | 3387 | } |
| 3387 | 3388 | ||
| 3388 | pub fn toEnum(mod: *Module, comptime E: type, val: Value) E { | 3389 | pub fn toEnum(mod: *Zcu, comptime E: type, val: Value) E { |
| 3389 | return mod.intern_pool.toEnum(E, val.toIntern()); | 3390 | return mod.intern_pool.toEnum(E, val.toIntern()); |
| 3390 | } | 3391 | } |
| 3391 | 3392 | ||
| 3392 | pub fn isAnytypeParam(mod: *Module, func: InternPool.Index, index: u32) bool { | 3393 | pub fn isAnytypeParam(mod: *Zcu, func: InternPool.Index, index: u32) bool { |
| 3393 | const file = mod.declPtr(func.owner_decl).getFileScope(mod); | 3394 | const file = mod.declPtr(func.owner_decl).getFileScope(mod); |
| 3394 | 3395 | ||
| 3395 | const tags = file.zir.instructions.items(.tag); | 3396 | const tags = file.zir.instructions.items(.tag); |
| ... | @@ -3404,7 +3405,7 @@ pub fn isAnytypeParam(mod: *Module, func: InternPool.Index, index: u32) bool { | ... | @@ -3404,7 +3405,7 @@ pub fn isAnytypeParam(mod: *Module, func: InternPool.Index, index: u32) bool { |
| 3404 | }; | 3405 | }; |
| 3405 | } | 3406 | } |
| 3406 | 3407 | ||
| 3407 | pub fn getParamName(mod: *Module, func_index: InternPool.Index, index: u32) [:0]const u8 { | 3408 | pub fn getParamName(mod: *Zcu, func_index: InternPool.Index, index: u32) [:0]const u8 { |
| 3408 | const func = mod.funcInfo(func_index); | 3409 | const func = mod.funcInfo(func_index); |
| 3409 | const file = mod.declPtr(func.owner_decl).getFileScope(mod); | 3410 | const file = mod.declPtr(func.owner_decl).getFileScope(mod); |
| 3410 | 3411 | ||
| ... | @@ -3441,7 +3442,7 @@ pub const UnionLayout = struct { | ... | @@ -3441,7 +3442,7 @@ pub const UnionLayout = struct { |
| 3441 | }; | 3442 | }; |
| 3442 | 3443 | ||
| 3443 | /// Returns the index of the active field, given the current tag value | 3444 | /// Returns the index of the active field, given the current tag value |
| 3444 | pub fn unionTagFieldIndex(mod: *Module, loaded_union: InternPool.LoadedUnionType, enum_tag: Value) ?u32 { | 3445 | pub fn unionTagFieldIndex(mod: *Zcu, loaded_union: InternPool.LoadedUnionType, enum_tag: Value) ?u32 { |
| 3445 | const ip = &mod.intern_pool; | 3446 | const ip = &mod.intern_pool; |
| 3446 | if (enum_tag.toIntern() == .none) return null; | 3447 | if (enum_tag.toIntern() == .none) return null; |
| 3447 | assert(ip.typeOf(enum_tag.toIntern()) == loaded_union.enum_tag_ty); | 3448 | assert(ip.typeOf(enum_tag.toIntern()) == loaded_union.enum_tag_ty); |
src/Zcu/PerThread.zig+141-123| ... | @@ -60,10 +60,6 @@ pub fn destroyFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) void { | ... | @@ -60,10 +60,6 @@ pub fn destroyFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) void { |
| 60 | pub fn astGenFile( | 60 | pub fn astGenFile( |
| 61 | pt: Zcu.PerThread, | 61 | pt: Zcu.PerThread, |
| 62 | file: *Zcu.File, | 62 | file: *Zcu.File, |
| 63 | /// This parameter is provided separately from `file` because it is not | ||
| 64 | /// safe to access `import_table` without a lock, and this index is needed | ||
| 65 | /// in the call to `updateZirRefs`. | ||
| 66 | file_index: Zcu.File.Index, | ||
| 67 | path_digest: Cache.BinDigest, | 63 | path_digest: Cache.BinDigest, |
| 68 | opt_root_decl: Zcu.Decl.OptionalIndex, | 64 | opt_root_decl: Zcu.Decl.OptionalIndex, |
| 69 | ) !void { | 65 | ) !void { |
| ... | @@ -210,13 +206,18 @@ pub fn astGenFile( | ... | @@ -210,13 +206,18 @@ pub fn astGenFile( |
| 210 | 206 | ||
| 211 | pt.lockAndClearFileCompileError(file); | 207 | pt.lockAndClearFileCompileError(file); |
| 212 | 208 | ||
| 213 | // If the previous ZIR does not have compile errors, keep it around | 209 | // Previous ZIR is kept for two reasons: |
| 214 | // in case parsing or new ZIR fails. In case of successful ZIR update | 210 | // |
| 215 | // at the end of this function we will free it. | 211 | // 1. In case an update to the file causes a Parse or AstGen failure, we |
| 216 | // We keep the previous ZIR loaded so that we can use it | 212 | // need to compare two successful ZIR files in order to proceed with an |
| 217 | // for the update next time it does not have any compile errors. This avoids | 213 | // incremental update. This avoids needlessly tossing out semantic |
| 218 | // needlessly tossing out semantic analysis work when an error is | 214 | // analysis work when an error is temporarily introduced. |
| 219 | // temporarily introduced. | 215 | // |
| 216 | // 2. In order to detect updates, we need to iterate over the intern pool | ||
| 217 | // values while comparing old ZIR to new ZIR. This is better done in a | ||
| 218 | // single-threaded context, so we need to keep both versions around | ||
| 219 | // until that point in the pipeline. Previous ZIR data is freed after | ||
| 220 | // that. | ||
| 220 | if (file.zir_loaded and !file.zir.hasCompileErrors()) { | 221 | if (file.zir_loaded and !file.zir.hasCompileErrors()) { |
| 221 | assert(file.prev_zir == null); | 222 | assert(file.prev_zir == null); |
| 222 | const prev_zir_ptr = try gpa.create(Zir); | 223 | const prev_zir_ptr = try gpa.create(Zir); |
| ... | @@ -320,14 +321,6 @@ pub fn astGenFile( | ... | @@ -320,14 +321,6 @@ pub fn astGenFile( |
| 320 | return error.AnalysisFail; | 321 | return error.AnalysisFail; |
| 321 | } | 322 | } |
| 322 | 323 | ||
| 323 | if (file.prev_zir) |prev_zir| { | ||
| 324 | try pt.updateZirRefs(file, file_index, prev_zir.*); | ||
| 325 | // No need to keep previous ZIR. | ||
| 326 | prev_zir.deinit(gpa); | ||
| 327 | gpa.destroy(prev_zir); | ||
| 328 | file.prev_zir = null; | ||
| 329 | } | ||
| 330 | |||
| 331 | if (opt_root_decl.unwrap()) |root_decl| { | 324 | if (opt_root_decl.unwrap()) |root_decl| { |
| 332 | // The root of this file must be re-analyzed, since the file has changed. | 325 | // The root of this file must be re-analyzed, since the file has changed. |
| 333 | comp.mutex.lock(); | 326 | comp.mutex.lock(); |
| ... | @@ -338,137 +331,162 @@ pub fn astGenFile( | ... | @@ -338,137 +331,162 @@ pub fn astGenFile( |
| 338 | } | 331 | } |
| 339 | } | 332 | } |
| 340 | 333 | ||
| 341 | /// This is called from the AstGen thread pool, so must acquire | 334 | const UpdatedFile = struct { |
| 342 | /// the Compilation mutex when acting on shared state. | 335 | file_index: Zcu.File.Index, |
| 343 | fn updateZirRefs(pt: Zcu.PerThread, file: *Zcu.File, file_index: Zcu.File.Index, old_zir: Zir) !void { | 336 | file: *Zcu.File, |
| 337 | inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index), | ||
| 338 | }; | ||
| 339 | |||
| 340 | fn cleanupUpdatedFiles(gpa: Allocator, updated_files: *std.ArrayListUnmanaged(UpdatedFile)) void { | ||
| 341 | for (updated_files.items) |*elem| elem.inst_map.deinit(gpa); | ||
| 342 | updated_files.deinit(gpa); | ||
| 343 | } | ||
| 344 | |||
| 345 | pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void { | ||
| 346 | assert(pt.tid == .main); | ||
| 344 | const zcu = pt.zcu; | 347 | const zcu = pt.zcu; |
| 345 | const ip = &zcu.intern_pool; | 348 | const ip = &zcu.intern_pool; |
| 346 | const gpa = zcu.gpa; | 349 | const gpa = zcu.gpa; |
| 347 | const new_zir = file.zir; | ||
| 348 | |||
| 349 | var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{}; | ||
| 350 | defer inst_map.deinit(gpa); | ||
| 351 | 350 | ||
| 352 | try Zcu.mapOldZirToNew(gpa, old_zir, new_zir, &inst_map); | 351 | // We need to visit every updated File for every TrackedInst in InternPool. |
| 352 | var updated_files: std.ArrayListUnmanaged(UpdatedFile) = .{}; | ||
| 353 | defer cleanupUpdatedFiles(gpa, &updated_files); | ||
| 354 | for (zcu.import_table.values()) |file_index| { | ||
| 355 | const file = zcu.fileByIndex(file_index); | ||
| 356 | const old_zir = file.prev_zir orelse continue; | ||
| 357 | const new_zir = file.zir; | ||
| 358 | try updated_files.append(gpa, .{ | ||
| 359 | .file_index = file_index, | ||
| 360 | .file = file, | ||
| 361 | .inst_map = .{}, | ||
| 362 | }); | ||
| 363 | const inst_map = &updated_files.items[updated_files.items.len - 1].inst_map; | ||
| 364 | try Zcu.mapOldZirToNew(gpa, old_zir.*, new_zir, inst_map); | ||
| 365 | } | ||
| 353 | 366 | ||
| 354 | const old_tag = old_zir.instructions.items(.tag); | 367 | if (updated_files.items.len == 0) |
| 355 | const old_data = old_zir.instructions.items(.data); | 368 | return; |
| 356 | 369 | ||
| 357 | // TODO: this should be done after all AstGen workers complete, to avoid | ||
| 358 | // iterating over this full set for every updated file. | ||
| 359 | for (ip.locals, 0..) |*local, tid| { | 370 | for (ip.locals, 0..) |*local, tid| { |
| 360 | local.mutate.tracked_insts.mutex.lock(); | ||
| 361 | defer local.mutate.tracked_insts.mutex.unlock(); | ||
| 362 | const tracked_insts_list = local.getMutableTrackedInsts(gpa); | 371 | const tracked_insts_list = local.getMutableTrackedInsts(gpa); |
| 363 | for (tracked_insts_list.view().items(.@"0"), 0..) |*tracked_inst, tracked_inst_unwrapped_index| { | 372 | for (tracked_insts_list.view().items(.@"0"), 0..) |*tracked_inst, tracked_inst_unwrapped_index| { |
| 364 | if (tracked_inst.file != file_index) continue; | 373 | for (updated_files.items) |updated_file| { |
| 365 | const old_inst = tracked_inst.inst; | 374 | const file_index = updated_file.file_index; |
| 366 | const tracked_inst_index = (InternPool.TrackedInst.Index.Unwrapped{ | 375 | if (tracked_inst.file != file_index) continue; |
| 367 | .tid = @enumFromInt(tid), | 376 | |
| 368 | .index = @intCast(tracked_inst_unwrapped_index), | 377 | const file = updated_file.file; |
| 369 | }).wrap(ip); | 378 | const old_zir = file.prev_zir.?.*; |
| 370 | tracked_inst.inst = inst_map.get(old_inst) orelse { | 379 | const new_zir = file.zir; |
| 371 | // Tracking failed for this instruction. Invalidate associated `src_hash` deps. | 380 | const old_tag = old_zir.instructions.items(.tag); |
| 372 | zcu.comp.mutex.lock(); | 381 | const old_data = old_zir.instructions.items(.data); |
| 373 | defer zcu.comp.mutex.unlock(); | 382 | const inst_map = &updated_file.inst_map; |
| 374 | log.debug("tracking failed for %{d}", .{old_inst}); | 383 | |
| 375 | try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index }); | 384 | const old_inst = tracked_inst.inst; |
| 376 | continue; | 385 | const tracked_inst_index = (InternPool.TrackedInst.Index.Unwrapped{ |
| 377 | }; | 386 | .tid = @enumFromInt(tid), |
| 387 | .index = @intCast(tracked_inst_unwrapped_index), | ||
| 388 | }).wrap(ip); | ||
| 389 | tracked_inst.inst = inst_map.get(old_inst) orelse { | ||
| 390 | // Tracking failed for this instruction. Invalidate associated `src_hash` deps. | ||
| 391 | log.debug("tracking failed for %{d}", .{old_inst}); | ||
| 392 | try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index }); | ||
| 393 | continue; | ||
| 394 | }; | ||
| 378 | 395 | ||
| 379 | if (old_zir.getAssociatedSrcHash(old_inst)) |old_hash| hash_changed: { | 396 | if (old_zir.getAssociatedSrcHash(old_inst)) |old_hash| hash_changed: { |
| 380 | if (new_zir.getAssociatedSrcHash(tracked_inst.inst)) |new_hash| { | 397 | if (new_zir.getAssociatedSrcHash(tracked_inst.inst)) |new_hash| { |
| 381 | if (std.zig.srcHashEql(old_hash, new_hash)) { | 398 | if (std.zig.srcHashEql(old_hash, new_hash)) { |
| 382 | break :hash_changed; | 399 | break :hash_changed; |
| 400 | } | ||
| 401 | log.debug("hash for (%{d} -> %{d}) changed: {} -> {}", .{ | ||
| 402 | old_inst, | ||
| 403 | tracked_inst.inst, | ||
| 404 | std.fmt.fmtSliceHexLower(&old_hash), | ||
| 405 | std.fmt.fmtSliceHexLower(&new_hash), | ||
| 406 | }); | ||
| 383 | } | 407 | } |
| 384 | log.debug("hash for (%{d} -> %{d}) changed: {} -> {}", .{ | 408 | // The source hash associated with this instruction changed - invalidate relevant dependencies. |
| 385 | old_inst, | 409 | try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index }); |
| 386 | tracked_inst.inst, | ||
| 387 | std.fmt.fmtSliceHexLower(&old_hash), | ||
| 388 | std.fmt.fmtSliceHexLower(&new_hash), | ||
| 389 | }); | ||
| 390 | } | 410 | } |
| 391 | // The source hash associated with this instruction changed - invalidate relevant dependencies. | ||
| 392 | zcu.comp.mutex.lock(); | ||
| 393 | defer zcu.comp.mutex.unlock(); | ||
| 394 | try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index }); | ||
| 395 | } | ||
| 396 | 411 | ||
| 397 | // If this is a `struct_decl` etc, we must invalidate any outdated namespace dependencies. | 412 | // If this is a `struct_decl` etc, we must invalidate any outdated namespace dependencies. |
| 398 | const has_namespace = switch (old_tag[@intFromEnum(old_inst)]) { | 413 | const has_namespace = switch (old_tag[@intFromEnum(old_inst)]) { |
| 399 | .extended => switch (old_data[@intFromEnum(old_inst)].extended.opcode) { | 414 | .extended => switch (old_data[@intFromEnum(old_inst)].extended.opcode) { |
| 400 | .struct_decl, .union_decl, .opaque_decl, .enum_decl => true, | 415 | .struct_decl, .union_decl, .opaque_decl, .enum_decl => true, |
| 416 | else => false, | ||
| 417 | }, | ||
| 401 | else => false, | 418 | else => false, |
| 402 | }, | 419 | }; |
| 403 | else => false, | 420 | if (!has_namespace) continue; |
| 404 | }; | 421 | |
| 405 | if (!has_namespace) continue; | 422 | var old_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{}; |
| 406 | 423 | defer old_names.deinit(zcu.gpa); | |
| 407 | var old_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{}; | 424 | { |
| 408 | defer old_names.deinit(zcu.gpa); | 425 | var it = old_zir.declIterator(old_inst); |
| 409 | { | 426 | while (it.next()) |decl_inst| { |
| 410 | var it = old_zir.declIterator(old_inst); | 427 | const decl_name = old_zir.getDeclaration(decl_inst)[0].name; |
| 411 | while (it.next()) |decl_inst| { | 428 | switch (decl_name) { |
| 412 | const decl_name = old_zir.getDeclaration(decl_inst)[0].name; | 429 | .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue, |
| 413 | switch (decl_name) { | 430 | _ => if (decl_name.isNamedTest(old_zir)) continue, |
| 414 | .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue, | 431 | } |
| 415 | _ => if (decl_name.isNamedTest(old_zir)) continue, | 432 | const name_zir = decl_name.toString(old_zir).?; |
| 433 | const name_ip = try zcu.intern_pool.getOrPutString( | ||
| 434 | zcu.gpa, | ||
| 435 | pt.tid, | ||
| 436 | old_zir.nullTerminatedString(name_zir), | ||
| 437 | .no_embedded_nulls, | ||
| 438 | ); | ||
| 439 | try old_names.put(zcu.gpa, name_ip, {}); | ||
| 416 | } | 440 | } |
| 417 | const name_zir = decl_name.toString(old_zir).?; | ||
| 418 | const name_ip = try zcu.intern_pool.getOrPutString( | ||
| 419 | zcu.gpa, | ||
| 420 | pt.tid, | ||
| 421 | old_zir.nullTerminatedString(name_zir), | ||
| 422 | .no_embedded_nulls, | ||
| 423 | ); | ||
| 424 | try old_names.put(zcu.gpa, name_ip, {}); | ||
| 425 | } | 441 | } |
| 426 | } | 442 | var any_change = false; |
| 427 | var any_change = false; | 443 | { |
| 428 | { | 444 | var it = new_zir.declIterator(tracked_inst.inst); |
| 429 | var it = new_zir.declIterator(tracked_inst.inst); | 445 | while (it.next()) |decl_inst| { |
| 430 | while (it.next()) |decl_inst| { | 446 | const decl_name = old_zir.getDeclaration(decl_inst)[0].name; |
| 431 | const decl_name = old_zir.getDeclaration(decl_inst)[0].name; | 447 | switch (decl_name) { |
| 432 | switch (decl_name) { | 448 | .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue, |
| 433 | .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue, | 449 | _ => if (decl_name.isNamedTest(old_zir)) continue, |
| 434 | _ => if (decl_name.isNamedTest(old_zir)) continue, | 450 | } |
| 451 | const name_zir = decl_name.toString(old_zir).?; | ||
| 452 | const name_ip = try zcu.intern_pool.getOrPutString( | ||
| 453 | zcu.gpa, | ||
| 454 | pt.tid, | ||
| 455 | old_zir.nullTerminatedString(name_zir), | ||
| 456 | .no_embedded_nulls, | ||
| 457 | ); | ||
| 458 | if (!old_names.swapRemove(name_ip)) continue; | ||
| 459 | // Name added | ||
| 460 | any_change = true; | ||
| 461 | try zcu.markDependeeOutdated(.{ .namespace_name = .{ | ||
| 462 | .namespace = tracked_inst_index, | ||
| 463 | .name = name_ip, | ||
| 464 | } }); | ||
| 435 | } | 465 | } |
| 436 | const name_zir = decl_name.toString(old_zir).?; | 466 | } |
| 437 | const name_ip = try zcu.intern_pool.getOrPutString( | 467 | // The only elements remaining in `old_names` now are any names which were removed. |
| 438 | zcu.gpa, | 468 | for (old_names.keys()) |name_ip| { |
| 439 | pt.tid, | ||
| 440 | old_zir.nullTerminatedString(name_zir), | ||
| 441 | .no_embedded_nulls, | ||
| 442 | ); | ||
| 443 | if (!old_names.swapRemove(name_ip)) continue; | ||
| 444 | // Name added | ||
| 445 | any_change = true; | 469 | any_change = true; |
| 446 | zcu.comp.mutex.lock(); | ||
| 447 | defer zcu.comp.mutex.unlock(); | ||
| 448 | try zcu.markDependeeOutdated(.{ .namespace_name = .{ | 470 | try zcu.markDependeeOutdated(.{ .namespace_name = .{ |
| 449 | .namespace = tracked_inst_index, | 471 | .namespace = tracked_inst_index, |
| 450 | .name = name_ip, | 472 | .name = name_ip, |
| 451 | } }); | 473 | } }); |
| 452 | } | 474 | } |
| 453 | } | ||
| 454 | // The only elements remaining in `old_names` now are any names which were removed. | ||
| 455 | for (old_names.keys()) |name_ip| { | ||
| 456 | any_change = true; | ||
| 457 | zcu.comp.mutex.lock(); | ||
| 458 | defer zcu.comp.mutex.unlock(); | ||
| 459 | try zcu.markDependeeOutdated(.{ .namespace_name = .{ | ||
| 460 | .namespace = tracked_inst_index, | ||
| 461 | .name = name_ip, | ||
| 462 | } }); | ||
| 463 | } | ||
| 464 | 475 | ||
| 465 | if (any_change) { | 476 | if (any_change) { |
| 466 | zcu.comp.mutex.lock(); | 477 | try zcu.markDependeeOutdated(.{ .namespace = tracked_inst_index }); |
| 467 | defer zcu.comp.mutex.unlock(); | 478 | } |
| 468 | try zcu.markDependeeOutdated(.{ .namespace = tracked_inst_index }); | ||
| 469 | } | 479 | } |
| 470 | } | 480 | } |
| 471 | } | 481 | } |
| 482 | |||
| 483 | for (updated_files.items) |updated_file| { | ||
| 484 | const file = updated_file.file; | ||
| 485 | const prev_zir = file.prev_zir.?; | ||
| 486 | file.prev_zir = null; | ||
| 487 | prev_zir.deinit(gpa); | ||
| 488 | gpa.destroy(prev_zir); | ||
| 489 | } | ||
| 472 | } | 490 | } |
| 473 | 491 | ||
| 474 | /// Like `ensureDeclAnalyzed`, but the Decl is a file's root Decl. | 492 | /// Like `ensureDeclAnalyzed`, but the Decl is a file's root Decl. |
src/main.zig+3| ... | @@ -4230,6 +4230,9 @@ fn serveUpdateResults(s: *Server, comp: *Compilation) !void { | ... | @@ -4230,6 +4230,9 @@ fn serveUpdateResults(s: *Server, comp: *Compilation) !void { |
| 4230 | }); | 4230 | }); |
| 4231 | return; | 4231 | return; |
| 4232 | } | 4232 | } |
| 4233 | |||
| 4234 | // Serve empty error bundle to indicate the update is done. | ||
| 4235 | try s.serveErrorBundle(std.zig.ErrorBundle.empty); | ||
| 4233 | } | 4236 | } |
| 4234 | 4237 | ||
| 4235 | fn runOrTest( | 4238 | fn runOrTest( |