From 272fe0cbfe4d59a307389e20b3bf57099b182ebe Mon Sep 17 00:00:00 2001 From: Timon Kruiper Date: Thu, 8 Apr 2021 14:20:40 +0200 Subject: [PATCH 01/17] stage2: fix bug in ZIR gen of global comptime block A global comptime block did not end with a break_inline instruction which caused an assertion to be hit. --- src/Module.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Module.zig b/src/Module.zig index 933917d9484096d70710f2461170068da9b897fe..dc22b3fd3b5b6bcf0849e84a379ef4554794acb5 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2513,6 +2513,7 @@ fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool { const block_expr = node_datas[decl_node].lhs; _ = try AstGen.comptimeExpr(&gen_scope, &gen_scope.base, .none, block_expr); + _ = try gen_scope.addBreak(.break_inline, gen_scope.break_block, .void_value); const code = try gen_scope.finish(); if (std.builtin.mode == .Debug and mod.comp.verbose_ir) { -- 2.54.0 From 91e416bbf049b875d090ba050081d9964ed4b452 Mon Sep 17 00:00:00 2001 From: Timon Kruiper Date: Thu, 8 Apr 2021 14:22:56 +0200 Subject: [PATCH 02/17] stage2: add a simplified export builtin call std.builtin.ExportOptions is not yet supported, thus the second argument of export is now a simple string that specifies the exported symbol name. --- src/AstGen.zig | 14 +++++++++++++- src/Sema.zig | 29 +++++++++++++++++++++++++++++ src/zir.zig | 6 ++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 8ee27b16588bb6efd9d3bf717858cadff48dfa1c..364f76cc7abdd6990599d67c836f71aa732eb387 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1336,6 +1336,7 @@ fn blockExprStmts( .dbg_stmt_node, .ensure_result_used, .ensure_result_non_error, + .@"export", .set_eval_branch_quota, .compile_log, .ensure_err_payload_void, @@ -4146,6 +4147,18 @@ fn builtinCall( return rvalue(gz, scope, rl, result, node); }, + .@"export" => { + const target_fn = try expr(gz, scope, .none, params[0]); + // FIXME: When structs work in stage2, actually implement this correctly! + // Currently the name is always signifies Strong linkage. + const export_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]); + _ = try gz.addPlNode(.@"export", node, zir.Inst.Bin{ + .lhs = target_fn, + .rhs = export_name, + }); + return rvalue(gz, scope, rl, .void_value, node); + }, + .add_with_overflow, .align_cast, .align_of, @@ -4175,7 +4188,6 @@ fn builtinCall( .error_name, .error_return_trace, .err_set_cast, - .@"export", .fence, .field_parent_ptr, .float_to_int, diff --git a/src/Sema.zig b/src/Sema.zig index 519d5df40164ea887fbeaf6fd79bb8069b7421e8..d2abaaf09148856beec97f1484aad125b59f8081 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -342,6 +342,10 @@ pub fn analyzeBody( try sema.zirValidateStructInitPtr(block, inst); continue; }, + .@"export" => { + try sema.zirExport(block, inst); + continue; + }, // Special case instructions to handle comptime control flow. .repeat_inline => { @@ -1333,6 +1337,31 @@ fn analyzeBlockBody( return &merges.block_inst.base; } +fn zirExport(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void { + const tracy = trace(@src()); + defer tracy.end(); + + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data; + const src = inst_data.src(); + + const target_fn = try sema.resolveInst(extra.lhs); + const target_fn_val = try sema.resolveConstValue( + block, + .{ .node_offset_builtin_call_arg0 = inst_data.src_node }, + target_fn, + ); + + const export_name = try sema.resolveConstString( + block, + .{ .node_offset_builtin_call_arg1 = inst_data.src_node }, + extra.rhs, + ); + + const actual_fn = target_fn_val.castTag(.function).?.data; + try sema.mod.analyzeExport(&block.base, src, export_name, actual_fn.owner_decl); +} + fn zirBreakpoint(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void { const tracy = trace(@src()); defer tracy.end(); diff --git a/src/zir.zig b/src/zir.zig index d3d9d56bfd501a0c15f7c4413f9b9c163e8e956d..ff402a9ddba721e2fcd34cfae3872bed6c31ebcc 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -328,6 +328,10 @@ pub const Inst = struct { error_union_type, /// `error.Foo` syntax. Uses the `str_tok` field of the Data union. error_value, + /// Exports a function with a specified name. This can be used at comptime + /// to export a function conditionally. + /// Uses the `pl_node` union field. Payload is `Bin`. + @"export", /// Given a pointer to a struct or object that contains virtual fields, returns a pointer /// to the named field. The field name is stored in string_bytes. Used by a.b syntax. /// Uses `pl_node` field. The AST node is the a.b syntax. Payload is Field. @@ -737,6 +741,7 @@ pub const Inst = struct { .elem_val_node, .ensure_result_used, .ensure_result_non_error, + .@"export", .floatcast, .field_ptr, .field_val, @@ -1682,6 +1687,7 @@ const Writer = struct { .xor, .store_node, .error_union_type, + .@"export", .merge_error_sets, .bit_and, .bit_or, -- 2.54.0 From fb16cb9183bfdf5db9448666803943127802317a Mon Sep 17 00:00:00 2001 From: Timon Kruiper Date: Mon, 5 Apr 2021 20:44:33 +0200 Subject: [PATCH 03/17] stage2: add initial support for builtin pkg We currently emit a simplified builtin2.zig that is able to be parsed and correctly interpreted in stage2. --- src/Compilation.zig | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/Compilation.zig b/src/Compilation.zig index 080d4bddaadfbf7e11feab45358496a75ff1fa21..95db5e78c60b61aba2dc409fb4486b00f082803c 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -906,6 +906,9 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { artifact_sub_dir, }; + const builtin_pkg = try Package.create(gpa, zig_cache_artifact_directory.path.?, "builtin2.zig"); + try root_pkg.add(gpa, "builtin", builtin_pkg); + // TODO when we implement serialization and deserialization of incremental compilation metadata, // this is where we would load it. We have open a handle to the directory where // the output either already is, or will be. @@ -2822,6 +2825,11 @@ fn updateBuiltinZigFile(comp: *Compilation, mod: *Module) !void { const source = try comp.generateBuiltinZigSource(comp.gpa); defer comp.gpa.free(source); try mod.zig_cache_artifact_directory.handle.writeFile("builtin.zig", source); + + // FIXME: Remove builtin2.zig when stage2 can correctly generate code for builtin.zig! + const source2 = try comp.generateBuiltin2ZigSource(comp.gpa); + defer comp.gpa.free(source2); + try mod.zig_cache_artifact_directory.handle.writeFile("builtin2.zig", source2); } pub fn dump_argv(argv: []const []const u8) void { @@ -2831,6 +2839,26 @@ pub fn dump_argv(argv: []const []const u8) void { std.debug.print("{s}\n", .{argv[argv.len - 1]}); } +fn generateBuiltin2ZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 { + var buffer = std.ArrayList(u8).init(allocator); + defer buffer.deinit(); + + const target = comp.getTarget(); + + try buffer.writer().print( + \\pub const link_libc = {}; + \\pub const arch = {}; + \\pub const os = {}; + \\ + , .{ + comp.bin_file.options.link_libc, + @enumToInt(target.cpu.arch), + @enumToInt(target.os.tag), + }); + + return buffer.toOwnedSlice(); +} + pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 { const tracy = trace(@src()); defer tracy.end(); -- 2.54.0 From a97efbd1850cbf12dbf9332f7da2652385a38cd6 Mon Sep 17 00:00:00 2001 From: Timon Kruiper Date: Mon, 5 Apr 2021 20:53:46 +0200 Subject: [PATCH 04/17] stage2: add support for root pkg Fix some infinite recursions, because the code assumed that packages cannot point to each other. But this assumption does not hold anymore. --- src/Compilation.zig | 12 +++++++----- src/Package.zig | 4 +++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 95db5e78c60b61aba2dc409fb4486b00f082803c..c0409fcb3bfd7da2afe5d28f0504759b80751fa8 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -510,11 +510,11 @@ pub const InitOptions = struct { fn addPackageTableToCacheHash( hash: *Cache.HashHelper, arena: *std.heap.ArenaAllocator, - pkg_table: Package.Table, + package: *Package, hash_type: union(enum) { path_bytes, files: *Cache.Manifest }, ) (error{OutOfMemory} || std.os.GetCwdError)!void { const allocator = &arena.allocator; - + const pkg_table = package.table; const packages = try allocator.alloc(Package.Table.Entry, pkg_table.count()); { // Copy over the hashmap entries to our slice @@ -547,7 +547,8 @@ fn addPackageTableToCacheHash( }, } // Recurse to handle the package's dependencies - try addPackageTableToCacheHash(hash, arena, pkg.value.table, hash_type); + if (package != pkg.value) + try addPackageTableToCacheHash(hash, arena, pkg.value, hash_type); } } @@ -885,7 +886,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { { var local_arena = std.heap.ArenaAllocator.init(gpa); defer local_arena.deinit(); - try addPackageTableToCacheHash(&hash, &local_arena, root_pkg.table, .path_bytes); + try addPackageTableToCacheHash(&hash, &local_arena, root_pkg, .path_bytes); } hash.add(valgrind); hash.add(single_threaded); @@ -908,6 +909,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { const builtin_pkg = try Package.create(gpa, zig_cache_artifact_directory.path.?, "builtin2.zig"); try root_pkg.add(gpa, "builtin", builtin_pkg); + try root_pkg.add(gpa, "root", root_pkg); // TODO when we implement serialization and deserialization of incremental compilation metadata, // this is where we would load it. We have open a handle to the directory where @@ -3203,7 +3205,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node { var local_arena = std.heap.ArenaAllocator.init(comp.gpa); defer local_arena.deinit(); - try addPackageTableToCacheHash(&man.hash, &local_arena, mod.root_pkg.table, .{ .files = &man }); + try addPackageTableToCacheHash(&man.hash, &local_arena, mod.root_pkg, .{ .files = &man }); } man.hash.add(comp.bin_file.options.valgrind); man.hash.add(comp.bin_file.options.single_threaded); diff --git a/src/Package.zig b/src/Package.zig index 33ff4766cae383518acdfc6a70058b5923b89984..03c9e9ea3d2b14f9bfa88430cbd492c3bc2dcb0f 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -58,7 +58,9 @@ pub fn destroy(pkg: *Package, gpa: *Allocator) void { { var it = pkg.table.iterator(); while (it.next()) |kv| { - kv.value.destroy(gpa); + if (pkg != kv.value) { + kv.value.destroy(gpa); + } gpa.free(kv.key); } } -- 2.54.0 From e85cd616ef6439fdb9e7ac118251bb7c2296e553 Mon Sep 17 00:00:00 2001 From: Timon Kruiper Date: Wed, 7 Apr 2021 14:55:11 +0200 Subject: [PATCH 05/17] stage2: implement builtin function hasDecl --- src/AstGen.zig | 12 +++++++++++- src/Sema.zig | 34 ++++++++++++++++++++++++++++++++++ src/zir.zig | 5 +++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 364f76cc7abdd6990599d67c836f71aa732eb387..20e52ea55933100b4601b4b3e5780a4b15dd38b5 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1245,6 +1245,7 @@ fn blockExprStmts( .fn_type_var_args, .fn_type_cc, .fn_type_cc_var_args, + .has_decl, .int, .float, .float128, @@ -4159,6 +4160,16 @@ fn builtinCall( return rvalue(gz, scope, rl, .void_value, node); }, + .has_decl => { + const container_type = try typeExpr(gz, scope, params[0]); + const name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]); + const result = try gz.addPlNode(.has_decl, node, zir.Inst.Bin{ + .lhs = container_type, + .rhs = name, + }); + return rvalue(gz, scope, rl, result, node); + }, + .add_with_overflow, .align_cast, .align_of, @@ -4191,7 +4202,6 @@ fn builtinCall( .fence, .field_parent_ptr, .float_to_int, - .has_decl, .has_field, .int_to_float, .int_to_ptr, diff --git a/src/Sema.zig b/src/Sema.zig index d2abaaf09148856beec97f1484aad125b59f8081..b4b4798850fd6d12969f38c326b54d0c9705afc7 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -199,6 +199,7 @@ pub fn analyzeBody( .fn_type_cc => try sema.zirFnTypeCc(block, inst, false), .fn_type_cc_var_args => try sema.zirFnTypeCc(block, inst, true), .fn_type_var_args => try sema.zirFnType(block, inst, true), + .has_decl => try sema.zirHasDecl(block, inst), .import => try sema.zirImport(block, inst), .indexable_ptr_len => try sema.zirIndexablePtrLen(block, inst), .int => try sema.zirInt(block, inst), @@ -3624,6 +3625,39 @@ fn validateSwitchNoRange( return sema.mod.failWithOwnedErrorMsg(&block.base, msg); } +fn zirHasDecl(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { + const tracy = trace(@src()); + defer tracy.end(); + + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data; + const src = inst_data.src(); + const lhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const rhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const container_type = try sema.resolveType(block, lhs_src, extra.lhs); + const decl_name = try sema.resolveConstString(block, rhs_src, extra.rhs); + + const maybe_scope = container_type.getContainerScope(); + if (maybe_scope == null) { + return sema.mod.fail( + &block.base, + src, + "expected container (struct, enum, or union), found '{}'", + .{container_type}, + ); + } + + const found = blk: { + for (maybe_scope.?.decls.items()) |kv| { + if (mem.eql(u8, mem.spanZ(kv.key.name), decl_name)) + break :blk true; + } + break :blk false; + }; + + return sema.mod.constBool(sema.arena, src, found); +} + fn zirImport(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); diff --git a/src/zir.zig b/src/zir.zig index ff402a9ddba721e2fcd34cfae3872bed6c31ebcc..40ad2b78449623dcef5541ba352304a929c00429 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -364,6 +364,9 @@ pub const Inst = struct { fn_type_cc, /// Same as `fn_type_cc` but the function is variadic. fn_type_cc_var_args, + /// Determines whether a container has a declaration matching name. + /// Uses the `pl_node` union field. Payload is `Bin`. + has_decl, /// `@import(operand)`. /// Uses the `un_node` field. import, @@ -751,6 +754,7 @@ pub const Inst = struct { .fn_type_var_args, .fn_type_cc, .fn_type_cc_var_args, + .has_decl, .int, .float, .float128, @@ -1681,6 +1685,7 @@ const Writer = struct { .cmp_gt, .cmp_neq, .div, + .has_decl, .mod_rem, .shl, .shr, -- 2.54.0 From ac14b52e85f857f7f70846d22ea18ea265acb91a Mon Sep 17 00:00:00 2001 From: Timon Kruiper Date: Mon, 5 Apr 2021 22:50:54 +0200 Subject: [PATCH 06/17] stage2: add support for start.zig This adds a simplified start2.zig that the current stage2 compiler is able to generate code for. --- lib/std/start2.zig | 58 +++++++++++++++++++++++++++++++++++++ src/Compilation.zig | 70 ++++++++++++++++++++++++++------------------- src/Module.zig | 9 ++++-- src/Package.zig | 25 ++++++++++++++++ src/Sema.zig | 5 +++- src/main.zig | 3 +- 6 files changed, 136 insertions(+), 34 deletions(-) create mode 100644 lib/std/start2.zig diff --git a/lib/std/start2.zig b/lib/std/start2.zig new file mode 100644 index 0000000000000000000000000000000000000000..22f3578d9886f4b8c68c62d24fedd47e5c306d14 --- /dev/null +++ b/lib/std/start2.zig @@ -0,0 +1,58 @@ +const root = @import("root"); +const builtin = @import("builtin"); + +comptime { + if (builtin.output_mode == 0) { // OutputMode.Exe + if (builtin.link_libc or builtin.object_format == 5) { // ObjectFormat.c + if (!@hasDecl(root, "main")) { + @export(otherMain, "main"); + } + } else { + if (!@hasDecl(root, "_start")) { + @export(otherStart, "_start"); + } + } + } +} + +// FIXME: Cannot call this function `main`, because `fully qualified names` +// have not been implemented yet. +fn otherMain() callconv(.C) c_int { + root.zigMain(); + return 0; +} + +// FIXME: Cannot call this function `_start`, because `fully qualified names` +// have not been implemented yet. +fn otherStart() callconv(.Naked) noreturn { + root.zigMain(); + otherExit(); +} + +// FIXME: Cannot call this function `exit`, because `fully qualified names` +// have not been implemented yet. +fn otherExit() noreturn { + if (builtin.arch == 31) { // x86_64 + asm volatile ("syscall" + : + : [number] "{rax}" (231), + [arg1] "{rdi}" (0) + : "rcx", "r11", "memory" + ); + } else if (builtin.arch == 0) { // arm + asm volatile ("svc #0" + : + : [number] "{r7}" (1), + [arg1] "{r0}" (0) + : "memory" + ); + } else if (builtin.arch == 2) { // aarch64 + asm volatile ("svc #0" + : + : [number] "{x8}" (93), + [arg1] "{x0}" (0) + : "memory", "cc" + ); + } else @compileError("not yet supported!"); + unreachable; +} diff --git a/src/Compilation.zig b/src/Compilation.zig index c0409fcb3bfd7da2afe5d28f0504759b80751fa8..2a3d9659b0ffa78331035333f825d71dfcf3478e 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -908,41 +908,45 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { }; const builtin_pkg = try Package.create(gpa, zig_cache_artifact_directory.path.?, "builtin2.zig"); + + const std_dir_path = try options.zig_lib_directory.join(gpa, &[_][]const u8{"std"}); + defer gpa.free(std_dir_path); + const start_pkg = try Package.create(gpa, std_dir_path, "start2.zig"); + try root_pkg.add(gpa, "builtin", builtin_pkg); try root_pkg.add(gpa, "root", root_pkg); + try start_pkg.add(gpa, "builtin", builtin_pkg); + try start_pkg.add(gpa, "root", root_pkg); + // TODO when we implement serialization and deserialization of incremental compilation metadata, // this is where we would load it. We have open a handle to the directory where // the output either already is, or will be. // However we currently do not have serialization of such metadata, so for now // we set up an empty Module that does the entire compilation fresh. - const root_scope = rs: { - if (mem.endsWith(u8, root_pkg.root_src_path, ".zig")) { - const root_scope = try gpa.create(Module.Scope.File); - const struct_ty = try Type.Tag.empty_struct.create( - gpa, - &root_scope.root_container, - ); - root_scope.* = .{ - // TODO this is duped so it can be freed in Container.deinit - .sub_file_path = try gpa.dupe(u8, root_pkg.root_src_path), - .source = .{ .unloaded = {} }, - .tree = undefined, - .status = .never_loaded, - .pkg = root_pkg, - .root_container = .{ - .file_scope = root_scope, - .decls = .{}, - .ty = struct_ty, - }, - }; - break :rs root_scope; - } else if (mem.endsWith(u8, root_pkg.root_src_path, ".zir")) { - return error.ZirFilesUnsupported; - } else { - unreachable; - } + if (mem.endsWith(u8, root_pkg.root_src_path, ".zir")) return error.ZirFilesUnsupported; + + const start_scope = ss: { + const start_scope = try gpa.create(Module.Scope.File); + const struct_ty = try Type.Tag.empty_struct.create( + gpa, + &start_scope.root_container, + ); + start_scope.* = .{ + // TODO this is duped so it can be freed in Container.deinit + .sub_file_path = try gpa.dupe(u8, start_pkg.root_src_path), + .source = .{ .unloaded = {} }, + .tree = undefined, + .status = .never_loaded, + .pkg = start_pkg, + .root_container = .{ + .file_scope = start_scope, + .decls = .{}, + .ty = struct_ty, + }, + }; + break :ss start_scope; }; const module = try arena.create(Module); @@ -951,7 +955,9 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .gpa = gpa, .comp = comp, .root_pkg = root_pkg, - .root_scope = root_scope, + .root_scope = null, + .start_pkg = start_pkg, + .start_scope = start_scope, .zig_cache_artifact_directory = zig_cache_artifact_directory, .emit_h = options.emit_h, .error_name_list = try std.ArrayListUnmanaged([]const u8).initCapacity(gpa, 1), @@ -1353,9 +1359,9 @@ pub fn update(self: *Compilation) !void { // TODO Detect which source files changed. // Until then we simulate a full cache miss. Source files could have been loaded // for any reason; to force a refresh we unload now. - module.unloadFile(module.root_scope); + module.unloadFile(module.start_scope); module.failed_root_src_file = null; - module.analyzeContainer(&module.root_scope.root_container) catch |err| switch (err) { + module.analyzeContainer(&module.start_scope.root_container) catch |err| switch (err) { error.AnalysisFail => { assert(self.totalErrorCount() != 0); }, @@ -1416,7 +1422,7 @@ pub fn update(self: *Compilation) !void { // to report error messages. Otherwise we unload all source files to save memory. if (self.totalErrorCount() == 0 and !self.keep_source_files_loaded) { if (self.bin_file.options.module) |module| { - module.root_scope.unload(self.gpa); + module.start_scope.unload(self.gpa); } } } @@ -2851,11 +2857,15 @@ fn generateBuiltin2ZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 { \\pub const link_libc = {}; \\pub const arch = {}; \\pub const os = {}; + \\pub const output_mode = {}; + \\pub const object_format = {}; \\ , .{ comp.bin_file.options.link_libc, @enumToInt(target.cpu.arch), @enumToInt(target.os.tag), + @enumToInt(comp.bin_file.options.output_mode), + @enumToInt(comp.bin_file.options.object_format), }); return buffer.toOwnedSlice(); diff --git a/src/Module.zig b/src/Module.zig index dc22b3fd3b5b6bcf0849e84a379ef4554794acb5..18dfc6bae0783999602803240dd06277cc2fb49d 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -35,8 +35,11 @@ comp: *Compilation, zig_cache_artifact_directory: Compilation.Directory, /// Pointer to externally managed resource. `null` if there is no zig file being compiled. root_pkg: *Package, +/// This is populated when `@import("root")` is analysed. +root_scope: ?*Scope.File, +start_pkg: *Package, /// Module owns this resource. -root_scope: *Scope.File, +start_scope: *Scope.File, /// It's rare for a decl to be exported, so we save memory by having a sparse map of /// Decl pointers to details about them being exported. /// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table. @@ -2341,7 +2344,9 @@ pub fn deinit(mod: *Module) void { mod.export_owners.deinit(gpa); mod.symbol_exports.deinit(gpa); - mod.root_scope.destroy(gpa); + + mod.start_scope.destroy(gpa); + mod.start_pkg.destroy(gpa); var it = mod.global_error_set.iterator(); while (it.next()) |entry| { diff --git a/src/Package.zig b/src/Package.zig index 03c9e9ea3d2b14f9bfa88430cbd492c3bc2dcb0f..d5960dcf9a2095740648af60c61d66597bd55c3d 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -15,6 +15,9 @@ root_src_path: []const u8, table: Table = .{}, parent: ?*Package = null, +// Used when freeing packages +seen: bool = false, + /// Allocate a Package. No references to the slices passed are kept. pub fn create( gpa: *Allocator, @@ -55,6 +58,14 @@ pub fn destroy(pkg: *Package, gpa: *Allocator) void { pkg.root_src_directory.handle.close(); } + // First we recurse into all the packages and remove packages from the tables + // once we have seen it before. We do this to make sure that that + // a package can only be found once in the whole tree. + if (!pkg.seen) { + pkg.seen = true; + pkg.markSeen(gpa); + } + { var it = pkg.table.iterator(); while (it.next()) |kv| { @@ -69,6 +80,20 @@ pub fn destroy(pkg: *Package, gpa: *Allocator) void { gpa.destroy(pkg); } +fn markSeen(pkg: *Package, gpa: *Allocator) void { + var it = pkg.table.iterator(); + while (it.next()) |kv| { + if (pkg != kv.value) { + if (kv.value.seen) { + pkg.table.removeAssertDiscard(kv.key); + } else { + kv.value.seen = true; + kv.value.markSeen(gpa); + } + } + } +} + pub fn add(pkg: *Package, gpa: *Allocator, name: []const u8, package: *Package) !void { try pkg.table.ensureCapacity(gpa, pkg.table.count() + 1); const name_dupe = try mem.dupe(gpa, u8, name); diff --git a/src/Sema.zig b/src/Sema.zig index b4b4798850fd6d12969f38c326b54d0c9705afc7..d4c2592446818ed737b38eb63214fa08ecd3cda7 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -4699,7 +4699,7 @@ fn namedFieldPtr( } // TODO this will give false positives for structs inside the root file - if (container_scope.file_scope == mod.root_scope) { + if (container_scope.file_scope == mod.root_scope.?) { return mod.fail( &block.base, src, @@ -5338,6 +5338,9 @@ fn analyzeImport(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, target_strin .ty = struct_ty, }, }; + if (mem.eql(u8, target_string, "root")) { + sema.mod.root_scope = file_scope; + } sema.mod.analyzeContainer(&file_scope.root_container) catch |err| switch (err) { error.AnalysisFail => { assert(sema.mod.comp.totalErrorCount() != 0); diff --git a/src/main.zig b/src/main.zig index 5fb74db61f21a1f22be847e6f3dcf3fd8f486761..0f03813a3637b64659436a734833f7fb3594d430 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1732,6 +1732,8 @@ fn buildOutputType( }, } + // This gets cleaned up, because root_pkg becomes part of the + // package table of the start_pkg. const root_pkg: ?*Package = if (root_src_file) |src_path| blk: { if (main_pkg_path) |p| { const rel_src_path = try fs.path.relative(gpa, p, src_path); @@ -1741,7 +1743,6 @@ fn buildOutputType( break :blk try Package.create(gpa, fs.path.dirname(src_path), fs.path.basename(src_path)); } } else null; - defer if (root_pkg) |p| p.destroy(gpa); // Transfer packages added with --pkg-begin/--pkg-end to the root package if (root_pkg) |pkg| { -- 2.54.0 From a483e38df62f73dc0cdadee6faf3e083094210d4 Mon Sep 17 00:00:00 2001 From: Timon Kruiper Date: Wed, 7 Apr 2021 13:27:36 +0200 Subject: [PATCH 07/17] stage2: fix bug where invalid ZIR was generated The following code caused an assertion to be hit: ``` pub fn main() void { var e: anyerror!c_int = error.Foo; const i = e catch 69; assert(69 - i == 0); } ``` --- src/Module.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index 18dfc6bae0783999602803240dd06277cc2fb49d..dab3319b2e701f699d92f55337037faad0b8e7be 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2863,8 +2863,9 @@ fn astgenAndSemaFn( _ = try AstGen.expr(&gen_scope, params_scope, .none, body_node); - if (gen_scope.instructions.items.len == 0 or - !astgen.instructions.items(.tag)[gen_scope.instructions.items.len - 1] + const inst_tags = astgen.instructions.items(.tag); + if (inst_tags.len == 0 or + !inst_tags[inst_tags.len - 1] .isNoReturn()) { // astgen uses result location semantics to coerce return operands. -- 2.54.0 From b9e508c410cd077d704a73418281f6d7839df241 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 8 Apr 2021 11:29:31 -0700 Subject: [PATCH 08/17] stage2: revert to only has_decl and export ZIR support Reverting most of the code from the previous commits in this branch. Will pull in the code with modifications bit by bit. --- src/AstGen.zig | 10 ++-- src/Compilation.zig | 110 ++++++++++++++------------------------------ src/Module.zig | 15 ++---- src/Package.zig | 29 +----------- src/Sema.zig | 59 ++++++++++-------------- src/main.zig | 3 +- src/zir.zig | 5 +- 7 files changed, 73 insertions(+), 158 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 20e52ea55933100b4601b4b3e5780a4b15dd38b5..e269c4ab4fd788288c2fdb862e9bd09ffdc95b9e 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -4149,12 +4149,14 @@ fn builtinCall( }, .@"export" => { - const target_fn = try expr(gz, scope, .none, params[0]); - // FIXME: When structs work in stage2, actually implement this correctly! - // Currently the name is always signifies Strong linkage. + // TODO: @export is supposed to be able to export things other than functions. + // Instead of `comptimeExpr` here we need `decl_ref`. + const fn_to_export = try comptimeExpr(gz, scope, .none, params[0]); + // TODO: the second parameter here is supposed to be + // `std.builtin.ExportOptions`, not a string. const export_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]); _ = try gz.addPlNode(.@"export", node, zir.Inst.Bin{ - .lhs = target_fn, + .lhs = fn_to_export, .rhs = export_name, }); return rvalue(gz, scope, rl, .void_value, node); diff --git a/src/Compilation.zig b/src/Compilation.zig index 2a3d9659b0ffa78331035333f825d71dfcf3478e..080d4bddaadfbf7e11feab45358496a75ff1fa21 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -510,11 +510,11 @@ pub const InitOptions = struct { fn addPackageTableToCacheHash( hash: *Cache.HashHelper, arena: *std.heap.ArenaAllocator, - package: *Package, + pkg_table: Package.Table, hash_type: union(enum) { path_bytes, files: *Cache.Manifest }, ) (error{OutOfMemory} || std.os.GetCwdError)!void { const allocator = &arena.allocator; - const pkg_table = package.table; + const packages = try allocator.alloc(Package.Table.Entry, pkg_table.count()); { // Copy over the hashmap entries to our slice @@ -547,8 +547,7 @@ fn addPackageTableToCacheHash( }, } // Recurse to handle the package's dependencies - if (package != pkg.value) - try addPackageTableToCacheHash(hash, arena, pkg.value, hash_type); + try addPackageTableToCacheHash(hash, arena, pkg.value.table, hash_type); } } @@ -886,7 +885,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { { var local_arena = std.heap.ArenaAllocator.init(gpa); defer local_arena.deinit(); - try addPackageTableToCacheHash(&hash, &local_arena, root_pkg, .path_bytes); + try addPackageTableToCacheHash(&hash, &local_arena, root_pkg.table, .path_bytes); } hash.add(valgrind); hash.add(single_threaded); @@ -907,46 +906,38 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { artifact_sub_dir, }; - const builtin_pkg = try Package.create(gpa, zig_cache_artifact_directory.path.?, "builtin2.zig"); - - const std_dir_path = try options.zig_lib_directory.join(gpa, &[_][]const u8{"std"}); - defer gpa.free(std_dir_path); - const start_pkg = try Package.create(gpa, std_dir_path, "start2.zig"); - - try root_pkg.add(gpa, "builtin", builtin_pkg); - try root_pkg.add(gpa, "root", root_pkg); - - try start_pkg.add(gpa, "builtin", builtin_pkg); - try start_pkg.add(gpa, "root", root_pkg); - // TODO when we implement serialization and deserialization of incremental compilation metadata, // this is where we would load it. We have open a handle to the directory where // the output either already is, or will be. // However we currently do not have serialization of such metadata, so for now // we set up an empty Module that does the entire compilation fresh. - if (mem.endsWith(u8, root_pkg.root_src_path, ".zir")) return error.ZirFilesUnsupported; - - const start_scope = ss: { - const start_scope = try gpa.create(Module.Scope.File); - const struct_ty = try Type.Tag.empty_struct.create( - gpa, - &start_scope.root_container, - ); - start_scope.* = .{ - // TODO this is duped so it can be freed in Container.deinit - .sub_file_path = try gpa.dupe(u8, start_pkg.root_src_path), - .source = .{ .unloaded = {} }, - .tree = undefined, - .status = .never_loaded, - .pkg = start_pkg, - .root_container = .{ - .file_scope = start_scope, - .decls = .{}, - .ty = struct_ty, - }, - }; - break :ss start_scope; + const root_scope = rs: { + if (mem.endsWith(u8, root_pkg.root_src_path, ".zig")) { + const root_scope = try gpa.create(Module.Scope.File); + const struct_ty = try Type.Tag.empty_struct.create( + gpa, + &root_scope.root_container, + ); + root_scope.* = .{ + // TODO this is duped so it can be freed in Container.deinit + .sub_file_path = try gpa.dupe(u8, root_pkg.root_src_path), + .source = .{ .unloaded = {} }, + .tree = undefined, + .status = .never_loaded, + .pkg = root_pkg, + .root_container = .{ + .file_scope = root_scope, + .decls = .{}, + .ty = struct_ty, + }, + }; + break :rs root_scope; + } else if (mem.endsWith(u8, root_pkg.root_src_path, ".zir")) { + return error.ZirFilesUnsupported; + } else { + unreachable; + } }; const module = try arena.create(Module); @@ -955,9 +946,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .gpa = gpa, .comp = comp, .root_pkg = root_pkg, - .root_scope = null, - .start_pkg = start_pkg, - .start_scope = start_scope, + .root_scope = root_scope, .zig_cache_artifact_directory = zig_cache_artifact_directory, .emit_h = options.emit_h, .error_name_list = try std.ArrayListUnmanaged([]const u8).initCapacity(gpa, 1), @@ -1359,9 +1348,9 @@ pub fn update(self: *Compilation) !void { // TODO Detect which source files changed. // Until then we simulate a full cache miss. Source files could have been loaded // for any reason; to force a refresh we unload now. - module.unloadFile(module.start_scope); + module.unloadFile(module.root_scope); module.failed_root_src_file = null; - module.analyzeContainer(&module.start_scope.root_container) catch |err| switch (err) { + module.analyzeContainer(&module.root_scope.root_container) catch |err| switch (err) { error.AnalysisFail => { assert(self.totalErrorCount() != 0); }, @@ -1422,7 +1411,7 @@ pub fn update(self: *Compilation) !void { // to report error messages. Otherwise we unload all source files to save memory. if (self.totalErrorCount() == 0 and !self.keep_source_files_loaded) { if (self.bin_file.options.module) |module| { - module.start_scope.unload(self.gpa); + module.root_scope.unload(self.gpa); } } } @@ -2833,11 +2822,6 @@ fn updateBuiltinZigFile(comp: *Compilation, mod: *Module) !void { const source = try comp.generateBuiltinZigSource(comp.gpa); defer comp.gpa.free(source); try mod.zig_cache_artifact_directory.handle.writeFile("builtin.zig", source); - - // FIXME: Remove builtin2.zig when stage2 can correctly generate code for builtin.zig! - const source2 = try comp.generateBuiltin2ZigSource(comp.gpa); - defer comp.gpa.free(source2); - try mod.zig_cache_artifact_directory.handle.writeFile("builtin2.zig", source2); } pub fn dump_argv(argv: []const []const u8) void { @@ -2847,30 +2831,6 @@ pub fn dump_argv(argv: []const []const u8) void { std.debug.print("{s}\n", .{argv[argv.len - 1]}); } -fn generateBuiltin2ZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 { - var buffer = std.ArrayList(u8).init(allocator); - defer buffer.deinit(); - - const target = comp.getTarget(); - - try buffer.writer().print( - \\pub const link_libc = {}; - \\pub const arch = {}; - \\pub const os = {}; - \\pub const output_mode = {}; - \\pub const object_format = {}; - \\ - , .{ - comp.bin_file.options.link_libc, - @enumToInt(target.cpu.arch), - @enumToInt(target.os.tag), - @enumToInt(comp.bin_file.options.output_mode), - @enumToInt(comp.bin_file.options.object_format), - }); - - return buffer.toOwnedSlice(); -} - pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 { const tracy = trace(@src()); defer tracy.end(); @@ -3215,7 +3175,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node { var local_arena = std.heap.ArenaAllocator.init(comp.gpa); defer local_arena.deinit(); - try addPackageTableToCacheHash(&man.hash, &local_arena, mod.root_pkg, .{ .files = &man }); + try addPackageTableToCacheHash(&man.hash, &local_arena, mod.root_pkg.table, .{ .files = &man }); } man.hash.add(comp.bin_file.options.valgrind); man.hash.add(comp.bin_file.options.single_threaded); diff --git a/src/Module.zig b/src/Module.zig index dab3319b2e701f699d92f55337037faad0b8e7be..933917d9484096d70710f2461170068da9b897fe 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -35,11 +35,8 @@ comp: *Compilation, zig_cache_artifact_directory: Compilation.Directory, /// Pointer to externally managed resource. `null` if there is no zig file being compiled. root_pkg: *Package, -/// This is populated when `@import("root")` is analysed. -root_scope: ?*Scope.File, -start_pkg: *Package, /// Module owns this resource. -start_scope: *Scope.File, +root_scope: *Scope.File, /// It's rare for a decl to be exported, so we save memory by having a sparse map of /// Decl pointers to details about them being exported. /// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table. @@ -2344,9 +2341,7 @@ pub fn deinit(mod: *Module) void { mod.export_owners.deinit(gpa); mod.symbol_exports.deinit(gpa); - - mod.start_scope.destroy(gpa); - mod.start_pkg.destroy(gpa); + mod.root_scope.destroy(gpa); var it = mod.global_error_set.iterator(); while (it.next()) |entry| { @@ -2518,7 +2513,6 @@ fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool { const block_expr = node_datas[decl_node].lhs; _ = try AstGen.comptimeExpr(&gen_scope, &gen_scope.base, .none, block_expr); - _ = try gen_scope.addBreak(.break_inline, gen_scope.break_block, .void_value); const code = try gen_scope.finish(); if (std.builtin.mode == .Debug and mod.comp.verbose_ir) { @@ -2863,9 +2857,8 @@ fn astgenAndSemaFn( _ = try AstGen.expr(&gen_scope, params_scope, .none, body_node); - const inst_tags = astgen.instructions.items(.tag); - if (inst_tags.len == 0 or - !inst_tags[inst_tags.len - 1] + if (gen_scope.instructions.items.len == 0 or + !astgen.instructions.items(.tag)[gen_scope.instructions.items.len - 1] .isNoReturn()) { // astgen uses result location semantics to coerce return operands. diff --git a/src/Package.zig b/src/Package.zig index d5960dcf9a2095740648af60c61d66597bd55c3d..33ff4766cae383518acdfc6a70058b5923b89984 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -15,9 +15,6 @@ root_src_path: []const u8, table: Table = .{}, parent: ?*Package = null, -// Used when freeing packages -seen: bool = false, - /// Allocate a Package. No references to the slices passed are kept. pub fn create( gpa: *Allocator, @@ -58,20 +55,10 @@ pub fn destroy(pkg: *Package, gpa: *Allocator) void { pkg.root_src_directory.handle.close(); } - // First we recurse into all the packages and remove packages from the tables - // once we have seen it before. We do this to make sure that that - // a package can only be found once in the whole tree. - if (!pkg.seen) { - pkg.seen = true; - pkg.markSeen(gpa); - } - { var it = pkg.table.iterator(); while (it.next()) |kv| { - if (pkg != kv.value) { - kv.value.destroy(gpa); - } + kv.value.destroy(gpa); gpa.free(kv.key); } } @@ -80,20 +67,6 @@ pub fn destroy(pkg: *Package, gpa: *Allocator) void { gpa.destroy(pkg); } -fn markSeen(pkg: *Package, gpa: *Allocator) void { - var it = pkg.table.iterator(); - while (it.next()) |kv| { - if (pkg != kv.value) { - if (kv.value.seen) { - pkg.table.removeAssertDiscard(kv.key); - } else { - kv.value.seen = true; - kv.value.markSeen(gpa); - } - } - } -} - pub fn add(pkg: *Package, gpa: *Allocator, name: []const u8, package: *Package) !void { try pkg.table.ensureCapacity(gpa, pkg.table.count() + 1); const name_dupe = try mem.dupe(gpa, u8, name); diff --git a/src/Sema.zig b/src/Sema.zig index d4c2592446818ed737b38eb63214fa08ecd3cda7..51d350ea7cbb0f8b2ea157586b599a4c9bbad7b3 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1345,21 +1345,18 @@ fn zirExport(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError! const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data; const src = inst_data.src(); + const lhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const rhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; - const target_fn = try sema.resolveInst(extra.lhs); - const target_fn_val = try sema.resolveConstValue( - block, - .{ .node_offset_builtin_call_arg0 = inst_data.src_node }, - target_fn, - ); + // TODO (see corresponding TODO in AstGen) this is supposed to be a `decl_ref` + // instruction, which could reference any decl, which is then supposed to get + // exported, regardless of whether or not it is a function. + const target_fn = try sema.resolveInstConst(block, lhs_src, extra.lhs); + // TODO (see corresponding TODO in AstGen) this is supposed to be + // `std.builtin.ExportOptions`, not a string. + const export_name = try sema.resolveConstString(block, rhs_src, extra.rhs); - const export_name = try sema.resolveConstString( - block, - .{ .node_offset_builtin_call_arg1 = inst_data.src_node }, - extra.rhs, - ); - - const actual_fn = target_fn_val.castTag(.function).?.data; + const actual_fn = target_fn.val.castTag(.function).?.data; try sema.mod.analyzeExport(&block.base, src, export_name, actual_fn.owner_decl); } @@ -3636,26 +3633,21 @@ fn zirHasDecl(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError const rhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const container_type = try sema.resolveType(block, lhs_src, extra.lhs); const decl_name = try sema.resolveConstString(block, rhs_src, extra.rhs); + const mod = sema.mod; + const arena = sema.arena; - const maybe_scope = container_type.getContainerScope(); - if (maybe_scope == null) { - return sema.mod.fail( - &block.base, - src, - "expected container (struct, enum, or union), found '{}'", - .{container_type}, - ); + const container_scope = container_type.getContainerScope() orelse return mod.fail( + &block.base, + lhs_src, + "expected struct, enum, union, or opaque, found '{}'", + .{container_type}, + ); + if (mod.lookupDeclName(&container_scope.base, decl_name)) |decl| { + // TODO if !decl.is_pub and inDifferentFiles() return false + return mod.constBool(arena, src, true); + } else { + return mod.constBool(arena, src, false); } - - const found = blk: { - for (maybe_scope.?.decls.items()) |kv| { - if (mem.eql(u8, mem.spanZ(kv.key.name), decl_name)) - break :blk true; - } - break :blk false; - }; - - return sema.mod.constBool(sema.arena, src, found); } fn zirImport(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { @@ -4699,7 +4691,7 @@ fn namedFieldPtr( } // TODO this will give false positives for structs inside the root file - if (container_scope.file_scope == mod.root_scope.?) { + if (container_scope.file_scope == mod.root_scope) { return mod.fail( &block.base, src, @@ -5338,9 +5330,6 @@ fn analyzeImport(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, target_strin .ty = struct_ty, }, }; - if (mem.eql(u8, target_string, "root")) { - sema.mod.root_scope = file_scope; - } sema.mod.analyzeContainer(&file_scope.root_container) catch |err| switch (err) { error.AnalysisFail => { assert(sema.mod.comp.totalErrorCount() != 0); diff --git a/src/main.zig b/src/main.zig index 0f03813a3637b64659436a734833f7fb3594d430..5fb74db61f21a1f22be847e6f3dcf3fd8f486761 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1732,8 +1732,6 @@ fn buildOutputType( }, } - // This gets cleaned up, because root_pkg becomes part of the - // package table of the start_pkg. const root_pkg: ?*Package = if (root_src_file) |src_path| blk: { if (main_pkg_path) |p| { const rel_src_path = try fs.path.relative(gpa, p, src_path); @@ -1743,6 +1741,7 @@ fn buildOutputType( break :blk try Package.create(gpa, fs.path.dirname(src_path), fs.path.basename(src_path)); } } else null; + defer if (root_pkg) |p| p.destroy(gpa); // Transfer packages added with --pkg-begin/--pkg-end to the root package if (root_pkg) |pkg| { diff --git a/src/zir.zig b/src/zir.zig index 40ad2b78449623dcef5541ba352304a929c00429..807e25e6b84b8f7f77c35d9fe459427613fb7eda 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -328,8 +328,7 @@ pub const Inst = struct { error_union_type, /// `error.Foo` syntax. Uses the `str_tok` field of the Data union. error_value, - /// Exports a function with a specified name. This can be used at comptime - /// to export a function conditionally. + /// Implements the `@export` builtin function. /// Uses the `pl_node` union field. Payload is `Bin`. @"export", /// Given a pointer to a struct or object that contains virtual fields, returns a pointer @@ -364,7 +363,7 @@ pub const Inst = struct { fn_type_cc, /// Same as `fn_type_cc` but the function is variadic. fn_type_cc_var_args, - /// Determines whether a container has a declaration matching name. + /// Implements the `@hasDecl` builtin. /// Uses the `pl_node` union field. Payload is `Bin`. has_decl, /// `@import(operand)`. -- 2.54.0 From 482b995a4963e045860c7fb1a4e11c48cf4de880 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 8 Apr 2021 19:05:05 -0700 Subject: [PATCH 09/17] stage2: blaze the trail for std lib integration This branch adds "builtin" and "std" to the import table when using the self-hosted backend. "builtin" gains one additional item: ``` pub const zig_is_stage2 = true; // false when using stage1 backend ``` This allows the std lib to do conditional compilation based on detecting which backend is being used. This will be removed from builtin as soon as self-hosted catches up to feature parity with stage1. Keep a sharp eye out - people are going to be tempted to abuse this. The general rule of thumb is do not use `builtin.zig_is_stage2`. However this commit breaks the rule so that we can gain limited start.zig support as we incrementally improve the self-hosted compiler. This commit also implements `fullyQualifiedNameHash` and related functionality, which effectively puts all Decls in their proper namespaces. `fullyQualifiedName` is not yet implemented. Stop printing "todo" log messages for test decls unless we are in test mode. Add "previous definition here" error notes for Decl name collisions. This commit does not bring us yet to a newly passing test case. Here's what I'm working towards: ```zig const std = @import("std"); export fn main() c_int { const a = std.fs.base64_alphabet[0]; return a - 'A'; } ``` Current output: ``` $ ./zig-cache/bin/zig build-exe test.zig test.zig:3:1: error: TODO implement more analyze elemptr zig-cache/lib/zig/std/start.zig:38:46: error: TODO implement structInitExpr ty ``` So the next steps are clear: * Sema: improve elemptr * AstGen: implement structInitExpr --- lib/std/start.zig | 116 +++++++++++++++++++++++++++++++---------- lib/std/start2.zig | 58 --------------------- lib/std/std.zig | 2 +- lib/std/zig.zig | 19 ++++--- src/Compilation.zig | 89 ++++++++++++++++++++----------- src/Module.zig | 17 +++--- src/Package.zig | 76 ++++++++++++++++++++++++--- src/Sema.zig | 19 ++++--- src/main.zig | 28 +++++++--- src/stage1/codegen.cpp | 1 + 10 files changed, 274 insertions(+), 151 deletions(-) delete mode 100644 lib/std/start2.zig diff --git a/lib/std/start.zig b/lib/std/start.zig index 0fb96c768f60e2a1f346ae64bc89de0e454b9bd8..5454cc58c6ebd545f6b30b54b6828793b042b37e 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -7,7 +7,7 @@ const root = @import("root"); const std = @import("std.zig"); -const builtin = std.builtin; +const builtin = @import("builtin"); const assert = std.debug.assert; const uefi = std.os.uefi; const tlcsprng = @import("crypto/tlcsprng.zig"); @@ -17,39 +17,101 @@ var argc_argv_ptr: [*]usize = undefined; const start_sym_name = if (builtin.arch.isMIPS()) "__start" else "_start"; comptime { - if (builtin.output_mode == .Lib and builtin.link_mode == .Dynamic) { - if (builtin.os.tag == .windows and !@hasDecl(root, "_DllMainCRTStartup")) { - @export(_DllMainCRTStartup, .{ .name = "_DllMainCRTStartup" }); + // The self-hosted compiler is not fully capable of handling all of this start.zig file. + // Until then, we have simplified logic here for self-hosted. TODO remove this once + // self-hosted is capable enough to handle all of the real start.zig logic. + if (builtin.zig_is_stage2) { + if (builtin.output_mode == .Exe) { + if (builtin.link_libc or builtin.object_format == .c) { + if (!@hasDecl(root, "main")) { + @export(main2, "main"); + } + } else { + if (!@hasDecl(root, "_start")) { + @export(_start2, "_start"); + } + } } - } else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) { - if (builtin.link_libc and @hasDecl(root, "main")) { - if (@typeInfo(@TypeOf(root.main)).Fn.calling_convention != .C) { - @export(main, .{ .name = "main", .linkage = .Weak }); + } else { + if (builtin.output_mode == .Lib and builtin.link_mode == .Dynamic) { + if (builtin.os.tag == .windows and !@hasDecl(root, "_DllMainCRTStartup")) { + @export(_DllMainCRTStartup, .{ .name = "_DllMainCRTStartup" }); } - } else if (builtin.os.tag == .windows) { - if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and - !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup")) - { - @export(WinStartup, .{ .name = "wWinMainCRTStartup" }); - } else if (@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and - !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup")) - { - @compileError("WinMain not supported; declare wWinMain or main instead"); - } else if (@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup") and - !@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup")) - { - @export(wWinMainCRTStartup, .{ .name = "wWinMainCRTStartup" }); + } else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) { + if (builtin.link_libc and @hasDecl(root, "main")) { + if (@typeInfo(@TypeOf(root.main)).Fn.calling_convention != .C) { + @export(main, .{ .name = "main", .linkage = .Weak }); + } + } else if (builtin.os.tag == .windows) { + if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and + !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup")) + { + @export(WinStartup, .{ .name = "wWinMainCRTStartup" }); + } else if (@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and + !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup")) + { + @compileError("WinMain not supported; declare wWinMain or main instead"); + } else if (@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup") and + !@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup")) + { + @export(wWinMainCRTStartup, .{ .name = "wWinMainCRTStartup" }); + } + } else if (builtin.os.tag == .uefi) { + if (!@hasDecl(root, "EfiMain")) @export(EfiMain, .{ .name = "EfiMain" }); + } else if (builtin.arch.isWasm() and builtin.os.tag == .freestanding) { + if (!@hasDecl(root, start_sym_name)) @export(wasm_freestanding_start, .{ .name = start_sym_name }); + } else if (builtin.os.tag != .other and builtin.os.tag != .freestanding) { + if (!@hasDecl(root, start_sym_name)) @export(_start, .{ .name = start_sym_name }); } - } else if (builtin.os.tag == .uefi) { - if (!@hasDecl(root, "EfiMain")) @export(EfiMain, .{ .name = "EfiMain" }); - } else if (builtin.arch.isWasm() and builtin.os.tag == .freestanding) { - if (!@hasDecl(root, start_sym_name)) @export(wasm_freestanding_start, .{ .name = start_sym_name }); - } else if (builtin.os.tag != .other and builtin.os.tag != .freestanding) { - if (!@hasDecl(root, start_sym_name)) @export(_start, .{ .name = start_sym_name }); } } } +// Simplified start code for stage2 until it supports more language features /// + +fn main2() callconv(.C) c_int { + root.main(); + return 0; +} + +fn _start2() callconv(.Naked) noreturn { + root.main(); + exit2(0); +} + +fn exit2(code: u8) noreturn { + switch (builtin.arch) { + .x86_64 => { + asm volatile ("syscall" + : + : [number] "{rax}" (231), + [arg1] "{rdi}" (code) + : "rcx", "r11", "memory" + ); + }, + .arm => { + asm volatile ("svc #0" + : + : [number] "{r7}" (1), + [arg1] "{r0}" (code) + : "memory" + ); + }, + .aarch64 => { + asm volatile ("svc #0" + : + : [number] "{x8}" (93), + [arg1] "{x0}" (code) + : "memory", "cc" + ); + }, + else => @compileError("TODO"), + } + unreachable; +} + +//////////////////////////////////////////////////////////////////////////////// + fn _DllMainCRTStartup( hinstDLL: std.os.windows.HINSTANCE, fdwReason: std.os.windows.DWORD, diff --git a/lib/std/start2.zig b/lib/std/start2.zig deleted file mode 100644 index 22f3578d9886f4b8c68c62d24fedd47e5c306d14..0000000000000000000000000000000000000000 --- a/lib/std/start2.zig +++ /dev/null @@ -1,58 +0,0 @@ -const root = @import("root"); -const builtin = @import("builtin"); - -comptime { - if (builtin.output_mode == 0) { // OutputMode.Exe - if (builtin.link_libc or builtin.object_format == 5) { // ObjectFormat.c - if (!@hasDecl(root, "main")) { - @export(otherMain, "main"); - } - } else { - if (!@hasDecl(root, "_start")) { - @export(otherStart, "_start"); - } - } - } -} - -// FIXME: Cannot call this function `main`, because `fully qualified names` -// have not been implemented yet. -fn otherMain() callconv(.C) c_int { - root.zigMain(); - return 0; -} - -// FIXME: Cannot call this function `_start`, because `fully qualified names` -// have not been implemented yet. -fn otherStart() callconv(.Naked) noreturn { - root.zigMain(); - otherExit(); -} - -// FIXME: Cannot call this function `exit`, because `fully qualified names` -// have not been implemented yet. -fn otherExit() noreturn { - if (builtin.arch == 31) { // x86_64 - asm volatile ("syscall" - : - : [number] "{rax}" (231), - [arg1] "{rdi}" (0) - : "rcx", "r11", "memory" - ); - } else if (builtin.arch == 0) { // arm - asm volatile ("svc #0" - : - : [number] "{r7}" (1), - [arg1] "{r0}" (0) - : "memory" - ); - } else if (builtin.arch == 2) { // aarch64 - asm volatile ("svc #0" - : - : [number] "{x8}" (93), - [arg1] "{x0}" (0) - : "memory", "cc" - ); - } else @compileError("not yet supported!"); - unreachable; -} diff --git a/lib/std/std.zig b/lib/std/std.zig index f8bc477068c945df3c92069a065642010406251a..5f10def72e88d3e98408cada498474b28a6d7f68 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -92,7 +92,7 @@ pub const zig = @import("zig.zig"); pub const start = @import("start.zig"); // This forces the start.zig file to be imported, and the comptime logic inside that -// file decides whether to export any appropriate start symbols. +// file decides whether to export any appropriate start symbols, and call main. comptime { _ = start; } diff --git a/lib/std/zig.zig b/lib/std/zig.zig index cff07a2bd2e5ef21cc4e27b1efbe180a10d6d1cd..714235523ecf4ec57f5170515ebc9b7d4b9275a3 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -18,16 +18,19 @@ pub const CrossTarget = @import("zig/cross_target.zig").CrossTarget; pub const SrcHash = [16]u8; -/// If the source is small enough, it is used directly as the hash. -/// If it is long, blake3 hash is computed. pub fn hashSrc(src: []const u8) SrcHash { var out: SrcHash = undefined; - if (src.len <= @typeInfo(SrcHash).Array.len) { - std.mem.copy(u8, &out, src); - std.mem.set(u8, out[src.len..], 0); - } else { - std.crypto.hash.Blake3.hash(src, &out, .{}); - } + std.crypto.hash.Blake3.hash(src, &out, .{}); + return out; +} + +pub fn hashName(parent_hash: SrcHash, sep: []const u8, name: []const u8) SrcHash { + var out: SrcHash = undefined; + var hasher = std.crypto.hash.Blake3.init(.{}); + hasher.update(&parent_hash); + hasher.update(sep); + hasher.update(name); + hasher.final(&out); return out; } diff --git a/src/Compilation.zig b/src/Compilation.zig index 080d4bddaadfbf7e11feab45358496a75ff1fa21..73fafa0976e7af72f5c0d472b4e872fe85c7963a 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -906,38 +906,61 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { artifact_sub_dir, }; - // TODO when we implement serialization and deserialization of incremental compilation metadata, - // this is where we would load it. We have open a handle to the directory where - // the output either already is, or will be. + // If we rely on stage1, we must not redundantly add these packages. + const use_stage1 = build_options.is_stage1 and use_llvm; + if (!use_stage1) { + const builtin_pkg = try Package.createWithDir( + gpa, + zig_cache_artifact_directory, + null, + "builtin.zig", + ); + errdefer builtin_pkg.destroy(gpa); + + const std_pkg = try Package.createWithDir( + gpa, + options.zig_lib_directory, + "std", + "std.zig", + ); + errdefer std_pkg.destroy(gpa); + + try root_pkg.addAndAdopt(gpa, "builtin", builtin_pkg); + try root_pkg.add(gpa, "root", root_pkg); + try root_pkg.addAndAdopt(gpa, "std", std_pkg); + + try std_pkg.add(gpa, "builtin", builtin_pkg); + try std_pkg.add(gpa, "root", root_pkg); + } + + // TODO when we implement serialization and deserialization of incremental + // compilation metadata, this is where we would load it. We have open a handle + // to the directory where the output either already is, or will be. // However we currently do not have serialization of such metadata, so for now // we set up an empty Module that does the entire compilation fresh. - const root_scope = rs: { - if (mem.endsWith(u8, root_pkg.root_src_path, ".zig")) { - const root_scope = try gpa.create(Module.Scope.File); - const struct_ty = try Type.Tag.empty_struct.create( - gpa, - &root_scope.root_container, - ); - root_scope.* = .{ - // TODO this is duped so it can be freed in Container.deinit - .sub_file_path = try gpa.dupe(u8, root_pkg.root_src_path), - .source = .{ .unloaded = {} }, - .tree = undefined, - .status = .never_loaded, - .pkg = root_pkg, - .root_container = .{ - .file_scope = root_scope, - .decls = .{}, - .ty = struct_ty, - }, - }; - break :rs root_scope; - } else if (mem.endsWith(u8, root_pkg.root_src_path, ".zir")) { - return error.ZirFilesUnsupported; - } else { - unreachable; - } + // TODO remove CLI support for .zir files and then we can remove this error + // handling and assertion. + if (mem.endsWith(u8, root_pkg.root_src_path, ".zir")) return error.ZirFilesUnsupported; + assert(mem.endsWith(u8, root_pkg.root_src_path, ".zig")); + + const root_scope = try gpa.create(Module.Scope.File); + errdefer gpa.destroy(root_scope); + + const struct_ty = try Type.Tag.empty_struct.create(gpa, &root_scope.root_container); + root_scope.* = .{ + // TODO this is duped so it can be freed in Container.deinit + .sub_file_path = try gpa.dupe(u8, root_pkg.root_src_path), + .source = .{ .unloaded = {} }, + .tree = undefined, + .status = .never_loaded, + .pkg = root_pkg, + .root_container = .{ + .file_scope = root_scope, + .decls = .{}, + .ty = struct_ty, + .parent_name_hash = root_pkg.namespace_hash, + }, }; const module = try arena.create(Module); @@ -1339,7 +1362,8 @@ pub fn update(self: *Compilation) !void { self.c_object_work_queue.writeItemAssumeCapacity(entry.key); } - const use_stage1 = build_options.omit_stage2 or build_options.is_stage1 and self.bin_file.options.use_llvm; + const use_stage1 = build_options.omit_stage2 or + (build_options.is_stage1 and self.bin_file.options.use_llvm); if (!use_stage1) { if (self.bin_file.options.module) |module| { module.compile_log_text.shrinkAndFree(module.gpa, 0); @@ -2840,6 +2864,8 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 const target = comp.getTarget(); const generic_arch_name = target.cpu.arch.genericName(); + const use_stage1 = build_options.omit_stage2 or + (build_options.is_stage1 and comp.bin_file.options.use_llvm); @setEvalBranchQuota(4000); try buffer.writer().print( @@ -2852,6 +2878,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 \\/// Zig version. When writing code that supports multiple versions of Zig, prefer \\/// feature detection (i.e. with `@hasDecl` or `@hasField`) over version checks. \\pub const zig_version = try @import("std").SemanticVersion.parse("{s}"); + \\pub const zig_is_stage2 = {}; \\ \\pub const output_mode = OutputMode.{}; \\pub const link_mode = LinkMode.{}; @@ -2865,6 +2892,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 \\ , .{ build_options.version, + !use_stage1, std.zig.fmtId(@tagName(comp.bin_file.options.output_mode)), std.zig.fmtId(@tagName(comp.bin_file.options.link_mode)), comp.bin_file.options.is_test, @@ -3074,6 +3102,7 @@ fn buildOutputFromZig( .handle = special_dir, }, .root_src_path = src_basename, + .namespace_hash = Package.root_namespace_hash, }; const root_name = src_basename[0 .. src_basename.len - std.fs.path.extension(src_basename).len]; const target = comp.getTarget(); diff --git a/src/Module.zig b/src/Module.zig index 933917d9484096d70710f2461170068da9b897fe..5883109852aed1219ba2a5aec59da9f5affaad4a 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -678,6 +678,7 @@ pub const Scope = struct { base: Scope = Scope{ .tag = base_tag }, file_scope: *Scope.File, + parent_name_hash: NameHash, /// Direct children of the file. decls: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{}, @@ -696,8 +697,7 @@ pub const Scope = struct { } pub fn fullyQualifiedNameHash(cont: *Container, name: []const u8) NameHash { - // TODO container scope qualified names. - return std.zig.hashSrc(name); + return std.zig.hashName(cont.parent_name_hash, ".", name); } pub fn renderFullyQualifiedName(cont: Container, name: []const u8, writer: anytype) !void { @@ -2513,6 +2513,7 @@ fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool { const block_expr = node_datas[decl_node].lhs; _ = try AstGen.comptimeExpr(&gen_scope, &gen_scope.base, .none, block_expr); + _ = try gen_scope.addBreak(.break_inline, 0, .void_value); const code = try gen_scope.finish(); if (std.builtin.mode == .Debug and mod.comp.verbose_ir) { @@ -3468,7 +3469,9 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { ), .test_decl => { - log.err("TODO: analyze test decl", .{}); + if (mod.comp.bin_file.options.is_test) { + log.err("TODO: analyze test decl", .{}); + } }, .@"usingnamespace" => { log.err("TODO: analyze usingnamespace decl", .{}); @@ -3532,6 +3535,7 @@ fn semaContainerFn( .lazy = .{ .token_abs = name_tok }, }, "redefinition of '{s}'", .{decl.name}); errdefer msg.destroy(mod.gpa); + try mod.errNoteNonLazy(decl.srcLoc(), msg, "previous definition here", .{}); try mod.failed_decls.putNoClobber(mod.gpa, decl, msg); } else { if (!srcHashEql(decl.contents_hash, contents_hash)) { @@ -3589,12 +3593,13 @@ fn semaContainerVar( decl.src_index = decl_i; if (deleted_decls.swapRemove(decl) == null) { decl.analysis = .sema_failure; - const err_msg = try ErrorMsg.create(mod.gpa, .{ + const msg = try ErrorMsg.create(mod.gpa, .{ .container = .{ .file_scope = container_scope.file_scope }, .lazy = .{ .token_abs = name_token }, }, "redefinition of '{s}'", .{decl.name}); - errdefer err_msg.destroy(mod.gpa); - try mod.failed_decls.putNoClobber(mod.gpa, decl, err_msg); + errdefer msg.destroy(mod.gpa); + try mod.errNoteNonLazy(decl.srcLoc(), msg, "previous definition here", .{}); + try mod.failed_decls.putNoClobber(mod.gpa, decl, msg); } else if (!srcHashEql(decl.contents_hash, contents_hash)) { try outdated_decls.put(decl, {}); decl.contents_hash = contents_hash; diff --git a/src/Package.zig b/src/Package.zig index 33ff4766cae383518acdfc6a70058b5923b89984..25a9f55d634884e1cdfae313517bc19a9e863b5b 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -4,18 +4,29 @@ const std = @import("std"); const fs = std.fs; const mem = std.mem; const Allocator = mem.Allocator; +const assert = std.debug.assert; const Compilation = @import("Compilation.zig"); +const Module = @import("Module.zig"); pub const Table = std.StringHashMapUnmanaged(*Package); +pub const root_namespace_hash: Module.Scope.NameHash = .{ + 0, 0, 6, 6, 6, 0, 0, 0, + 6, 9, 0, 0, 0, 4, 2, 0, +}; + root_src_directory: Compilation.Directory, /// Relative to `root_src_directory`. May contain path separators. root_src_path: []const u8, table: Table = .{}, parent: ?*Package = null, +namespace_hash: Module.Scope.NameHash, +/// Whether to free `root_src_directory` on `destroy`. +root_src_directory_owned: bool = false, /// Allocate a Package. No references to the slices passed are kept. +/// Don't forget to set `namespace_hash` later. pub fn create( gpa: *Allocator, /// Null indicates the current working directory @@ -38,27 +49,69 @@ pub fn create( .handle = if (owned_dir_path) |p| try fs.cwd().openDir(p, .{}) else fs.cwd(), }, .root_src_path = owned_src_path, + .root_src_directory_owned = true, + .namespace_hash = undefined, }; return ptr; } -/// Free all memory associated with this package and recursively call destroy -/// on all packages in its table +pub fn createWithDir( + gpa: *Allocator, + directory: Compilation.Directory, + /// Relative to `directory`. If null, means `directory` is the root src dir + /// and is owned externally. + root_src_dir_path: ?[]const u8, + /// Relative to root_src_dir_path + root_src_path: []const u8, +) !*Package { + const ptr = try gpa.create(Package); + errdefer gpa.destroy(ptr); + + const owned_src_path = try gpa.dupe(u8, root_src_path); + errdefer gpa.free(owned_src_path); + + if (root_src_dir_path) |p| { + const owned_dir_path = try directory.join(gpa, &[1][]const u8{p}); + errdefer gpa.free(owned_dir_path); + + ptr.* = .{ + .root_src_directory = .{ + .path = owned_dir_path, + .handle = try directory.handle.openDir(p, .{}), + }, + .root_src_directory_owned = true, + .root_src_path = owned_src_path, + .namespace_hash = undefined, + }; + } else { + ptr.* = .{ + .root_src_directory = directory, + .root_src_directory_owned = false, + .root_src_path = owned_src_path, + .namespace_hash = undefined, + }; + } + return ptr; +} + +/// Free all memory associated with this package. It does not destroy any packages +/// inside its table; the caller is responsible for calling destroy() on them. pub fn destroy(pkg: *Package, gpa: *Allocator) void { gpa.free(pkg.root_src_path); - // If root_src_directory.path is null then the handle is the cwd() - // which shouldn't be closed. - if (pkg.root_src_directory.path) |p| { - gpa.free(p); - pkg.root_src_directory.handle.close(); + if (pkg.root_src_directory_owned) { + // If root_src_directory.path is null then the handle is the cwd() + // which shouldn't be closed. + if (pkg.root_src_directory.path) |p| { + gpa.free(p); + pkg.root_src_directory.handle.close(); + } } { var it = pkg.table.iterator(); while (it.next()) |kv| { - kv.value.destroy(gpa); gpa.free(kv.key); } } @@ -72,3 +125,10 @@ pub fn add(pkg: *Package, gpa: *Allocator, name: []const u8, package: *Package) const name_dupe = try mem.dupe(gpa, u8, name); pkg.table.putAssumeCapacityNoClobber(name_dupe, package); } + +pub fn addAndAdopt(parent: *Package, gpa: *Allocator, name: []const u8, child: *Package) !void { + assert(child.parent == null); // make up your mind, who is the parent?? + child.parent = parent; + child.namespace_hash = std.zig.hashName(parent.namespace_hash, ":", name); + return parent.add(gpa, name, child); +} diff --git a/src/Sema.zig b/src/Sema.zig index 51d350ea7cbb0f8b2ea157586b599a4c9bbad7b3..161c4b05392128e5ea22b3ef39e5c60260349b83 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -598,6 +598,10 @@ fn zirStructDecl( const struct_obj = try new_decl_arena.allocator.create(Module.Struct); const struct_ty = try Type.Tag.@"struct".create(&new_decl_arena.allocator, struct_obj); const struct_val = try Value.Tag.ty.create(&new_decl_arena.allocator, struct_ty); + const new_decl = try sema.mod.createAnonymousDecl(&block.base, &new_decl_arena, .{ + .ty = Type.initTag(.type), + .val = struct_val, + }); struct_obj.* = .{ .owner_decl = sema.owner_decl, .fields = fields_map, @@ -605,12 +609,9 @@ fn zirStructDecl( .container = .{ .ty = struct_ty, .file_scope = block.getFileScope(), + .parent_name_hash = new_decl.fullyQualifiedNameHash(), }, }; - const new_decl = try sema.mod.createAnonymousDecl(&block.base, &new_decl_arena, .{ - .ty = Type.initTag(.type), - .val = struct_val, - }); return sema.analyzeDeclVal(block, src, new_decl); } @@ -5298,9 +5299,9 @@ fn analyzeImport(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, target_strin try std.fs.path.resolve(sema.gpa, &[_][]const u8{ cur_pkg_dir_path, target_string }); errdefer sema.gpa.free(resolved_path); - if (sema.mod.import_table.get(resolved_path)) |some| { + if (sema.mod.import_table.get(resolved_path)) |cached_import| { sema.gpa.free(resolved_path); - return some; + return cached_import; } if (found_pkg == null) { @@ -5318,6 +5319,11 @@ fn analyzeImport(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, target_strin const struct_ty = try Type.Tag.empty_struct.create(sema.gpa, &file_scope.root_container); errdefer sema.gpa.destroy(struct_ty.castTag(.empty_struct).?); + const container_name_hash: Scope.NameHash = if (found_pkg) |pkg| + pkg.namespace_hash + else + std.zig.hashName(cur_pkg.namespace_hash, "/", resolved_path); + file_scope.* = .{ .sub_file_path = resolved_path, .source = .{ .unloaded = {} }, @@ -5328,6 +5334,7 @@ fn analyzeImport(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, target_strin .file_scope = file_scope, .decls = .{}, .ty = struct_ty, + .parent_name_hash = container_name_hash, }, }; sema.mod.analyzeContainer(&file_scope.root_container) catch |err| switch (err) { diff --git a/src/main.zig b/src/main.zig index 5fb74db61f21a1f22be847e6f3dcf3fd8f486761..6136c964ceb29cd5528b1c9318c06f4c884b4a99 100644 --- a/src/main.zig +++ b/src/main.zig @@ -599,15 +599,15 @@ fn buildOutputType( var test_exec_args = std.ArrayList(?[]const u8).init(gpa); defer test_exec_args.deinit(); - const pkg_tree_root = try gpa.create(Package); // This package only exists to clean up the code parsing --pkg-begin and // --pkg-end flags. Use dummy values that are safe for the destroy call. - pkg_tree_root.* = .{ + var pkg_tree_root: Package = .{ .root_src_directory = .{ .path = null, .handle = fs.cwd() }, .root_src_path = &[0]u8{}, + .namespace_hash = Package.root_namespace_hash, }; - defer pkg_tree_root.destroy(gpa); - var cur_pkg: *Package = pkg_tree_root; + defer freePkgTree(gpa, &pkg_tree_root, false); + var cur_pkg: *Package = &pkg_tree_root; switch (arg_mode) { .build, .translate_c, .zig_test, .run => { @@ -658,8 +658,7 @@ fn buildOutputType( ) catch |err| { fatal("Failed to add package at path {s}: {s}", .{ pkg_path, @errorName(err) }); }; - new_cur_pkg.parent = cur_pkg; - try cur_pkg.add(gpa, pkg_name, new_cur_pkg); + try cur_pkg.addAndAdopt(gpa, pkg_name, new_cur_pkg); cur_pkg = new_cur_pkg; } else if (mem.eql(u8, arg, "--pkg-end")) { cur_pkg = cur_pkg.parent orelse @@ -1747,6 +1746,7 @@ fn buildOutputType( if (root_pkg) |pkg| { pkg.table = pkg_tree_root.table; pkg_tree_root.table = .{}; + pkg.namespace_hash = pkg_tree_root.namespace_hash; } const self_exe_path = try fs.selfExePathAlloc(arena); @@ -2151,6 +2151,18 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, hook: AfterUpdateHook) !voi } } +fn freePkgTree(gpa: *Allocator, pkg: *Package, free_parent: bool) void { + { + var it = pkg.table.iterator(); + while (it.next()) |kv| { + freePkgTree(gpa, kv.value, true); + } + } + if (free_parent) { + pkg.destroy(gpa); + } +} + fn cmdTranslateC(comp: *Compilation, arena: *Allocator, enable_cache: bool) !void { if (!build_options.have_llvm) fatal("cannot translate-c: compiler built without LLVM extensions", .{}); @@ -2505,6 +2517,7 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v .handle = try zig_lib_directory.handle.openDir(std_special, .{}), }, .root_src_path = "build_runner.zig", + .namespace_hash = Package.root_namespace_hash, }; defer root_pkg.root_src_directory.handle.close(); @@ -2550,8 +2563,9 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v var build_pkg: Package = .{ .root_src_directory = build_directory, .root_src_path = build_zig_basename, + .namespace_hash = undefined, }; - try root_pkg.table.put(arena, "@build", &build_pkg); + try root_pkg.addAndAdopt(arena, "@build", &build_pkg); var global_cache_directory: Compilation.Directory = l: { const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena); diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index 5c37a1247b725dfa34922068e60ef5603420176b..6e966fe75d1e458adc5cd08e9a16e6b404b00edd 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -9137,6 +9137,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) { buf_appendf(contents, "pub const position_independent_executable = %s;\n", bool_to_str(g->have_pie)); buf_appendf(contents, "pub const strip_debug_info = %s;\n", bool_to_str(g->strip_debug_symbols)); buf_appendf(contents, "pub const code_model = CodeModel.default;\n"); + buf_appendf(contents, "pub const zig_is_stage2 = false;\n"); { TargetSubsystem detected_subsystem = detect_subsystem(g); -- 2.54.0 From 61b868f9a5345ab1dba3395107c7cdee3fd2989e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 8 Apr 2021 20:37:19 -0700 Subject: [PATCH 10/17] stage2: simplify Decl src_node field Also fix "previous definition here" error notes to be correct. --- src/Module.zig | 130 +++++++++++--------------------- src/codegen.zig | 2 +- src/link/Elf.zig | 6 +- src/link/MachO/DebugSymbols.zig | 6 +- src/test.zig | 12 ++- test/stage2/test.zig | 15 +++- 6 files changed, 68 insertions(+), 103 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index 5883109852aed1219ba2a5aec59da9f5affaad4a..8da9a6dbdc76213ee631de4478ddf2dcf2fb8687 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -150,9 +150,15 @@ pub const Decl = struct { /// The direct parent container of the Decl. /// Reference to externally owned memory. container: *Scope.Container, - /// The AST Node decl index or ZIR Inst index that contains this declaration. + + /// An integer that can be checked against the corresponding incrementing + /// generation field of Module. This is used to determine whether `complete` status + /// represents pre- or post- re-analysis. + generation: u32, + /// The AST Node index or ZIR Inst index that contains this declaration. /// Must be recomputed when the corresponding source file is modified. - src_index: usize, + src_node: ast.Node.Index, + /// The most recent value of the Decl after a successful semantic analysis. typed_value: union(enum) { never_succeeded: void, @@ -198,11 +204,6 @@ pub const Decl = struct { /// Whether the corresponding AST decl has a `pub` keyword. is_pub: bool, - /// An integer that can be checked against the corresponding incrementing - /// generation field of Module. This is used to determine whether `complete` status - /// represents pre- or post- re-analysis. - generation: u32, - /// Represents the position of the code in the output file. /// This is populated regardless of semantic analysis and code generation. link: link.File.LinkBlock, @@ -249,11 +250,11 @@ pub const Decl = struct { } pub fn relativeToNodeIndex(decl: Decl, offset: i32) ast.Node.Index { - return @bitCast(ast.Node.Index, offset + @bitCast(i32, decl.srcNode())); + return @bitCast(ast.Node.Index, offset + @bitCast(i32, decl.src_node)); } pub fn nodeIndexToRelative(decl: Decl, node_index: ast.Node.Index) i32 { - return @bitCast(i32, node_index) - @bitCast(i32, decl.srcNode()); + return @bitCast(i32, node_index) - @bitCast(i32, decl.src_node); } pub fn tokSrcLoc(decl: Decl, token_index: ast.TokenIndex) LazySrcLoc { @@ -271,14 +272,9 @@ pub const Decl = struct { }; } - pub fn srcNode(decl: Decl) u32 { - const tree = &decl.container.file_scope.tree; - return tree.rootDecls()[decl.src_index]; - } - pub fn srcToken(decl: Decl) u32 { const tree = &decl.container.file_scope.tree; - return tree.firstToken(decl.srcNode()); + return tree.firstToken(decl.src_node); } pub fn srcByteOffset(decl: Decl) u32 { @@ -2458,7 +2454,7 @@ fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool { const tree = try mod.getAstTree(decl.container.file_scope); const node_tags = tree.nodes.items(.tag); const node_datas = tree.nodes.items(.data); - const decl_node = tree.rootDecls()[decl.src_index]; + const decl_node = decl.src_node; switch (node_tags[decl_node]) { .fn_decl => { const fn_proto = node_datas[decl_node].lhs; @@ -3292,7 +3288,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { var outdated_decls = std.AutoArrayHashMap(*Decl, void).init(mod.gpa); defer outdated_decls.deinit(); - for (decls) |decl_node, decl_i| switch (node_tags[decl_node]) { + for (decls) |decl_node| switch (node_tags[decl_node]) { .fn_decl => { const fn_proto = node_datas[decl_node].lhs; const body = node_datas[decl_node].rhs; @@ -3304,7 +3300,6 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { &deleted_decls, &outdated_decls, decl_node, - decl_i, tree.*, body, tree.fnProtoSimple(¶ms, fn_proto), @@ -3315,7 +3310,6 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { &deleted_decls, &outdated_decls, decl_node, - decl_i, tree.*, body, tree.fnProtoMulti(fn_proto), @@ -3327,7 +3321,6 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { &deleted_decls, &outdated_decls, decl_node, - decl_i, tree.*, body, tree.fnProtoOne(¶ms, fn_proto), @@ -3338,7 +3331,6 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { &deleted_decls, &outdated_decls, decl_node, - decl_i, tree.*, body, tree.fnProto(fn_proto), @@ -3353,7 +3345,6 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { &deleted_decls, &outdated_decls, decl_node, - decl_i, tree.*, 0, tree.fnProtoSimple(¶ms, decl_node), @@ -3364,7 +3355,6 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { &deleted_decls, &outdated_decls, decl_node, - decl_i, tree.*, 0, tree.fnProtoMulti(decl_node), @@ -3376,7 +3366,6 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { &deleted_decls, &outdated_decls, decl_node, - decl_i, tree.*, 0, tree.fnProtoOne(¶ms, decl_node), @@ -3387,7 +3376,6 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { &deleted_decls, &outdated_decls, decl_node, - decl_i, tree.*, 0, tree.fnProto(decl_node), @@ -3398,7 +3386,6 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { &deleted_decls, &outdated_decls, decl_node, - decl_i, tree.*, tree.globalVarDecl(decl_node), ), @@ -3407,7 +3394,6 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { &deleted_decls, &outdated_decls, decl_node, - decl_i, tree.*, tree.localVarDecl(decl_node), ), @@ -3416,7 +3402,6 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { &deleted_decls, &outdated_decls, decl_node, - decl_i, tree.*, tree.simpleVarDecl(decl_node), ), @@ -3425,7 +3410,6 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { &deleted_decls, &outdated_decls, decl_node, - decl_i, tree.*, tree.alignedVarDecl(decl_node), ), @@ -3438,35 +3422,16 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { const name_hash = container_scope.fullyQualifiedNameHash(name); const contents_hash = std.zig.hashSrc(tree.getNodeSource(decl_node)); - const new_decl = try mod.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash); + const new_decl = try mod.createNewDecl(&container_scope.base, name, decl_node, name_hash, contents_hash); container_scope.decls.putAssumeCapacity(new_decl, {}); mod.comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl }); }, - .container_field_init => try mod.semaContainerField( - container_scope, - &deleted_decls, - decl_node, - decl_i, - tree.*, - tree.containerFieldInit(decl_node), - ), - .container_field_align => try mod.semaContainerField( - container_scope, - &deleted_decls, - decl_node, - decl_i, - tree.*, - tree.containerFieldAlign(decl_node), - ), - .container_field => try mod.semaContainerField( - container_scope, - &deleted_decls, - decl_node, - decl_i, - tree.*, - tree.containerField(decl_node), - ), + // Container fields are handled in AstGen. + .container_field_init, + .container_field_align, + .container_field, + => continue, .test_decl => { if (mod.comp.bin_file.options.is_test) { @@ -3508,7 +3473,6 @@ fn semaContainerFn( deleted_decls: *std.AutoArrayHashMap(*Decl, void), outdated_decls: *std.AutoArrayHashMap(*Decl, void), decl_node: ast.Node.Index, - decl_i: usize, tree: ast.Tree, body_node: ast.Node.Index, fn_proto: ast.full.FnProto, @@ -3517,25 +3481,30 @@ fn semaContainerFn( defer tracy.end(); // We will create a Decl for it regardless of analysis status. - const name_tok = fn_proto.name_token orelse { + const name_token = fn_proto.name_token orelse { // This problem will go away with #1717. @panic("TODO missing function name"); }; - const name = tree.tokenSlice(name_tok); // TODO use identifierTokenString + const name = tree.tokenSlice(name_token); // TODO use identifierTokenString const name_hash = container_scope.fullyQualifiedNameHash(name); const contents_hash = std.zig.hashSrc(tree.getNodeSource(decl_node)); if (mod.decl_table.get(name_hash)) |decl| { // Update the AST Node index of the decl, even if its contents are unchanged, it may // have been re-ordered. - decl.src_index = decl_i; + const prev_src_node = decl.src_node; + decl.src_node = decl_node; if (deleted_decls.swapRemove(decl) == null) { decl.analysis = .sema_failure; const msg = try ErrorMsg.create(mod.gpa, .{ .container = .{ .file_scope = container_scope.file_scope }, - .lazy = .{ .token_abs = name_tok }, + .lazy = .{ .token_abs = name_token }, }, "redefinition of '{s}'", .{decl.name}); errdefer msg.destroy(mod.gpa); - try mod.errNoteNonLazy(decl.srcLoc(), msg, "previous definition here", .{}); + const other_src_loc: SrcLoc = .{ + .container = .{ .file_scope = decl.container.file_scope }, + .lazy = .{ .node_abs = prev_src_node }, + }; + try mod.errNoteNonLazy(other_src_loc, msg, "previous definition here", .{}); try mod.failed_decls.putNoClobber(mod.gpa, decl, msg); } else { if (!srcHashEql(decl.contents_hash, contents_hash)) { @@ -3559,7 +3528,7 @@ fn semaContainerFn( } } } else { - const new_decl = try mod.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash); + const new_decl = try mod.createNewDecl(&container_scope.base, name, decl_node, name_hash, contents_hash); container_scope.decls.putAssumeCapacity(new_decl, {}); if (fn_proto.extern_export_token) |maybe_export_token| { const token_tags = tree.tokens.items(.tag); @@ -3576,7 +3545,6 @@ fn semaContainerVar( deleted_decls: *std.AutoArrayHashMap(*Decl, void), outdated_decls: *std.AutoArrayHashMap(*Decl, void), decl_node: ast.Node.Index, - decl_i: usize, tree: ast.Tree, var_decl: ast.full.VarDecl, ) !void { @@ -3590,7 +3558,8 @@ fn semaContainerVar( if (mod.decl_table.get(name_hash)) |decl| { // Update the AST Node index of the decl, even if its contents are unchanged, it may // have been re-ordered. - decl.src_index = decl_i; + const prev_src_node = decl.src_node; + decl.src_node = decl_node; if (deleted_decls.swapRemove(decl) == null) { decl.analysis = .sema_failure; const msg = try ErrorMsg.create(mod.gpa, .{ @@ -3598,14 +3567,18 @@ fn semaContainerVar( .lazy = .{ .token_abs = name_token }, }, "redefinition of '{s}'", .{decl.name}); errdefer msg.destroy(mod.gpa); - try mod.errNoteNonLazy(decl.srcLoc(), msg, "previous definition here", .{}); + const other_src_loc: SrcLoc = .{ + .container = .{ .file_scope = decl.container.file_scope }, + .lazy = .{ .node_abs = prev_src_node }, + }; + try mod.errNoteNonLazy(other_src_loc, msg, "previous definition here", .{}); try mod.failed_decls.putNoClobber(mod.gpa, decl, msg); } else if (!srcHashEql(decl.contents_hash, contents_hash)) { try outdated_decls.put(decl, {}); decl.contents_hash = contents_hash; } } else { - const new_decl = try mod.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash); + const new_decl = try mod.createNewDecl(&container_scope.base, name, decl_node, name_hash, contents_hash); container_scope.decls.putAssumeCapacity(new_decl, {}); if (var_decl.extern_export_token) |maybe_export_token| { const token_tags = tree.tokens.items(.tag); @@ -3616,21 +3589,6 @@ fn semaContainerVar( } } -fn semaContainerField( - mod: *Module, - container_scope: *Scope.Container, - deleted_decls: *std.AutoArrayHashMap(*Decl, void), - decl_node: ast.Node.Index, - decl_i: usize, - tree: ast.Tree, - field: ast.full.ContainerField, -) !void { - const tracy = trace(@src()); - defer tracy.end(); - - log.err("TODO: analyze container field", .{}); -} - pub fn deleteDecl( mod: *Module, decl: *Decl, @@ -3813,7 +3771,7 @@ fn markOutdatedDecl(mod: *Module, decl: *Decl) !void { fn allocateNewDecl( mod: *Module, scope: *Scope, - src_index: usize, + src_node: ast.Node.Index, contents_hash: std.zig.SrcHash, ) !*Decl { // If we have emit-h then we must allocate a bigger structure to store the emit-h state. @@ -3829,7 +3787,7 @@ fn allocateNewDecl( new_decl.* = .{ .name = "", .container = scope.namespace(), - .src_index = src_index, + .src_node = src_node, .typed_value = .{ .never_succeeded = {} }, .analysis = .unreferenced, .deletion_flag = false, @@ -3860,12 +3818,12 @@ fn createNewDecl( mod: *Module, scope: *Scope, decl_name: []const u8, - src_index: usize, + src_node: ast.Node.Index, name_hash: Scope.NameHash, contents_hash: std.zig.SrcHash, ) !*Decl { try mod.decl_table.ensureCapacity(mod.gpa, mod.decl_table.items().len + 1); - const new_decl = try mod.allocateNewDecl(scope, src_index, contents_hash); + const new_decl = try mod.allocateNewDecl(scope, src_node, contents_hash); errdefer mod.gpa.destroy(new_decl); new_decl.name = try mem.dupeZ(mod.gpa, u8, decl_name); mod.decl_table.putAssumeCapacityNoClobber(name_hash, new_decl); @@ -4078,7 +4036,7 @@ pub fn createAnonymousDecl( defer mod.gpa.free(name); const name_hash = scope.namespace().fullyQualifiedNameHash(name); const src_hash: std.zig.SrcHash = undefined; - const new_decl = try mod.createNewDecl(scope, name, scope_decl.src_index, name_hash, src_hash); + const new_decl = try mod.createNewDecl(scope, name, scope_decl.src_node, name_hash, src_hash); const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State); decl_arena_state.* = decl_arena.state; @@ -4114,7 +4072,7 @@ pub fn createContainerDecl( defer mod.gpa.free(name); const name_hash = scope.namespace().fullyQualifiedNameHash(name); const src_hash: std.zig.SrcHash = undefined; - const new_decl = try mod.createNewDecl(scope, name, scope_decl.src_index, name_hash, src_hash); + const new_decl = try mod.createNewDecl(scope, name, scope_decl.src_node, name_hash, src_hash); const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State); decl_arena_state.* = decl_arena.state; diff --git a/src/codegen.zig b/src/codegen.zig index 220a8fa374bde73584f8bb0f6945b41cff81c4a5..1ab54bcc80fc64e4020c9807dd6352c39702cfb5 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -417,7 +417,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const node_datas = tree.nodes.items(.data); const token_starts = tree.tokens.items(.start); - const fn_decl = tree.rootDecls()[module_fn.owner_decl.src_index]; + const fn_decl = module_fn.owner_decl.src_node; assert(node_tags[fn_decl] == .fn_decl); const block = node_datas[fn_decl].rhs; const lbrace_src = token_starts[tree.firstToken(block)]; diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 3d074f4043760ac60d126e30e314d95e72b861cf..f60a7423a2fef6ab9be14298e0e35f593279e5a1 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -2228,10 +2228,9 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { const node_datas = tree.nodes.items(.data); const token_starts = tree.tokens.items(.start); - const file_ast_decls = tree.rootDecls(); // TODO Look into improving the performance here by adding a token-index-to-line // lookup table. Currently this involves scanning over the source code for newlines. - const fn_decl = file_ast_decls[decl.src_index]; + const fn_decl = decl.src_node; assert(node_tags[fn_decl] == .fn_decl); const block = node_datas[fn_decl].rhs; const lbrace = tree.firstToken(block); @@ -2755,10 +2754,9 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec const node_datas = tree.nodes.items(.data); const token_starts = tree.tokens.items(.start); - const file_ast_decls = tree.rootDecls(); // TODO Look into improving the performance here by adding a token-index-to-line // lookup table. Currently this involves scanning over the source code for newlines. - const fn_decl = file_ast_decls[decl.src_index]; + const fn_decl = decl.src_node; assert(node_tags[fn_decl] == .fn_decl); const block = node_datas[fn_decl].rhs; const lbrace = tree.firstToken(block); diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index a81fd00c0af69ddb9e62b5b4fec151dc3b67fc85..b05f0e1a7753dde26f6f377abbd2e62d3dd85e81 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -909,10 +909,9 @@ pub fn updateDeclLineNumber(self: *DebugSymbols, module: *Module, decl: *const M const node_datas = tree.nodes.items(.data); const token_starts = tree.tokens.items(.start); - const file_ast_decls = tree.rootDecls(); // TODO Look into improving the performance here by adding a token-index-to-line // lookup table. Currently this involves scanning over the source code for newlines. - const fn_decl = file_ast_decls[decl.src_index]; + const fn_decl = decl.src_node; assert(node_tags[fn_decl] == .fn_decl); const block = node_datas[fn_decl].rhs; const lbrace = tree.firstToken(block); @@ -959,10 +958,9 @@ pub fn initDeclDebugBuffers( const node_datas = tree.nodes.items(.data); const token_starts = tree.tokens.items(.start); - const file_ast_decls = tree.rootDecls(); // TODO Look into improving the performance here by adding a token-index-to-line // lookup table. Currently this involves scanning over the source code for newlines. - const fn_decl = file_ast_decls[decl.src_index]; + const fn_decl = decl.src_node; assert(node_tags[fn_decl] == .fn_decl); const block = node_datas[fn_decl].rhs; const lbrace = tree.firstToken(block); diff --git a/src/test.zig b/src/test.zig index d6c78281dcb5ba6e31350473791356d9a74cd57a..1ffbba535dc991c4fa40921dd81a435078cfb983 100644 --- a/src/test.zig +++ b/src/test.zig @@ -622,6 +622,7 @@ pub const TestContext = struct { var root_pkg: Package = .{ .root_src_directory = .{ .path = tmp_dir_path, .handle = tmp.dir }, .root_src_path = tmp_src_path, + .namespace_hash = Package.root_namespace_hash, }; const bin_name = try std.zig.binNameAlloc(arena, .{ @@ -639,13 +640,10 @@ pub const TestContext = struct { .directory = emit_directory, .basename = bin_name, }; - const emit_h: ?Compilation.EmitLoc = if (case.emit_h) - .{ - .directory = emit_directory, - .basename = "test_case.h", - } - else - null; + const emit_h: ?Compilation.EmitLoc = if (case.emit_h) .{ + .directory = emit_directory, + .basename = "test_case.h", + } else null; const comp = try Compilation.create(allocator, .{ .local_cache_directory = zig_cache_directory, .global_cache_directory = global_cache_directory, diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 4ef172e65d576c6eae17446a27bc925590b331be..298241e22a76f585a193940575224b3b27b82dd7 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -1040,9 +1040,22 @@ pub fn addCases(ctx: *TestContext) !void { } ctx.compileError("function redefinition", linux_x64, + \\// dummy comment \\fn entry() void {} \\fn entry() void {} - , &[_][]const u8{":2:4: error: redefinition of 'entry'"}); + , &[_][]const u8{ + ":3:4: error: redefinition of 'entry'", + ":2:1: note: previous definition here", + }); + + ctx.compileError("global variable redefinition", linux_x64, + \\// dummy comment + \\var foo = false; + \\var foo = true; + , &[_][]const u8{ + ":3:5: error: redefinition of 'foo'", + ":2:1: note: previous definition here", + }); ctx.compileError("compileError", linux_x64, \\export fn _start() noreturn { -- 2.54.0 From 1b702f8ddb11d53b53de8350323fd14b85d58caf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 8 Apr 2021 20:52:02 -0700 Subject: [PATCH 11/17] stage2: fix the memory leaks --- src/Module.zig | 14 ++++++++++++++ src/test.zig | 1 + 2 files changed, 15 insertions(+) diff --git a/src/Module.zig b/src/Module.zig index 8da9a6dbdc76213ee631de4478ddf2dcf2fb8687..af3a04fe9844115dc4f2510680ae17f6369308c9 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2292,6 +2292,20 @@ pub const InnerError = error{ OutOfMemory, AnalysisFail }; pub fn deinit(mod: *Module) void { const gpa = mod.gpa; + // The callsite of `Compilation.create` owns the `root_pkg`, however + // Module owns the builtin and std packages that it adds. + if (mod.root_pkg.table.remove("builtin")) |entry| { + gpa.free(entry.key); + entry.value.destroy(gpa); + } + if (mod.root_pkg.table.remove("std")) |entry| { + gpa.free(entry.key); + entry.value.destroy(gpa); + } + if (mod.root_pkg.table.remove("root")) |entry| { + gpa.free(entry.key); + } + mod.compile_log_text.deinit(gpa); mod.zig_cache_artifact_directory.handle.close(); diff --git a/src/test.zig b/src/test.zig index 1ffbba535dc991c4fa40921dd81a435078cfb983..f57708746ab3d15c4a0e956de94a02c60d942e43 100644 --- a/src/test.zig +++ b/src/test.zig @@ -624,6 +624,7 @@ pub const TestContext = struct { .root_src_path = tmp_src_path, .namespace_hash = Package.root_namespace_hash, }; + defer root_pkg.table.deinit(allocator); const bin_name = try std.zig.binNameAlloc(arena, .{ .root_name = "test_case", -- 2.54.0 From 6d616d82572045a594d97cef298e8453aa1ee189 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 8 Apr 2021 21:56:19 -0700 Subject: [PATCH 12/17] update stack trace test cases to new start.zig line offsets --- test/stack_traces.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/stack_traces.zig b/test/stack_traces.zig index d9c900ebae89be930b7d6c31e61f0bd282821a50..190e216563897ebda667fb8ed7aabf63a8bd73e9 100644 --- a/test/stack_traces.zig +++ b/test/stack_traces.zig @@ -282,10 +282,10 @@ pub fn addCases(cases: *tests.StackTracesContext) void { \\source.zig:10:8: [address] in main (test) \\ foo(); \\ ^ - \\start.zig:342:29: [address] in std.start.posixCallMainAndExit (test) + \\start.zig:404:29: [address] in std.start.posixCallMainAndExit (test) \\ return root.main(); \\ ^ - \\start.zig:163:5: [address] in std.start._start (test) + \\start.zig:225:5: [address] in std.start._start (test) \\ @call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{}); \\ ^ \\ @@ -294,7 +294,7 @@ pub fn addCases(cases: *tests.StackTracesContext) void { switch (std.Target.current.cpu.arch) { .aarch64 => "", // TODO disabled; results in segfault else => - \\start.zig:163:5: [address] in std.start._start (test) + \\start.zig:225:5: [address] in std.start._start (test) \\ @call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{}); \\ ^ \\ -- 2.54.0 From 91e1414b50beac41b8eee5e3a9f8ef7bdc67fdef Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 8 Apr 2021 21:56:42 -0700 Subject: [PATCH 13/17] stage2: implement array access to a global array --- src/AstGen.zig | 2 +- src/Sema.zig | 70 ++++++++++++++++++++++++++------------------ test/stage2/test.zig | 26 ++++++++++++++++ 3 files changed, 69 insertions(+), 29 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index e269c4ab4fd788288c2fdb862e9bd09ffdc95b9e..f4a07fb0286c7813538ac6463e0031d96130e2c3 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2349,7 +2349,7 @@ fn arrayAccess( ), else => return rvalue(gz, scope, rl, try gz.addBin( .elem_val, - try expr(gz, scope, .none, node_datas[node].lhs), + try expr(gz, scope, .none_or_ref, node_datas[node].lhs), try expr(gz, scope, .{ .ty = .usize_type }, node_datas[node].rhs), ), node), } diff --git a/src/Sema.zig b/src/Sema.zig index 161c4b05392128e5ea22b3ef39e5c60260349b83..04bc817839e3ed5b6e4b445183435666274cee4a 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1430,9 +1430,6 @@ fn zirDbgStmtNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerE } fn zirDeclRef(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { - const tracy = trace(@src()); - defer tracy.end(); - const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); const decl = sema.owner_decl.dependencies.entries.items[inst_data.payload_index].key; @@ -1440,9 +1437,6 @@ fn zirDeclRef(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError } fn zirDeclVal(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { - const tracy = trace(@src()); - defer tracy.end(); - const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); const decl = sema.owner_decl.dependencies.entries.items[inst_data.payload_index].key; @@ -2571,7 +2565,10 @@ fn zirElemVal(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError const bin_inst = sema.code.instructions.items(.data)[inst].bin; const array = try sema.resolveInst(bin_inst.lhs); - const array_ptr = try sema.analyzeRef(block, sema.src, array); + const array_ptr = if (array.ty.zigTypeTag() == .Pointer) + array + else + try sema.analyzeRef(block, sema.src, array); const elem_index = try sema.resolveInst(bin_inst.rhs); const result_ptr = try sema.elemPtr(block, sema.src, array_ptr, elem_index, sema.src); return sema.analyzeLoad(block, sema.src, result_ptr, sema.src); @@ -2586,7 +2583,10 @@ fn zirElemValNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerE const elem_index_src: LazySrcLoc = .{ .node_offset_array_access_index = inst_data.src_node }; const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data; const array = try sema.resolveInst(extra.lhs); - const array_ptr = try sema.analyzeRef(block, src, array); + const array_ptr = if (array.ty.zigTypeTag() == .Pointer) + array + else + try sema.analyzeRef(block, src, array); const elem_index = try sema.resolveInst(extra.rhs); const result_ptr = try sema.elemPtr(block, src, array_ptr, elem_index, elem_index_src); return sema.analyzeLoad(block, src, result_ptr, src); @@ -4786,37 +4786,51 @@ fn elemPtr( elem_index: *Inst, elem_index_src: LazySrcLoc, ) InnerError!*Inst { - const elem_ty = switch (array_ptr.ty.zigTypeTag()) { + const array_ty = switch (array_ptr.ty.zigTypeTag()) { .Pointer => array_ptr.ty.elemType(), else => return sema.mod.fail(&block.base, array_ptr.src, "expected pointer, found '{}'", .{array_ptr.ty}), }; - if (!elem_ty.isIndexable()) { - return sema.mod.fail(&block.base, src, "array access of non-array type '{}'", .{elem_ty}); + if (!array_ty.isIndexable()) { + return sema.mod.fail(&block.base, src, "array access of non-array type '{}'", .{array_ty}); } - - if (elem_ty.isSinglePointer() and elem_ty.elemType().zigTypeTag() == .Array) { + if (array_ty.isSinglePointer() and array_ty.elemType().zigTypeTag() == .Array) { // we have to deref the ptr operand to get the actual array pointer const array_ptr_deref = try sema.analyzeLoad(block, src, array_ptr, array_ptr.src); - if (array_ptr_deref.value()) |array_ptr_val| { - if (elem_index.value()) |index_val| { - // Both array pointer and index are compile-time known. - const index_u64 = index_val.toUnsignedInt(); - // @intCast here because it would have been impossible to construct a value that - // required a larger index. - const elem_ptr = try array_ptr_val.elemPtr(sema.arena, @intCast(usize, index_u64)); - const pointee_type = elem_ty.elemType().elemType(); - - return sema.mod.constInst(sema.arena, src, .{ - .ty = try Type.Tag.single_const_pointer.create(sema.arena, pointee_type), - .val = elem_ptr, - }); - } - } + return sema.elemPtrArray(block, src, array_ptr_deref, elem_index, elem_index_src); + } + if (array_ty.zigTypeTag() == .Array) { + return sema.elemPtrArray(block, src, array_ptr, elem_index, elem_index_src); } return sema.mod.fail(&block.base, src, "TODO implement more analyze elemptr", .{}); } +fn elemPtrArray( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + array_ptr: *Inst, + elem_index: *Inst, + elem_index_src: LazySrcLoc, +) InnerError!*Inst { + if (array_ptr.value()) |array_ptr_val| { + if (elem_index.value()) |index_val| { + // Both array pointer and index are compile-time known. + const index_u64 = index_val.toUnsignedInt(); + // @intCast here because it would have been impossible to construct a value that + // required a larger index. + const elem_ptr = try array_ptr_val.elemPtr(sema.arena, @intCast(usize, index_u64)); + const pointee_type = array_ptr.ty.elemType().elemType(); + + return sema.mod.constInst(sema.arena, src, .{ + .ty = try Type.Tag.single_const_pointer.create(sema.arena, pointee_type), + .val = elem_ptr, + }); + } + } + return sema.mod.fail(&block.base, src, "TODO implement more analyze elemptr for arrays", .{}); +} + fn coerce( sema: *Sema, block: *Scope.Block, diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 298241e22a76f585a193940575224b3b27b82dd7..e1130e5e286afb60167039cf14429cce7d66d635 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -941,6 +941,32 @@ pub fn addCases(ctx: *TestContext) !void { "", ); + // Array access to a global array. + case.addCompareOutput( + \\const hello = "hello".*; + \\export fn _start() noreturn { + \\ assert(hello[1] == 'e'); + \\ + \\ exit(); + \\} + \\ + \\pub fn assert(ok: bool) void { + \\ if (!ok) unreachable; // assertion failure + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "", + ); + // 64bit set stack case.addCompareOutput( \\export fn _start() noreturn { -- 2.54.0 From 6e05d53e88e07d6234aaf6e0b86cd52d8c9aea92 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 8 Apr 2021 22:33:40 -0700 Subject: [PATCH 14/17] AstGen: implement struct init with ResultLoc.ty --- src/AstGen.zig | 28 +++++++++++++++++++++++++++- src/Sema.zig | 14 ++++++++++++++ src/zir.zig | 29 +++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index f4a07fb0286c7813538ac6463e0031d96130e2c3..0a9f10e630ca1e06f6671dfc9d4483acc556658c 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -823,7 +823,31 @@ pub fn structInitExpr( .none, .none_or_ref => return mod.failNode(scope, node, "TODO implement structInitExpr none", .{}), .ref => unreachable, // struct literal not valid as l-value .ty => |ty_inst| { - return mod.failNode(scope, node, "TODO implement structInitExpr ty", .{}); + const fields_list = try gpa.alloc(zir.Inst.StructInit.Item, struct_init.ast.fields.len); + defer gpa.free(fields_list); + + for (struct_init.ast.fields) |field_init, i| { + const name_token = tree.firstToken(field_init) - 2; + const str_index = try gz.identAsString(name_token); + + const field_ty_inst = try gz.addPlNode(.field_type, field_init, zir.Inst.FieldType{ + .container_type = ty_inst, + .name_start = str_index, + }); + fields_list[i] = .{ + .field_type = astgen.refToIndex(field_ty_inst).?, + .init = try expr(gz, scope, .{ .ty = field_ty_inst }, field_init), + }; + } + const init_inst = try gz.addPlNode(.struct_init, node, zir.Inst.StructInit{ + .fields_len = @intCast(u32, fields_list.len), + }); + try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len + + fields_list.len * @typeInfo(zir.Inst.StructInit.Item).Struct.fields.len); + for (fields_list) |field| { + _ = gz.astgen.addExtraAssumeCapacity(field); + } + return rvalue(gz, scope, rl, init_inst, node); }, .ptr => |ptr_inst| { const field_ptr_list = try gpa.alloc(zir.Inst.Index, struct_init.ast.fields.len); @@ -1321,6 +1345,8 @@ fn blockExprStmts( .switch_capture_else, .switch_capture_else_ref, .struct_init_empty, + .struct_init, + .field_type, .struct_decl, .struct_decl_packed, .struct_decl_extern, diff --git a/src/Sema.zig b/src/Sema.zig index 04bc817839e3ed5b6e4b445183435666274cee4a..98c9d39a5ae8dbe867b77094bfd7e52911b9f96b 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -264,6 +264,8 @@ pub fn analyzeBody( .typeof_peer => try sema.zirTypeofPeer(block, inst), .xor => try sema.zirBitwise(block, inst, .xor), .struct_init_empty => try sema.zirStructInitEmpty(block, inst), + .struct_init => try sema.zirStructInit(block, inst), + .field_type => try sema.zirFieldType(block, inst), .struct_decl => try sema.zirStructDecl(block, inst, .Auto), .struct_decl_packed => try sema.zirStructDecl(block, inst, .Packed), @@ -4493,6 +4495,18 @@ fn zirStructInitEmpty(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) In }); } +fn zirStructInit(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + return sema.mod.fail(&block.base, src, "TODO: Sema.zirStructInit", .{}); +} + +fn zirFieldType(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + return sema.mod.fail(&block.base, src, "TODO: Sema.zirFieldType", .{}); +} + fn requireFunctionBlock(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) !void { if (sema.func == null) { return sema.mod.fail(&block.base, src, "instruction illegal outside function body", .{}); diff --git a/src/zir.zig b/src/zir.zig index 807e25e6b84b8f7f77c35d9fe459427613fb7eda..8086de5b75f20537abd2a183086e846bee1037d9 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -674,6 +674,13 @@ pub const Inst = struct { /// A struct literal with a specified type, with no fields. /// Uses the `un_node` field. struct_init_empty, + /// Given a struct, union, enum, or opaque and a field name, returns the field type. + /// Uses the `pl_node` field. Payload is `FieldType`. + field_type, + /// Finalizes a typed struct initialization, performs validation, and returns the + /// struct value. + /// Uses the `pl_node` field. Payload is `StructInit`. + struct_init, /// Converts an integer into an enum value. /// Uses `pl_node` with payload `Bin`. `lhs` is enum type, `rhs` is operand. int_to_enum, @@ -839,6 +846,8 @@ pub const Inst = struct { .switch_block_ref_under_multi, .validate_struct_init_ptr, .struct_init_empty, + .struct_init, + .field_type, .int_to_enum, .enum_to_int, => false, @@ -1551,6 +1560,24 @@ pub const Inst = struct { return @bitCast(f128, int_bits); } }; + + /// Trailing is an item per field. + pub const StructInit = struct { + fields_len: u32, + + pub const Item = struct { + /// The `field_type` ZIR instruction for this field init. + field_type: Index, + /// The field init expression to be used as the field value. + init: Ref, + }; + }; + + pub const FieldType = struct { + container_type: Ref, + /// Offset into `string_bytes`, null terminated. + name_start: u32, + }; }; pub const SpecialProng = enum { none, @"else", under }; @@ -1665,6 +1692,8 @@ const Writer = struct { .union_decl, .enum_decl, .enum_decl_nonexhaustive, + .struct_init, + .field_type, => try self.writePlNode(stream, inst), .add, -- 2.54.0 From 23db96931e5226f09a451663ba1c8bf2398af91b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 8 Apr 2021 22:38:13 -0700 Subject: [PATCH 15/17] AstGen: implement `@typeInfo` builtin --- src/AstGen.zig | 8 +++++++- src/Sema.zig | 7 +++++++ src/zir.zig | 4 ++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 0a9f10e630ca1e06f6671dfc9d4483acc556658c..3fe182fc2cf6a7608d53f48af4d91165c80a5718 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1356,6 +1356,7 @@ fn blockExprStmts( .opaque_decl, .int_to_enum, .enum_to_int, + .type_info, => break :b false, // ZIR instructions that are always either `noreturn` or `void`. @@ -4198,6 +4199,12 @@ fn builtinCall( return rvalue(gz, scope, rl, result, node); }, + .type_info => { + const operand = try typeExpr(gz, scope, params[0]); + const result = try gz.addUnNode(.type_info, operand, node); + return rvalue(gz, scope, rl, result, node); + }, + .add_with_overflow, .align_cast, .align_of, @@ -4274,7 +4281,6 @@ fn builtinCall( .This, .truncate, .Type, - .type_info, .type_name, .union_init, => return mod.failNode(scope, node, "TODO: implement builtin function {s}", .{ diff --git a/src/Sema.zig b/src/Sema.zig index 98c9d39a5ae8dbe867b77094bfd7e52911b9f96b..09c38f3a6585a643dd1327a5c47d9b32a9edacf8 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -259,6 +259,7 @@ pub fn analyzeBody( .switch_capture_multi_ref => try sema.zirSwitchCapture(block, inst, true, true), .switch_capture_else => try sema.zirSwitchCaptureElse(block, inst, false), .switch_capture_else_ref => try sema.zirSwitchCaptureElse(block, inst, true), + .type_info => try sema.zirTypeInfo(block, inst), .typeof => try sema.zirTypeof(block, inst), .typeof_elem => try sema.zirTypeofElem(block, inst), .typeof_peer => try sema.zirTypeofPeer(block, inst), @@ -4078,6 +4079,12 @@ fn zirCmp( return block.addBinOp(src, bool_type, tag, casted_lhs, casted_rhs); } +fn zirTypeInfo(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { + const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const src = inst_data.src(); + return sema.mod.fail(&block.base, src, "TODO: implement Sema.zirTypeInfo", .{}); +} + fn zirTypeof(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); diff --git a/src/zir.zig b/src/zir.zig index 8086de5b75f20537abd2a183086e846bee1037d9..06cf3bcf0723389973de9343d360b9d9715ed3ee 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -687,6 +687,8 @@ pub const Inst = struct { /// Converts an enum value into an integer. Resulting type will be the tag type /// of the enum. Uses `un_node`. enum_to_int, + /// Implements the `@typeInfo` builtin. Uses `un_node`. + type_info, /// Returns whether the instruction is one of the control flow "noreturn" types. /// Function calls do not count. @@ -850,6 +852,7 @@ pub const Inst = struct { .field_type, .int_to_enum, .enum_to_int, + .type_info, => false, .@"break", @@ -1652,6 +1655,7 @@ const Writer = struct { .typeof_elem, .struct_init_empty, .enum_to_int, + .type_info, => try self.writeUnNode(stream, inst), .ref, -- 2.54.0 From c6791d87d4f49598aab54064df683fc86e97dbd9 Mon Sep 17 00:00:00 2001 From: g-w1 <58830309+g-w1@users.noreply.github.com> Date: Fri, 9 Apr 2021 02:11:33 -0400 Subject: [PATCH 16/17] stage2: delete allowing input (and output) zir from the pipeline (#8471) Remove -femit-zir as we aren't going to need it. Also remove zir test code This removes a TODO that asserts the file is not zir. --- src/Compilation.zig | 12 +---------- src/main.zig | 18 ++-------------- src/test.zig | 50 ++++++++++++++------------------------------- 3 files changed, 18 insertions(+), 62 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 73fafa0976e7af72f5c0d472b4e872fe85c7963a..6e38fe14eb45f21c139e6bf81dc0947ab3d55977 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -939,11 +939,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { // However we currently do not have serialization of such metadata, so for now // we set up an empty Module that does the entire compilation fresh. - // TODO remove CLI support for .zir files and then we can remove this error - // handling and assertion. - if (mem.endsWith(u8, root_pkg.root_src_path, ".zir")) return error.ZirFilesUnsupported; - assert(mem.endsWith(u8, root_pkg.root_src_path, ".zig")); - const root_scope = try gpa.create(Module.Scope.File); errdefer gpa.destroy(root_scope); @@ -2487,7 +2482,7 @@ pub fn addCCArgs( try argv.append("-fPIC"); } }, - .shared_library, .assembly, .ll, .bc, .unknown, .static_library, .object, .zig, .zir => {}, + .shared_library, .assembly, .ll, .bc, .unknown, .static_library, .object, .zig => {}, } if (out_dep_path) |p| { try argv.appendSlice(&[_][]const u8{ "-MD", "-MV", "-MF", p }); @@ -2561,7 +2556,6 @@ pub const FileExt = enum { object, static_library, zig, - zir, unknown, pub fn clangSupportsDepFile(ext: FileExt) bool { @@ -2575,7 +2569,6 @@ pub const FileExt = enum { .object, .static_library, .zig, - .zir, .unknown, => false, }; @@ -2647,8 +2640,6 @@ pub fn classifyFileExt(filename: []const u8) FileExt { return .h; } else if (mem.endsWith(u8, filename, ".zig")) { return .zig; - } else if (mem.endsWith(u8, filename, ".zir")) { - return .zir; } else if (hasSharedLibraryExt(filename)) { return .shared_library; } else if (hasStaticLibraryExt(filename)) { @@ -2669,7 +2660,6 @@ test "classifyFileExt" { std.testing.expectEqual(FileExt.shared_library, classifyFileExt("foo.so.1.2.3")); std.testing.expectEqual(FileExt.unknown, classifyFileExt("foo.so.1.2.3~")); std.testing.expectEqual(FileExt.zig, classifyFileExt("foo.zig")); - std.testing.expectEqual(FileExt.zir, classifyFileExt("foo.zir")); } fn haveFramePointer(comp: *const Compilation) bool { diff --git a/src/main.zig b/src/main.zig index 6136c964ceb29cd5528b1c9318c06f4c884b4a99..208dea6d8c4d6536ca254549b5d589ab16c82ac9 100644 --- a/src/main.zig +++ b/src/main.zig @@ -505,7 +505,6 @@ fn buildOutputType( var emit_bin: EmitBin = .yes_default_path; var emit_asm: Emit = .no; var emit_llvm_ir: Emit = .no; - var emit_zir: Emit = .no; var emit_docs: Emit = .no; var emit_analysis: Emit = .no; var target_arch_os_abi: []const u8 = "native"; @@ -923,12 +922,6 @@ fn buildOutputType( emit_bin = .{ .yes = arg["-femit-bin=".len..] }; } else if (mem.eql(u8, arg, "-fno-emit-bin")) { emit_bin = .no; - } else if (mem.eql(u8, arg, "-femit-zir")) { - emit_zir = .yes_default_path; - } else if (mem.startsWith(u8, arg, "-femit-zir=")) { - emit_zir = .{ .yes = arg["-femit-zir=".len..] }; - } else if (mem.eql(u8, arg, "-fno-emit-zir")) { - emit_zir = .no; } else if (mem.eql(u8, arg, "-femit-h")) { emit_h = .yes_default_path; } else if (mem.startsWith(u8, arg, "-femit-h=")) { @@ -1025,7 +1018,7 @@ fn buildOutputType( .extra_flags = try arena.dupe([]const u8, extra_cflags.items), }); }, - .zig, .zir => { + .zig => { if (root_src_file) |other| { fatal("found another zig file '{s}' after root source file '{s}'", .{ arg, other }); } else { @@ -1086,7 +1079,7 @@ fn buildOutputType( .unknown, .shared_library, .object, .static_library => { try link_objects.append(it.only_arg); }, - .zig, .zir => { + .zig => { if (root_src_file) |other| { fatal("found another zig file '{s}' after root source file '{s}'", .{ it.only_arg, other }); } else { @@ -1724,13 +1717,6 @@ fn buildOutputType( var emit_docs_resolved = try emit_docs.resolve("docs"); defer emit_docs_resolved.deinit(); - switch (emit_zir) { - .no => {}, - .yes_default_path, .yes => { - fatal("The -femit-zir implementation has been intentionally deleted so that it can be rewritten as a proper backend.", .{}); - }, - } - const root_pkg: ?*Package = if (root_src_file) |src_path| blk: { if (main_pkg_path) |p| { const rel_src_path = try fs.path.relative(gpa, p, src_path); diff --git a/src/test.zig b/src/test.zig index f57708746ab3d15c4a0e956de94a02c60d942e43..ca3f073e144674d62fa43bda7296087b7c9c0952 100644 --- a/src/test.zig +++ b/src/test.zig @@ -122,11 +122,6 @@ pub const TestContext = struct { path: []const u8, }; - pub const Extension = enum { - Zig, - ZIR, - }; - /// A `Case` consists of a list of `Update`. The same `Compilation` is used for each /// update, so each update's source is treated as a single file being /// updated by the test harness and incrementally compiled. @@ -141,7 +136,6 @@ pub const TestContext = struct { /// to Executable. output_mode: std.builtin.OutputMode, updates: std.ArrayList(Update), - extension: Extension, object_format: ?std.builtin.ObjectFormat = null, emit_h: bool = false, llvm_backend: bool = false, @@ -238,14 +232,12 @@ pub const TestContext = struct { ctx: *TestContext, name: []const u8, target: CrossTarget, - extension: Extension, ) *Case { ctx.cases.append(Case{ .name = name, .target = target, .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Exe, - .extension = extension, .files = std.ArrayList(File).init(ctx.cases.allocator), }) catch @panic("out of memory"); return &ctx.cases.items[ctx.cases.items.len - 1]; @@ -253,7 +245,7 @@ pub const TestContext = struct { /// Adds a test case for Zig input, producing an executable pub fn exe(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case { - return ctx.addExe(name, target, .Zig); + return ctx.addExe(name, target); } /// Adds a test case for ZIR input, producing an executable @@ -269,7 +261,6 @@ pub const TestContext = struct { .target = target, .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Exe, - .extension = .Zig, .object_format = .c, .files = std.ArrayList(File).init(ctx.cases.allocator), }) catch @panic("out of memory"); @@ -284,7 +275,6 @@ pub const TestContext = struct { .target = target, .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Exe, - .extension = .Zig, .files = std.ArrayList(File).init(ctx.cases.allocator), .llvm_backend = true, }) catch @panic("out of memory"); @@ -295,14 +285,12 @@ pub const TestContext = struct { ctx: *TestContext, name: []const u8, target: CrossTarget, - extension: Extension, ) *Case { ctx.cases.append(Case{ .name = name, .target = target, .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Obj, - .extension = extension, .files = std.ArrayList(File).init(ctx.cases.allocator), }) catch @panic("out of memory"); return &ctx.cases.items[ctx.cases.items.len - 1]; @@ -310,7 +298,7 @@ pub const TestContext = struct { /// Adds a test case for Zig input, producing an object file. pub fn obj(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case { - return ctx.addObj(name, target, .Zig); + return ctx.addObj(name, target); } /// Adds a test case for ZIR input, producing an object file. @@ -319,13 +307,12 @@ pub const TestContext = struct { } /// Adds a test case for Zig or ZIR input, producing C code. - pub fn addC(ctx: *TestContext, name: []const u8, target: CrossTarget, ext: Extension) *Case { + pub fn addC(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case { ctx.cases.append(Case{ .name = name, .target = target, .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Obj, - .extension = ext, .object_format = .c, .files = std.ArrayList(File).init(ctx.cases.allocator), }) catch @panic("out of memory"); @@ -333,21 +320,20 @@ pub const TestContext = struct { } pub fn c(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void { - ctx.addC(name, target, .Zig).addCompareObjectFile(src, zig_h ++ out); + ctx.addC(name, target).addCompareObjectFile(src, zig_h ++ out); } pub fn h(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void { - ctx.addC(name, target, .Zig).addHeader(src, zig_h ++ out); + ctx.addC(name, target).addHeader(src, zig_h ++ out); } pub fn addCompareOutput( ctx: *TestContext, name: []const u8, - extension: Extension, src: [:0]const u8, expected_stdout: []const u8, ) void { - ctx.addExe(name, .{}, extension).addCompareOutput(src, expected_stdout); + ctx.addExe(name, .{}).addCompareOutput(src, expected_stdout); } /// Adds a test case that compiles the Zig source given in `src`, executes @@ -358,7 +344,7 @@ pub const TestContext = struct { src: [:0]const u8, expected_stdout: []const u8, ) void { - return ctx.addCompareOutput(name, .Zig, src, expected_stdout); + return ctx.addCompareOutput(name, src, expected_stdout); } /// Adds a test case that compiles the ZIR source given in `src`, executes @@ -376,11 +362,10 @@ pub const TestContext = struct { ctx: *TestContext, name: []const u8, target: CrossTarget, - extension: Extension, src: [:0]const u8, result: [:0]const u8, ) void { - ctx.addObj(name, target, extension).addTransform(src, result); + ctx.addObj(name, target).addTransform(src, result); } /// Adds a test case that compiles the Zig given in `src` to ZIR and tests @@ -392,7 +377,7 @@ pub const TestContext = struct { src: [:0]const u8, result: [:0]const u8, ) void { - ctx.addTransform(name, target, .Zig, src, result); + ctx.addTransform(name, target, src, result); } /// Adds a test case that cleans up the ZIR source given in `src`, and @@ -411,11 +396,10 @@ pub const TestContext = struct { ctx: *TestContext, name: []const u8, target: CrossTarget, - extension: Extension, src: [:0]const u8, expected_errors: []const []const u8, ) void { - ctx.addObj(name, target, extension).addError(src, expected_errors); + ctx.addObj(name, target).addError(src, expected_errors); } /// Adds a test case that ensures that the Zig given in `src` fails to @@ -428,7 +412,7 @@ pub const TestContext = struct { src: [:0]const u8, expected_errors: []const []const u8, ) void { - ctx.addError(name, target, .Zig, src, expected_errors); + ctx.addError(name, target, src, expected_errors); } /// Adds a test case that ensures that the ZIR given in `src` fails to @@ -448,10 +432,9 @@ pub const TestContext = struct { ctx: *TestContext, name: []const u8, target: CrossTarget, - extension: Extension, src: [:0]const u8, ) void { - ctx.addObj(name, target, extension).compiles(src); + ctx.addObj(name, target).compiles(src); } /// Adds a test case that asserts that the Zig given in `src` compiles @@ -462,7 +445,7 @@ pub const TestContext = struct { target: CrossTarget, src: [:0]const u8, ) void { - ctx.addCompiles(name, target, .Zig, src); + ctx.addCompiles(name, target, src); } /// Adds a test case that asserts that the ZIR given in `src` compiles @@ -489,7 +472,7 @@ pub const TestContext = struct { expected_errors: []const []const u8, fixed_src: [:0]const u8, ) void { - var case = ctx.addObj(name, target, .Zig); + var case = ctx.addObj(name, target); case.addError(src, expected_errors); case.compiles(fixed_src); } @@ -614,10 +597,7 @@ pub const TestContext = struct { .path = try std.fs.path.join(arena, &[_][]const u8{ tmp_dir_path, "zig-cache" }), }; - const tmp_src_path = switch (case.extension) { - .Zig => "test_case.zig", - .ZIR => "test_case.zir", - }; + const tmp_src_path = "test_case.zig"; var root_pkg: Package = .{ .root_src_directory = .{ .path = tmp_dir_path, .handle = tmp.dir }, -- 2.54.0 From afe5862111034ea4100b0eea5971e181d70ffc39 Mon Sep 17 00:00:00 2001 From: jacob gw Date: Fri, 9 Apr 2021 00:09:21 -0400 Subject: [PATCH 17/17] stage2: add error for private decls accessed from other files --- src/Module.zig | 1 + src/Sema.zig | 6 ++++-- test/stage2/test.zig | 39 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index af3a04fe9844115dc4f2510680ae17f6369308c9..d570652e730f118fdd1b4a39c505f4240a1918b6 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3550,6 +3550,7 @@ fn semaContainerFn( mod.comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl }); } } + new_decl.is_pub = fn_proto.visib_token != null; } } diff --git a/src/Sema.zig b/src/Sema.zig index 09c38f3a6585a643dd1327a5c47d9b32a9edacf8..1b6941806ee0044f73b5115b86ac28d5037690e7 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -4708,7 +4708,8 @@ fn namedFieldPtr( .Struct, .Opaque, .Union => { if (child_type.getContainerScope()) |container_scope| { if (mod.lookupDeclName(&container_scope.base, field_name)) |decl| { - // TODO if !decl.is_pub and inDifferentFiles() "{} is private" + if (!decl.is_pub and !(decl.container.file_scope == block.base.namespace().file_scope)) + return mod.fail(&block.base, src, "'{s}' is private", .{field_name}); return sema.analyzeDeclRef(block, src, decl); } @@ -4736,7 +4737,8 @@ fn namedFieldPtr( .Enum => { if (child_type.getContainerScope()) |container_scope| { if (mod.lookupDeclName(&container_scope.base, field_name)) |decl| { - // TODO if !decl.is_pub and inDifferentFiles() "{} is private" + if (!decl.is_pub and !(decl.container.file_scope == block.base.namespace().file_scope)) + return mod.fail(&block.base, src, "'{s}' is private", .{field_name}); return sema.analyzeDeclRef(block, src, decl); } } diff --git a/test/stage2/test.zig b/test/stage2/test.zig index e1130e5e286afb60167039cf14429cce7d66d635..b4bc1a413e93571bad8efb056bbb86a49c955f93 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -1048,7 +1048,7 @@ pub fn addCases(ctx: *TestContext) !void { "Hello, World!\n", ); try case.files.append(.{ - .src = + .src = \\pub fn print() void { \\ asm volatile ("syscall" \\ : @@ -1064,6 +1064,43 @@ pub fn addCases(ctx: *TestContext) !void { .path = "print.zig", }); } + { + var case = ctx.exe("import private", linux_x64); + case.addError( + \\export fn _start() noreturn { + \\ @import("print.zig").print(); + \\ exit(); + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (@as(usize, 0)) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + &.{":2:25: error: 'print' is private"}, + ); + try case.files.append(.{ + .src = + \\fn print() void { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (@as(usize, 1)), + \\ [arg1] "{rdi}" (@as(usize, 1)), + \\ [arg2] "{rsi}" (@ptrToInt("Hello, World!\n")), + \\ [arg3] "{rdx}" (@as(usize, 14)) + \\ : "rcx", "r11", "memory" + \\ ); + \\ return; + \\} + , + .path = "print.zig", + }); + } ctx.compileError("function redefinition", linux_x64, \\// dummy comment -- 2.54.0