Commit ac14b52e85

Timon Kruiper <timonkruiper@gmail.com>
2021-04-05 22:50:54
stage2: add support for start.zig
This adds a simplified start2.zig that the current stage2 compiler is able to generate code for.
1 parent e85cd61
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;
+}
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();
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| {
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| {
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);
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);