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| {