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 933917d948..dc22b3fd3b 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) { 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 8ee27b1658..364f76cc7a 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 519d5df401..d2abaaf091 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 d3d9d56bfd..ff402a9ddb 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, 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 080d4bddaa..95db5e78c6 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(); 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 95db5e78c6..c0409fcb3b 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 33ff4766ca..03c9e9ea3d 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); } } 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 364f76cc7a..20e52ea559 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 d2abaaf091..b4b4798850 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 ff402a9ddb..40ad2b7844 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, 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 0000000000..22f3578d98 --- /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 c0409fcb3b..2a3d9659b0 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 dc22b3fd3b..18dfc6bae0 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 03c9e9ea3d..d5960dcf9a 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 b4b4798850..d4c2592446 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 5fb74db61f..0f03813a36 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| { 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 18dfc6bae0..dab3319b2e 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. 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 20e52ea559..e269c4ab4f 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 2a3d9659b0..080d4bddaa 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 dab3319b2e..933917d948 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 d5960dcf9a..33ff4766ca 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 d4c2592446..51d350ea7c 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 0f03813a36..5fb74db61f 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 40ad2b7844..807e25e6b8 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)`. 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 0fb96c768f..5454cc58c6 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 22f3578d98..0000000000 --- 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 f8bc477068..5f10def72e 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 cff07a2bd2..714235523e 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 080d4bddaa..73fafa0976 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 933917d948..5883109852 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 33ff4766ca..25a9f55d63 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 51d350ea7c..161c4b0539 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 5fb74db61f..6136c964ce 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 5c37a1247b..6e966fe75d 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); 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 5883109852..8da9a6dbdc 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 220a8fa374..1ab54bcc80 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 3d074f4043..f60a7423a2 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 a81fd00c0a..b05f0e1a77 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 d6c78281dc..1ffbba535d 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 4ef172e65d..298241e22a 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 { 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 8da9a6dbdc..af3a04fe98 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 1ffbba535d..f57708746a 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", 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 d9c900ebae..190e216563 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, .{}); \\ ^ \\ 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 e269c4ab4f..f4a07fb028 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 161c4b0539..04bc817839 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 298241e22a..e1130e5e28 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 { 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 f4a07fb028..0a9f10e630 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 04bc817839..98c9d39a5a 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 807e25e6b8..8086de5b75 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, 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 0a9f10e630..3fe182fc2c 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 98c9d39a5a..09c38f3a65 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 8086de5b75..06cf3bcf07 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, 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 73fafa0976..6e38fe14eb 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 6136c964ce..208dea6d8c 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 f57708746a..ca3f073e14 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 }, 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 af3a04fe98..d570652e73 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 09c38f3a65..1b6941806e 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 e1130e5e28..b4bc1a413e 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