authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-05-05 13:16:14-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-05-05 13:16:14-07:00
log5d7f2697deadbcc6f2b370c6eaa69099d6a54ee7
treeb6648cc43d6d13d1665e473813cdf0a210204255
parent6b5d0b371111fd458e7fd0a1500f0d93914b5db1

stage2: add `zig changelist` debug command

and implement the first pass at mechanism to map old ZIR to new ZIR.

5 files changed, 454 insertions(+), 9 deletions(-)

BRANCH_TODO+12-2
......@@ -1,5 +1,11 @@
1 * AstGen threadlocal
2 * extern "foo" for vars and for functions
1 * implement the iterators that updateZirRefs needs
2 - for iterating over ZIR decls
3 - for iterating over ZIR instructions within a decl to find decl instructions
4 * implement `zig zirdiff a.zig b.zig` for showing debug output for how a particular
5 source transformation will be seen by the change detection algorithm.
6 * communicate the changelist back to the driver code and process it in semantic analysis,
7 handling deletions and outdatings.
8
39 * namespace decls table can't reference ZIR memory because it can get modified on updates
410 - change it for astgen worker to compare old and new ZIR, updating existing
511 namespaces & decls, and creating a changelist.
......@@ -55,4 +61,8 @@
5561 natural alignment for fields and do not have any comptime fields. this
5662 will save 16 bytes per struct field in the compilation.
5763
64 * AstGen threadlocal
65 * extern "foo" for vars
5866
67 * TODO all decls should probably store source hash. Without this,
68 we currently unnecessarily mark all anon decls outdated here.
src/Module.zig+189-3
......@@ -190,6 +190,7 @@ pub const Decl = struct {
190190 /// Index to ZIR `extra` array to the entry in the parent's decl structure
191191 /// (the part that says "for every decls_len"). The first item at this index is
192192 /// the contents hash, followed by line, name, etc.
193 /// For anonymous decls and also the root Decl for a File, this is 0.
193194 zir_decl_index: Zir.Inst.Index,
194195
195196 /// Represents the "shallow" analysis status. For example, for decls that are functions,
......@@ -329,6 +330,7 @@ pub const Decl = struct {
329330 }
330331
331332 pub fn getNameZir(decl: Decl, zir: Zir) ?[:0]const u8 {
333 assert(decl.zir_decl_index != 0);
332334 const name_index = zir.extra[decl.zir_decl_index + 5];
333335 if (name_index <= 1) return null;
334336 return zir.nullTerminatedString(name_index);
......@@ -340,24 +342,28 @@ pub const Decl = struct {
340342 }
341343
342344 pub fn contentsHashZir(decl: Decl, zir: Zir) std.zig.SrcHash {
345 assert(decl.zir_decl_index != 0);
343346 const hash_u32s = zir.extra[decl.zir_decl_index..][0..4];
344347 const contents_hash = @bitCast(std.zig.SrcHash, hash_u32s.*);
345348 return contents_hash;
346349 }
347350
348351 pub fn zirBlockIndex(decl: Decl) Zir.Inst.Index {
352 assert(decl.zir_decl_index != 0);
349353 const zir = decl.namespace.file_scope.zir;
350354 return zir.extra[decl.zir_decl_index + 6];
351355 }
352356
353357 pub fn zirAlignRef(decl: Decl) Zir.Inst.Ref {
354358 if (!decl.has_align) return .none;
359 assert(decl.zir_decl_index != 0);
355360 const zir = decl.namespace.file_scope.zir;
356361 return @intToEnum(Zir.Inst.Ref, zir.extra[decl.zir_decl_index + 6]);
357362 }
358363
359364 pub fn zirLinksectionRef(decl: Decl) Zir.Inst.Ref {
360365 if (!decl.has_linksection) return .none;
366 assert(decl.zir_decl_index != 0);
361367 const zir = decl.namespace.file_scope.zir;
362368 const extra_index = decl.zir_decl_index + 6 + @boolToInt(decl.has_align);
363369 return @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
......@@ -430,6 +436,15 @@ pub const Decl = struct {
430436 return tv.ty.zigTypeTag() == .Fn;
431437 }
432438
439 /// If the Decl has a value and it is a struct, return it,
440 /// otherwise null.
441 pub fn getStruct(decl: Decl) ?*Struct {
442 if (!decl.has_tv) return null;
443 const ty = (decl.val.castTag(.ty) orelse return null).data;
444 const struct_obj = (ty.castTag(.@"struct") orelse return null).data;
445 return struct_obj;
446 }
447
433448 pub fn dump(decl: *Decl) void {
434449 const loc = std.zig.findLineColumn(decl.scope.source.bytes, decl.src);
435450 std.debug.print("{s}:{d}:{d} name={s} status={s}", .{
......@@ -2335,7 +2350,8 @@ pub fn astGenFile(mod: *Module, file: *Scope.File, prog_node: *std.Progress.Node
23352350 // We do not need to hold any locks at this time because all the Decl and Namespace
23362351 // objects being touched are specific to this File, and the only other concurrent
23372352 // tasks are touching other File objects.
2338 @panic("TODO implement update references from old ZIR to new ZIR");
2353 const change_list = try updateZirRefs(gpa, file, prev_zir);
2354 @panic("TODO do something with change_list");
23392355 }
23402356
23412357 // TODO don't report compile errors until Sema @importFile
......@@ -2350,6 +2366,177 @@ pub fn astGenFile(mod: *Module, file: *Scope.File, prog_node: *std.Progress.Node
23502366 }
23512367}
23522368
2369const UpdateChangeList = struct {
2370 deleted: []*const Decl,
2371 outdated: []*const Decl,
2372
2373 fn deinit(self: *UpdateChangeList, gpa: *Allocator) void {
2374 gpa.free(self.deleted);
2375 gpa.free(self.outdated);
2376 }
2377};
2378
2379/// Patch ups:
2380/// * Struct.zir_index
2381/// * Decl.zir_decl_index
2382/// * Decl.name
2383/// * Namespace.decl keys
2384fn updateZirRefs(gpa: *Allocator, file: *Scope.File, old_zir: Zir) !UpdateChangeList {
2385 const new_zir = file.zir;
2386
2387 // Maps from old ZIR to new ZIR, struct_decl, enum_decl, etc. Any instruction which
2388 // creates a namespace, gets mapped from old to new here.
2389 var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{};
2390 defer inst_map.deinit(gpa);
2391 // Maps from old ZIR to new ZIR, the extra data index for the sub-decl item.
2392 // e.g. the thing that Decl.zir_decl_index points to.
2393 var extra_map: std.AutoHashMapUnmanaged(u32, u32) = .{};
2394 defer extra_map.deinit(gpa);
2395
2396 try mapOldZirToNew(gpa, old_zir, new_zir, &inst_map, &extra_map);
2397
2398 // Build string table for new ZIR.
2399 var string_table: std.StringHashMapUnmanaged(u32) = .{};
2400 defer string_table.deinit(gpa);
2401 {
2402 var i: usize = 2;
2403 while (i < new_zir.string_bytes.len) {
2404 const string = new_zir.nullTerminatedString(i);
2405 try string_table.put(gpa, string, @intCast(u32, i));
2406 i += string.len + 1;
2407 }
2408 }
2409
2410 // Walk the Decl graph.
2411
2412 var decl_stack: std.ArrayListUnmanaged(*Decl) = .{};
2413 defer decl_stack.deinit(gpa);
2414
2415 const root_decl = file.namespace.getDecl();
2416 try decl_stack.append(gpa, root_decl);
2417
2418 var deleted_decls: std.ArrayListUnmanaged(*Decl) = .{};
2419 defer deleted_decls.deinit(gpa);
2420 var outdated_decls: std.ArrayListUnmanaged(*Decl) = .{};
2421 defer outdated_decls.deinit(gpa);
2422
2423 while (decl_stack.popOrNull()) |decl| {
2424 // Anonymous decls and the root decl have this set to 0. We still need
2425 // to walk them but we do not need to modify this value.
2426 if (decl.zir_decl_index != 0) {
2427 decl.zir_decl_index = extra_map.get(decl.zir_decl_index) orelse {
2428 try deleted_decls.append(gpa, decl);
2429 continue;
2430 };
2431 const new_name_index = string_table.get(mem.spanZ(decl.name)) orelse {
2432 try deleted_decls.append(gpa, decl);
2433 continue;
2434 };
2435 decl.name = new_zir.nullTerminatedString(new_name_index).ptr;
2436
2437 const old_hash = decl.contentsHashZir(old_zir);
2438 const new_hash = decl.contentsHashZir(new_zir);
2439 if (!std.zig.srcHashEql(old_hash, new_hash)) {
2440 try outdated_decls.append(gpa, decl);
2441 }
2442 } else {
2443 // TODO all decls should probably store source hash. Without this,
2444 // we currently unnecessarily mark all anon decls outdated here.
2445 try outdated_decls.append(gpa, decl);
2446 }
2447
2448 if (!decl.has_tv) continue;
2449
2450 if (decl.getStruct()) |struct_obj| {
2451 struct_obj.zir_index = inst_map.get(struct_obj.zir_index) orelse {
2452 try deleted_decls.append(gpa, decl);
2453 continue;
2454 };
2455 }
2456
2457 if (decl.val.getTypeNamespace()) |namespace| {
2458 for (namespace.decls.items()) |*entry| {
2459 const sub_decl = entry.value;
2460 if (sub_decl.zir_decl_index != 0) {
2461 const new_key_index = string_table.get(entry.key) orelse {
2462 try deleted_decls.append(gpa, sub_decl);
2463 continue;
2464 };
2465 entry.key = new_zir.nullTerminatedString(new_key_index);
2466 }
2467 try decl_stack.append(gpa, sub_decl);
2468 }
2469 }
2470 }
2471
2472 const outdated_slice = outdated_decls.toOwnedSlice(gpa);
2473 const deleted_slice = deleted_decls.toOwnedSlice(gpa);
2474
2475 return UpdateChangeList{
2476 .outdated = outdated_slice,
2477 .deleted = deleted_slice,
2478 };
2479}
2480
2481pub fn mapOldZirToNew(
2482 gpa: *Allocator,
2483 old_zir: Zir,
2484 new_zir: Zir,
2485 inst_map: *std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index),
2486 extra_map: *std.AutoHashMapUnmanaged(u32, u32),
2487) Allocator.Error!void {
2488 // Contain ZIR indexes of declaration instructions.
2489 const MatchedZirDecl = struct {
2490 old_inst: Zir.Inst.Index,
2491 new_inst: Zir.Inst.Index,
2492 };
2493 var match_stack: std.ArrayListUnmanaged(MatchedZirDecl) = .{};
2494 defer match_stack.deinit(gpa);
2495
2496 const old_main_struct_inst = old_zir.extra[@enumToInt(Zir.ExtraIndex.main_struct)] -
2497 @intCast(u32, Zir.Inst.Ref.typed_value_map.len);
2498 const new_main_struct_inst = new_zir.extra[@enumToInt(Zir.ExtraIndex.main_struct)] -
2499 @intCast(u32, Zir.Inst.Ref.typed_value_map.len);
2500
2501 try match_stack.append(gpa, .{
2502 .old_inst = old_main_struct_inst,
2503 .new_inst = new_main_struct_inst,
2504 });
2505
2506 while (match_stack.popOrNull()) |match_item| {
2507 try inst_map.put(gpa, match_item.old_inst, match_item.new_inst);
2508
2509 // Maps name to extra index of decl sub item.
2510 var decl_map: std.StringHashMapUnmanaged(u32) = .{};
2511 defer decl_map.deinit(gpa);
2512
2513 {
2514 var old_decl_it = old_zir.declIterator(match_item.old_inst);
2515 while (old_decl_it.next()) |old_decl| {
2516 try decl_map.put(gpa, old_decl.name, old_decl.sub_index);
2517 }
2518 }
2519
2520 var new_decl_it = new_zir.declIterator(match_item.new_inst);
2521 while (new_decl_it.next()) |new_decl| {
2522 const old_extra_index = decl_map.get(new_decl.name) orelse continue;
2523 const new_extra_index = new_decl.sub_index;
2524 try extra_map.put(gpa, old_extra_index, new_extra_index);
2525
2526 //var old_it = declInstIterator(old_zir, old_extra_index);
2527 //var new_it = declInstIterator(new_zir, new_extra_index);
2528 //while (true) {
2529 // const old_decl_inst = old_it.next() orelse break;
2530 // const new_decl_inst = new_it.next() orelse break;
2531 // try match_stack.append(gpa, .{
2532 // .old_inst = old_decl_inst,
2533 // .new_inst = new_decl_inst,
2534 // });
2535 //}
2536 }
2537 }
2538}
2539
23532540pub fn ensureDeclAnalyzed(mod: *Module, decl: *Decl) InnerError!void {
23542541 const tracy = trace(@src());
23552542 defer tracy.end();
......@@ -2487,7 +2674,6 @@ pub fn semaFile(mod: *Module, file: *Scope.File) InnerError!void {
24872674 new_decl.is_exported = false;
24882675 new_decl.has_align = false;
24892676 new_decl.has_linksection = false;
2490 new_decl.zir_decl_index = undefined;
24912677 new_decl.ty = struct_ty;
24922678 new_decl.val = struct_val;
24932679 new_decl.has_tv = true;
......@@ -3256,7 +3442,7 @@ fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: ast.Node
32563442 .linksection_val = undefined,
32573443 .analysis = .unreferenced,
32583444 .deletion_flag = false,
3259 .zir_decl_index = undefined,
3445 .zir_decl_index = 0,
32603446 .link = switch (mod.comp.bin_file.tag) {
32613447 .coff => .{ .coff = link.File.Coff.TextBlock.empty },
32623448 .elf => .{ .elf = link.File.Elf.TextBlock.empty },
src/Zir.zig+109-2
......@@ -3702,6 +3702,8 @@ const Writer = struct {
37023702 const has_section = @truncate(u1, cur_bit_bag) != 0;
37033703 cur_bit_bag >>= 1;
37043704
3705 const sub_index = extra_index;
3706
37053707 const hash_u32s = self.code.extra[extra_index..][0..4];
37063708 extra_index += 4;
37073709 const line = self.code.extra[extra_index];
......@@ -3738,8 +3740,8 @@ const Writer = struct {
37383740 raw_decl_name;
37393741 const test_str = if (raw_decl_name.len == 0) "test " else "";
37403742 const export_str = if (is_exported) "export " else "";
3741 try stream.print("{s}{s}{s}{}", .{
3742 pub_str, test_str, export_str, std.zig.fmtId(decl_name),
3743 try stream.print("[{d}] {s}{s}{s}{}", .{
3744 sub_index, pub_str, test_str, export_str, std.zig.fmtId(decl_name),
37433745 });
37443746 if (align_inst != .none) {
37453747 try stream.writeAll(" align(");
......@@ -4334,3 +4336,108 @@ const Writer = struct {
43344336 }
43354337 }
43364338};
4339
4340pub const DeclIterator = struct {
4341 extra_index: usize,
4342 bit_bag_index: usize,
4343 cur_bit_bag: u32,
4344 decl_i: u32,
4345 decls_len: u32,
4346 zir: Zir,
4347
4348 pub const Item = struct {
4349 name: [:0]const u8,
4350 sub_index: u32,
4351 };
4352
4353 pub fn next(it: *DeclIterator) ?Item {
4354 if (it.decl_i >= it.decls_len) return null;
4355
4356 if (it.decl_i % 8 == 0) {
4357 it.cur_bit_bag = it.zir.extra[it.bit_bag_index];
4358 it.bit_bag_index += 1;
4359 }
4360 it.decl_i += 1;
4361
4362 const flags = @truncate(u4, it.cur_bit_bag);
4363 it.cur_bit_bag >>= 4;
4364
4365 const sub_index = @intCast(u32, it.extra_index);
4366 it.extra_index += 5; // src_hash(4) + line(1)
4367 const name = it.zir.nullTerminatedString(it.zir.extra[it.extra_index]);
4368 it.extra_index += 2; // name(1) + value(1)
4369 it.extra_index += @truncate(u1, flags >> 2);
4370 it.extra_index += @truncate(u1, flags >> 3);
4371
4372 return Item{
4373 .sub_index = sub_index,
4374 .name = name,
4375 };
4376 }
4377};
4378
4379pub fn declIterator(zir: Zir, decl_inst: u32) DeclIterator {
4380 const tags = zir.instructions.items(.tag);
4381 const datas = zir.instructions.items(.data);
4382 const decl_info: struct {
4383 extra_index: usize,
4384 decls_len: u32,
4385 } = switch (tags[decl_inst]) {
4386 .struct_decl,
4387 .struct_decl_packed,
4388 .struct_decl_extern,
4389 => blk: {
4390 const inst_data = datas[decl_inst].pl_node;
4391 const extra = zir.extraData(Inst.StructDecl, inst_data.payload_index);
4392 break :blk .{
4393 .extra_index = extra.end,
4394 .decls_len = extra.data.decls_len,
4395 };
4396 },
4397
4398 .union_decl,
4399 .union_decl_packed,
4400 .union_decl_extern,
4401 => blk: {
4402 const inst_data = datas[decl_inst].pl_node;
4403 const extra = zir.extraData(Inst.UnionDecl, inst_data.payload_index);
4404 break :blk .{
4405 .extra_index = extra.end,
4406 .decls_len = extra.data.decls_len,
4407 };
4408 },
4409
4410 .enum_decl,
4411 .enum_decl_nonexhaustive,
4412 => blk: {
4413 const inst_data = datas[decl_inst].pl_node;
4414 const extra = zir.extraData(Inst.EnumDecl, inst_data.payload_index);
4415 break :blk .{
4416 .extra_index = extra.end,
4417 .decls_len = extra.data.decls_len,
4418 };
4419 },
4420
4421 .opaque_decl => blk: {
4422 const inst_data = datas[decl_inst].pl_node;
4423 const extra = zir.extraData(Inst.OpaqueDecl, inst_data.payload_index);
4424 break :blk .{
4425 .extra_index = extra.end,
4426 .decls_len = extra.data.decls_len,
4427 };
4428 },
4429
4430 else => unreachable,
4431 };
4432
4433 const bit_bags_count = std.math.divCeil(usize, decl_info.decls_len, 8) catch unreachable;
4434
4435 return .{
4436 .zir = zir,
4437 .extra_index = decl_info.extra_index + bit_bags_count,
4438 .bit_bag_index = decl_info.extra_index,
4439 .cur_bit_bag = undefined,
4440 .decl_i = 0,
4441 .decls_len = decl_info.decls_len,
4442 };
4443}
src/main.zig+142
......@@ -72,6 +72,7 @@ const debug_usage = normal_usage ++
7272 \\Debug Commands:
7373 \\
7474 \\ astgen Print ZIR code for a .zig source file
75 \\ changelist Compute mappings from old ZIR to new ZIR
7576 \\
7677;
7778
......@@ -231,6 +232,8 @@ pub fn mainArgs(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
231232 return io.getStdOut().writeAll(usage);
232233 } else if (debug_extensions_enabled and mem.eql(u8, cmd, "astgen")) {
233234 return cmdAstgen(gpa, arena, cmd_args);
235 } else if (debug_extensions_enabled and mem.eql(u8, cmd, "changelist")) {
236 return cmdChangelist(gpa, arena, cmd_args);
234237 } else {
235238 std.log.info("{s}", .{usage});
236239 fatal("unknown command: {s}", .{args[1]});
......@@ -3618,3 +3621,142 @@ pub fn cmdAstgen(
36183621
36193622 return Zir.renderAsTextToFile(gpa, &file, io.getStdOut());
36203623}
3624
3625/// This is only enabled for debug builds.
3626pub fn cmdChangelist(
3627 gpa: *Allocator,
3628 arena: *Allocator,
3629 args: []const []const u8,
3630) !void {
3631 const Module = @import("Module.zig");
3632 const AstGen = @import("AstGen.zig");
3633 const Zir = @import("Zir.zig");
3634
3635 const old_source_file = args[0];
3636 const new_source_file = args[1];
3637
3638 var f = try fs.cwd().openFile(old_source_file, .{});
3639 defer f.close();
3640
3641 const stat = try f.stat();
3642
3643 if (stat.size > max_src_size)
3644 return error.FileTooBig;
3645
3646 var file: Module.Scope.File = .{
3647 .status = .never_loaded,
3648 .source_loaded = false,
3649 .tree_loaded = false,
3650 .zir_loaded = false,
3651 .sub_file_path = old_source_file,
3652 .source = undefined,
3653 .stat_size = stat.size,
3654 .stat_inode = stat.inode,
3655 .stat_mtime = stat.mtime,
3656 .tree = undefined,
3657 .zir = undefined,
3658 .pkg = undefined,
3659 .namespace = undefined,
3660 };
3661
3662 const source = try arena.allocSentinel(u8, stat.size, 0);
3663 const amt = try f.readAll(source);
3664 if (amt != stat.size)
3665 return error.UnexpectedEndOfFile;
3666 file.source = source;
3667 file.source_loaded = true;
3668
3669 file.tree = try std.zig.parse(gpa, file.source);
3670 file.tree_loaded = true;
3671 defer file.tree.deinit(gpa);
3672
3673 for (file.tree.errors) |parse_error| {
3674 try printErrMsgToFile(gpa, parse_error, file.tree, old_source_file, io.getStdErr(), .auto);
3675 }
3676 if (file.tree.errors.len != 0) {
3677 process.exit(1);
3678 }
3679
3680 file.zir = try AstGen.generate(gpa, file.tree);
3681 file.zir_loaded = true;
3682 defer file.zir.deinit(gpa);
3683
3684 if (file.zir.hasCompileErrors()) {
3685 var errors = std.ArrayList(Compilation.AllErrors.Message).init(arena);
3686 try Compilation.AllErrors.addZir(arena, &errors, &file);
3687 const ttyconf = std.debug.detectTTYConfig();
3688 for (errors.items) |full_err_msg| {
3689 full_err_msg.renderToStdErr(ttyconf);
3690 }
3691 process.exit(1);
3692 }
3693
3694 var new_f = try fs.cwd().openFile(new_source_file, .{});
3695 defer new_f.close();
3696
3697 const new_stat = try new_f.stat();
3698
3699 if (new_stat.size > max_src_size)
3700 return error.FileTooBig;
3701
3702 const new_source = try arena.allocSentinel(u8, new_stat.size, 0);
3703 const new_amt = try new_f.readAll(new_source);
3704 if (new_amt != new_stat.size)
3705 return error.UnexpectedEndOfFile;
3706
3707 var new_tree = try std.zig.parse(gpa, new_source);
3708 defer new_tree.deinit(gpa);
3709
3710 for (new_tree.errors) |parse_error| {
3711 try printErrMsgToFile(gpa, parse_error, new_tree, new_source_file, io.getStdErr(), .auto);
3712 }
3713 if (new_tree.errors.len != 0) {
3714 process.exit(1);
3715 }
3716
3717 var old_zir = file.zir;
3718 defer old_zir.deinit(gpa);
3719 file.zir_loaded = false;
3720 file.zir = try AstGen.generate(gpa, new_tree);
3721 file.zir_loaded = true;
3722
3723 if (file.zir.hasCompileErrors()) {
3724 var errors = std.ArrayList(Compilation.AllErrors.Message).init(arena);
3725 try Compilation.AllErrors.addZir(arena, &errors, &file);
3726 const ttyconf = std.debug.detectTTYConfig();
3727 for (errors.items) |full_err_msg| {
3728 full_err_msg.renderToStdErr(ttyconf);
3729 }
3730 process.exit(1);
3731 }
3732
3733 var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{};
3734 defer inst_map.deinit(gpa);
3735
3736 var extra_map: std.AutoHashMapUnmanaged(u32, u32) = .{};
3737 defer extra_map.deinit(gpa);
3738
3739 try Module.mapOldZirToNew(gpa, old_zir, file.zir, &inst_map, &extra_map);
3740
3741 var bw = io.bufferedWriter(io.getStdOut().writer());
3742 const stdout = bw.writer();
3743 {
3744 try stdout.print("Instruction mappings:\n", .{});
3745 var it = inst_map.iterator();
3746 while (it.next()) |entry| {
3747 try stdout.print(" %{d} => %{d}\n", .{
3748 entry.key, entry.value,
3749 });
3750 }
3751 }
3752 {
3753 try stdout.print("Extra mappings:\n", .{});
3754 var it = extra_map.iterator();
3755 while (it.next()) |entry| {
3756 try stdout.print(" {d} => {d}\n", .{
3757 entry.key, entry.value,
3758 });
3759 }
3760 }
3761 try bw.flush();
3762}
test/stage2/test.zig+2-2
......@@ -28,13 +28,13 @@ pub fn addCases(ctx: *TestContext) !void {
2828
2929 // Incorrect return type
3030 case.addError(
31 \\export fn _start() noreturn {
31 \\pub export fn _start() noreturn {
3232 \\}
3333 , &[_][]const u8{":2:1: error: expected noreturn, found void"});
3434
3535 // Regular old hello world
3636 case.addCompareOutput(
37 \\export fn _start() noreturn {
37 \\pub export fn _start() noreturn {
3838 \\ print();
3939 \\
4040 \\ exit();