diff --git a/build.zig b/build.zig index ab1d985b74..b57ae69638 100644 --- a/build.zig +++ b/build.zig @@ -51,6 +51,9 @@ pub fn build(b: *Builder) !void { var exe = b.addExecutable("zig", "src-self-hosted/main.zig"); exe.setBuildMode(mode); + test_step.dependOn(&exe.step); + b.default_step.dependOn(&exe.step); + exe.install(); const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false; const skip_release_small = b.option(bool, "skip-release-small", "Main test suite skips release-small builds") orelse skip_release; @@ -58,21 +61,17 @@ pub fn build(b: *Builder) !void { const skip_release_safe = b.option(bool, "skip-release-safe", "Main test suite skips release-safe builds") orelse skip_release; const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false; const skip_libc = b.option(bool, "skip-libc", "Main test suite skips tests that link libc") orelse false; - const skip_self_hosted = (b.option(bool, "skip-self-hosted", "Main test suite skips building self hosted compiler") orelse false) or true; // TODO evented I/O good enough that this passes everywhere - if (!skip_self_hosted) { - test_step.dependOn(&exe.step); - } const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false; - if (!only_install_lib_files and !skip_self_hosted) { + const enable_llvm = b.option(bool, "enable-llvm", "Build self-hosted compiler with LLVM backend enabled") orelse false; + if (enable_llvm) { var ctx = parseConfigH(b, config_h_text); ctx.llvm = try findLLVM(b, ctx.llvm_config_exe); try configureStage2(b, exe, ctx); - - b.default_step.dependOn(&exe.step); - exe.install(); } + const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse false; + if (link_libc) exe.linkLibC(); b.installDirectory(InstallDirectoryOptions{ .source_dir = "lib", diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig deleted file mode 100644 index 75be005c83..0000000000 --- a/src-self-hosted/compilation.zig +++ /dev/null @@ -1,1457 +0,0 @@ -const std = @import("std"); -const io = std.io; -const mem = std.mem; -const Allocator = mem.Allocator; -const ArrayListSentineled = std.ArrayListSentineled; -const llvm = @import("llvm.zig"); -const c = @import("c.zig"); -const builtin = std.builtin; -const Target = std.Target; -const warn = std.debug.warn; -const Token = std.zig.Token; -const ArrayList = std.ArrayList; -const errmsg = @import("errmsg.zig"); -const ast = std.zig.ast; -const event = std.event; -const assert = std.debug.assert; -const AtomicRmwOp = builtin.AtomicRmwOp; -const AtomicOrder = builtin.AtomicOrder; -const Scope = @import("scope.zig").Scope; -const Decl = @import("decl.zig").Decl; -const ir = @import("ir.zig"); -const Value = @import("value.zig").Value; -const Type = Value.Type; -const Span = errmsg.Span; -const Msg = errmsg.Msg; -const codegen = @import("codegen.zig"); -const Package = @import("package.zig").Package; -const link = @import("link.zig").link; -const LibCInstallation = @import("libc_installation.zig").LibCInstallation; -const CInt = @import("c_int.zig").CInt; -const fs = std.fs; - -pub const Visib = enum { - Private, - Pub, -}; - -const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB - -/// Data that is local to the event loop. -pub const ZigCompiler = struct { - llvm_handle_pool: std.atomic.Stack(*llvm.Context), - lld_lock: event.Lock, - allocator: *Allocator, - - /// TODO pool these so that it doesn't have to lock - prng: event.Locked(std.rand.DefaultPrng), - - native_libc: event.Future(LibCInstallation), - - var lazy_init_targets = std.once(initializeAllTargets); - - pub fn init(allocator: *Allocator) !ZigCompiler { - lazy_init_targets.call(); - - var seed_bytes: [@sizeOf(u64)]u8 = undefined; - try std.crypto.randomBytes(seed_bytes[0..]); - const seed = mem.readIntNative(u64, &seed_bytes); - - return ZigCompiler{ - .allocator = allocator, - .lld_lock = event.Lock.init(), - .llvm_handle_pool = std.atomic.Stack(*llvm.Context).init(), - .prng = event.Locked(std.rand.DefaultPrng).init(std.rand.DefaultPrng.init(seed)), - .native_libc = event.Future(LibCInstallation).init(), - }; - } - - /// Must be called only after EventLoop.run completes. - fn deinit(self: *ZigCompiler) void { - self.lld_lock.deinit(); - while (self.llvm_handle_pool.pop()) |node| { - llvm.ContextDispose(node.data); - self.allocator.destroy(node); - } - } - - /// Gets an exclusive handle on any LlvmContext. - /// Caller must release the handle when done. - pub fn getAnyLlvmContext(self: *ZigCompiler) !LlvmHandle { - if (self.llvm_handle_pool.pop()) |node| return LlvmHandle{ .node = node }; - - const context_ref = llvm.ContextCreate() orelse return error.OutOfMemory; - errdefer llvm.ContextDispose(context_ref); - - const node = try self.allocator.create(std.atomic.Stack(*llvm.Context).Node); - node.* = std.atomic.Stack(*llvm.Context).Node{ - .next = undefined, - .data = context_ref, - }; - errdefer self.allocator.destroy(node); - - return LlvmHandle{ .node = node }; - } - - pub fn getNativeLibC(self: *ZigCompiler) !*LibCInstallation { - if (self.native_libc.start()) |ptr| return ptr; - self.native_libc.data = try LibCInstallation.findNative(.{ .allocator = self.allocator }); - self.native_libc.resolve(); - return &self.native_libc.data; - } - - /// Must be called only once, ever. Sets global state. - pub fn setLlvmArgv(allocator: *Allocator, llvm_argv: []const []const u8) !void { - if (llvm_argv.len != 0) { - var c_compatible_args = try std.cstr.NullTerminated2DArray.fromSlices(allocator, &[_][]const []const u8{ - &[_][]const u8{"zig (LLVM option parsing)"}, - llvm_argv, - }); - defer c_compatible_args.deinit(); - c.ZigLLVMParseCommandLineOptions(llvm_argv.len + 1, c_compatible_args.ptr); - } - } -}; - -pub const LlvmHandle = struct { - node: *std.atomic.Stack(*llvm.Context).Node, - - pub fn release(self: LlvmHandle, zig_compiler: *ZigCompiler) void { - zig_compiler.llvm_handle_pool.push(self.node); - } -}; - -pub const Compilation = struct { - pub const FnLinkSet = std.TailQueue(?*Value.Fn); - - zig_compiler: *ZigCompiler, - name: ArrayListSentineled(u8, 0), - llvm_triple: ArrayListSentineled(u8, 0), - root_src_path: ?[]const u8, - target: std.Target, - llvm_target: *llvm.Target, - build_mode: builtin.Mode, - zig_lib_dir: []const u8, - zig_std_dir: []const u8, - - /// lazily created when we need it - tmp_dir: event.Future(BuildError![]u8) = event.Future(BuildError![]u8).init(), - - version: builtin.Version = builtin.Version{ .major = 0, .minor = 0, .patch = 0 }, - - linker_script: ?[]const u8 = null, - out_h_path: ?[]const u8 = null, - - is_test: bool = false, - strip: bool = false, - is_static: bool, - linker_rdynamic: bool = false, - - clang_argv: []const []const u8 = &[_][]const u8{}, - assembly_files: []const []const u8 = &[_][]const u8{}, - - /// paths that are explicitly provided by the user to link against - link_objects: []const []const u8 = &[_][]const u8{}, - - /// functions that have their own objects that we need to link - /// it uses an optional pointer so that tombstone removals are possible - fn_link_set: event.Locked(FnLinkSet) = event.Locked(FnLinkSet).init(FnLinkSet.init()), - - link_libs_list: ArrayList(*LinkLib), - libc_link_lib: ?*LinkLib = null, - - err_color: errmsg.Color = .Auto, - - verbose_tokenize: bool = false, - verbose_ast_tree: bool = false, - verbose_ast_fmt: bool = false, - verbose_cimport: bool = false, - verbose_ir: bool = false, - verbose_llvm_ir: bool = false, - verbose_link: bool = false, - - link_eh_frame_hdr: bool = false, - - darwin_version_min: DarwinVersionMin = .None, - - test_filters: []const []const u8 = &[_][]const u8{}, - test_name_prefix: ?[]const u8 = null, - - emit_bin: bool = true, - emit_asm: bool = false, - emit_llvm_ir: bool = false, - emit_h: bool = false, - - kind: Kind, - - events: *event.Channel(Event), - - exported_symbol_names: event.Locked(Decl.Table), - - /// Before code generation starts, must wait on this group to make sure - /// the build is complete. - prelink_group: event.Group(BuildError!void), - - compile_errors: event.Locked(CompileErrList), - - meta_type: *Type.MetaType, - void_type: *Type.Void, - bool_type: *Type.Bool, - noreturn_type: *Type.NoReturn, - comptime_int_type: *Type.ComptimeInt, - u8_type: *Type.Int, - - void_value: *Value.Void, - true_value: *Value.Bool, - false_value: *Value.Bool, - noreturn_value: *Value.NoReturn, - - target_machine: *llvm.TargetMachine, - target_data_ref: *llvm.TargetData, - target_layout_str: [*:0]u8, - target_ptr_bits: u32, - - /// for allocating things which have the same lifetime as this Compilation - arena_allocator: std.heap.ArenaAllocator, - - root_package: *Package, - std_package: *Package, - - override_libc: ?*LibCInstallation = null, - - /// need to wait on this group before deinitializing - deinit_group: event.Group(void), - - destroy_frame: *@Frame(createAsync), - main_loop_frame: *@Frame(Compilation.mainLoop), - main_loop_future: event.Future(void) = event.Future(void).init(), - - have_err_ret_tracing: bool = false, - - /// not locked because it is read-only - primitive_type_table: TypeTable, - - int_type_table: event.Locked(IntTypeTable), - array_type_table: event.Locked(ArrayTypeTable), - ptr_type_table: event.Locked(PtrTypeTable), - fn_type_table: event.Locked(FnTypeTable), - - c_int_types: [CInt.list.len]*Type.Int, - - fs_watch: *fs.Watch(*Scope.Root), - - cancelled: bool = false, - - const IntTypeTable = std.HashMap(*const Type.Int.Key, *Type.Int, Type.Int.Key.hash, Type.Int.Key.eql); - const ArrayTypeTable = std.HashMap(*const Type.Array.Key, *Type.Array, Type.Array.Key.hash, Type.Array.Key.eql); - const PtrTypeTable = std.HashMap(*const Type.Pointer.Key, *Type.Pointer, Type.Pointer.Key.hash, Type.Pointer.Key.eql); - const FnTypeTable = std.HashMap(*const Type.Fn.Key, *Type.Fn, Type.Fn.Key.hash, Type.Fn.Key.eql); - const TypeTable = std.StringHashMap(*Type); - - const CompileErrList = std.ArrayList(*Msg); - - // TODO handle some of these earlier and report them in a way other than error codes - pub const BuildError = error{ - OutOfMemory, - EndOfStream, - IsDir, - Unexpected, - SystemResources, - SharingViolation, - PathAlreadyExists, - FileNotFound, - AccessDenied, - PipeBusy, - FileTooBig, - SymLinkLoop, - ProcessFdQuotaExceeded, - NameTooLong, - SystemFdQuotaExceeded, - NoDevice, - NoSpaceLeft, - NotDir, - FileSystem, - OperationAborted, - IoPending, - BrokenPipe, - WouldBlock, - FileClosed, - DestinationAddressRequired, - DiskQuota, - InputOutput, - NoStdHandles, - Overflow, - NotSupported, - BufferTooSmall, - Unimplemented, // TODO remove this one - SemanticAnalysisFailed, // TODO remove this one - ReadOnlyFileSystem, - LinkQuotaExceeded, - EnvironmentVariableNotFound, - AppDataDirUnavailable, - LinkFailed, - LibCRequiredButNotProvidedOrFound, - LibCMissingDynamicLinker, - InvalidDarwinVersionString, - UnsupportedLinkArchitecture, - UserResourceLimitReached, - InvalidUtf8, - BadPathName, - DeviceBusy, - CurrentWorkingDirectoryUnlinked, - }; - - pub const Event = union(enum) { - Ok, - Error: BuildError, - Fail: []*Msg, - }; - - pub const DarwinVersionMin = union(enum) { - None, - MacOS: []const u8, - Ios: []const u8, - }; - - pub const Kind = enum { - Exe, - Lib, - Obj, - }; - - pub const LinkLib = struct { - name: []const u8, - path: ?[]const u8, - - /// the list of symbols we depend on from this lib - symbols: ArrayList([]u8), - provided_explicitly: bool, - }; - - pub const Emit = enum { - Binary, - Assembly, - LlvmIr, - }; - - pub fn create( - zig_compiler: *ZigCompiler, - name: []const u8, - root_src_path: ?[]const u8, - target: std.zig.CrossTarget, - kind: Kind, - build_mode: builtin.Mode, - is_static: bool, - zig_lib_dir: []const u8, - ) !*Compilation { - var optional_comp: ?*Compilation = null; - var frame = try zig_compiler.allocator.create(@Frame(createAsync)); - errdefer zig_compiler.allocator.destroy(frame); - frame.* = async createAsync( - &optional_comp, - zig_compiler, - name, - root_src_path, - target, - kind, - build_mode, - is_static, - zig_lib_dir, - ); - // TODO causes segfault - // return optional_comp orelse if (await frame) |_| unreachable else |err| err; - if (optional_comp) |comp| { - return comp; - } else if (await frame) |_| unreachable else |err| return err; - } - fn createAsync( - out_comp: *?*Compilation, - zig_compiler: *ZigCompiler, - name: []const u8, - root_src_path: ?[]const u8, - cross_target: std.zig.CrossTarget, - kind: Kind, - build_mode: builtin.Mode, - is_static: bool, - zig_lib_dir: []const u8, - ) callconv(.Async) !void { - const allocator = zig_compiler.allocator; - - // TODO merge this line with stage2.zig crossTargetToTarget - const target_info = try std.zig.system.NativeTargetInfo.detect(std.heap.c_allocator, cross_target); - const target = target_info.target; - - var comp = Compilation{ - .arena_allocator = std.heap.ArenaAllocator.init(allocator), - .zig_compiler = zig_compiler, - .events = undefined, - .root_src_path = root_src_path, - .target = target, - .llvm_target = undefined, - .kind = kind, - .build_mode = build_mode, - .zig_lib_dir = zig_lib_dir, - .zig_std_dir = undefined, - .destroy_frame = @frame(), - .main_loop_frame = undefined, - - .name = undefined, - .llvm_triple = undefined, - .is_static = is_static, - .link_libs_list = undefined, - .exported_symbol_names = event.Locked(Decl.Table).init(Decl.Table.init(allocator)), - .prelink_group = event.Group(BuildError!void).init(allocator), - .deinit_group = event.Group(void).init(allocator), - .compile_errors = event.Locked(CompileErrList).init(CompileErrList.init(allocator)), - .int_type_table = event.Locked(IntTypeTable).init(IntTypeTable.init(allocator)), - .array_type_table = event.Locked(ArrayTypeTable).init(ArrayTypeTable.init(allocator)), - .ptr_type_table = event.Locked(PtrTypeTable).init(PtrTypeTable.init(allocator)), - .fn_type_table = event.Locked(FnTypeTable).init(FnTypeTable.init(allocator)), - .c_int_types = undefined, - - .meta_type = undefined, - .void_type = undefined, - .void_value = undefined, - .bool_type = undefined, - .true_value = undefined, - .false_value = undefined, - .noreturn_type = undefined, - .noreturn_value = undefined, - .comptime_int_type = undefined, - .u8_type = undefined, - - .target_machine = undefined, - .target_data_ref = undefined, - .target_layout_str = undefined, - .target_ptr_bits = target.cpu.arch.ptrBitWidth(), - - .root_package = undefined, - .std_package = undefined, - - .primitive_type_table = undefined, - - .fs_watch = undefined, - }; - comp.link_libs_list = ArrayList(*LinkLib).init(comp.arena()); - comp.primitive_type_table = TypeTable.init(comp.arena()); - - defer { - comp.int_type_table.private_data.deinit(); - comp.array_type_table.private_data.deinit(); - comp.ptr_type_table.private_data.deinit(); - comp.fn_type_table.private_data.deinit(); - comp.arena_allocator.deinit(); - } - - comp.name = try ArrayListSentineled(u8, 0).init(comp.arena(), name); - comp.llvm_triple = try getLLVMTriple(comp.arena(), target); - comp.llvm_target = try llvmTargetFromTriple(comp.llvm_triple); - comp.zig_std_dir = try fs.path.join(comp.arena(), &[_][]const u8{ zig_lib_dir, "std" }); - - const opt_level = switch (build_mode) { - .Debug => llvm.CodeGenLevelNone, - else => llvm.CodeGenLevelAggressive, - }; - - const reloc_mode = if (is_static) llvm.RelocStatic else llvm.RelocPIC; - - var target_specific_cpu_args: ?[*:0]u8 = null; - var target_specific_cpu_features: ?[*:0]u8 = null; - defer llvm.DisposeMessage(target_specific_cpu_args); - defer llvm.DisposeMessage(target_specific_cpu_features); - - // TODO detect native CPU & features here - - comp.target_machine = llvm.CreateTargetMachine( - comp.llvm_target, - comp.llvm_triple.span(), - target_specific_cpu_args orelse "", - target_specific_cpu_features orelse "", - opt_level, - reloc_mode, - llvm.CodeModelDefault, - false, // TODO: add -ffunction-sections option - ) orelse return error.OutOfMemory; - defer llvm.DisposeTargetMachine(comp.target_machine); - - comp.target_data_ref = llvm.CreateTargetDataLayout(comp.target_machine) orelse return error.OutOfMemory; - defer llvm.DisposeTargetData(comp.target_data_ref); - - comp.target_layout_str = llvm.CopyStringRepOfTargetData(comp.target_data_ref) orelse return error.OutOfMemory; - defer llvm.DisposeMessage(comp.target_layout_str); - - comp.events = try allocator.create(event.Channel(Event)); - defer allocator.destroy(comp.events); - - comp.events.init(&[0]Event{}); - defer comp.events.deinit(); - - if (root_src_path) |root_src| { - const dirname = fs.path.dirname(root_src) orelse "."; - const basename = fs.path.basename(root_src); - - comp.root_package = try Package.create(comp.arena(), dirname, basename); - comp.std_package = try Package.create(comp.arena(), comp.zig_std_dir, "std.zig"); - try comp.root_package.add("std", comp.std_package); - } else { - comp.root_package = try Package.create(comp.arena(), ".", ""); - } - - comp.fs_watch = try fs.Watch(*Scope.Root).init(allocator, 16); - defer comp.fs_watch.deinit(); - - try comp.initTypes(); - defer comp.primitive_type_table.deinit(); - - comp.main_loop_frame = try allocator.create(@Frame(mainLoop)); - defer allocator.destroy(comp.main_loop_frame); - - comp.main_loop_frame.* = async comp.mainLoop(); - // Set this to indicate that initialization completed successfully. - // from here on out we must not return an error. - // This must occur before the first suspend/await. - out_comp.* = ∁ - // This suspend is resumed by destroy() - suspend; - // From here on is cleanup. - - comp.deinit_group.wait(); - - if (comp.tmp_dir.getOrNull()) |tmp_dir_result| - if (tmp_dir_result.*) |tmp_dir| { - fs.cwd().deleteTree(tmp_dir) catch {}; - } else |_| {}; - } - - /// it does ref the result because it could be an arbitrary integer size - pub fn getPrimitiveType(comp: *Compilation, name: []const u8) !?*Type { - if (name.len >= 2) { - switch (name[0]) { - 'i', 'u' => blk: { - for (name[1..]) |byte| - switch (byte) { - '0'...'9' => {}, - else => break :blk, - }; - const is_signed = name[0] == 'i'; - const bit_count = std.fmt.parseUnsigned(u32, name[1..], 10) catch |err| switch (err) { - error.Overflow => return error.Overflow, - error.InvalidCharacter => unreachable, // we just checked the characters above - }; - const int_type = try Type.Int.get(comp, Type.Int.Key{ - .bit_count = bit_count, - .is_signed = is_signed, - }); - errdefer int_type.base.base.deref(); - return &int_type.base; - }, - else => {}, - } - } - - if (comp.primitive_type_table.get(name)) |entry| { - entry.value.base.ref(); - return entry.value; - } - - return null; - } - - fn initTypes(comp: *Compilation) !void { - comp.meta_type = try comp.arena().create(Type.MetaType); - comp.meta_type.* = Type.MetaType{ - .base = Type{ - .name = "type", - .base = Value{ - .id = .Type, - .typ = undefined, - .ref_count = std.atomic.Int(usize).init(3), // 3 because it references itself twice - }, - .id = .Type, - .abi_alignment = Type.AbiAlignment.init(), - }, - .value = undefined, - }; - comp.meta_type.value = &comp.meta_type.base; - comp.meta_type.base.base.typ = &comp.meta_type.base; - assert((try comp.primitive_type_table.put(comp.meta_type.base.name, &comp.meta_type.base)) == null); - - comp.void_type = try comp.arena().create(Type.Void); - comp.void_type.* = Type.Void{ - .base = Type{ - .name = "void", - .base = Value{ - .id = .Type, - .typ = &Type.MetaType.get(comp).base, - .ref_count = std.atomic.Int(usize).init(1), - }, - .id = .Void, - .abi_alignment = Type.AbiAlignment.init(), - }, - }; - assert((try comp.primitive_type_table.put(comp.void_type.base.name, &comp.void_type.base)) == null); - - comp.noreturn_type = try comp.arena().create(Type.NoReturn); - comp.noreturn_type.* = Type.NoReturn{ - .base = Type{ - .name = "noreturn", - .base = Value{ - .id = .Type, - .typ = &Type.MetaType.get(comp).base, - .ref_count = std.atomic.Int(usize).init(1), - }, - .id = .NoReturn, - .abi_alignment = Type.AbiAlignment.init(), - }, - }; - assert((try comp.primitive_type_table.put(comp.noreturn_type.base.name, &comp.noreturn_type.base)) == null); - - comp.comptime_int_type = try comp.arena().create(Type.ComptimeInt); - comp.comptime_int_type.* = Type.ComptimeInt{ - .base = Type{ - .name = "comptime_int", - .base = Value{ - .id = .Type, - .typ = &Type.MetaType.get(comp).base, - .ref_count = std.atomic.Int(usize).init(1), - }, - .id = .ComptimeInt, - .abi_alignment = Type.AbiAlignment.init(), - }, - }; - assert((try comp.primitive_type_table.put(comp.comptime_int_type.base.name, &comp.comptime_int_type.base)) == null); - - comp.bool_type = try comp.arena().create(Type.Bool); - comp.bool_type.* = Type.Bool{ - .base = Type{ - .name = "bool", - .base = Value{ - .id = .Type, - .typ = &Type.MetaType.get(comp).base, - .ref_count = std.atomic.Int(usize).init(1), - }, - .id = .Bool, - .abi_alignment = Type.AbiAlignment.init(), - }, - }; - assert((try comp.primitive_type_table.put(comp.bool_type.base.name, &comp.bool_type.base)) == null); - - comp.void_value = try comp.arena().create(Value.Void); - comp.void_value.* = Value.Void{ - .base = Value{ - .id = .Void, - .typ = &Type.Void.get(comp).base, - .ref_count = std.atomic.Int(usize).init(1), - }, - }; - - comp.true_value = try comp.arena().create(Value.Bool); - comp.true_value.* = Value.Bool{ - .base = Value{ - .id = .Bool, - .typ = &Type.Bool.get(comp).base, - .ref_count = std.atomic.Int(usize).init(1), - }, - .x = true, - }; - - comp.false_value = try comp.arena().create(Value.Bool); - comp.false_value.* = Value.Bool{ - .base = Value{ - .id = .Bool, - .typ = &Type.Bool.get(comp).base, - .ref_count = std.atomic.Int(usize).init(1), - }, - .x = false, - }; - - comp.noreturn_value = try comp.arena().create(Value.NoReturn); - comp.noreturn_value.* = Value.NoReturn{ - .base = Value{ - .id = .NoReturn, - .typ = &Type.NoReturn.get(comp).base, - .ref_count = std.atomic.Int(usize).init(1), - }, - }; - - for (CInt.list) |cint, i| { - const c_int_type = try comp.arena().create(Type.Int); - c_int_type.* = Type.Int{ - .base = Type{ - .name = cint.zig_name, - .base = Value{ - .id = .Type, - .typ = &Type.MetaType.get(comp).base, - .ref_count = std.atomic.Int(usize).init(1), - }, - .id = .Int, - .abi_alignment = Type.AbiAlignment.init(), - }, - .key = Type.Int.Key{ - .is_signed = cint.is_signed, - .bit_count = cint.sizeInBits(comp.target), - }, - .garbage_node = undefined, - }; - comp.c_int_types[i] = c_int_type; - assert((try comp.primitive_type_table.put(cint.zig_name, &c_int_type.base)) == null); - } - comp.u8_type = try comp.arena().create(Type.Int); - comp.u8_type.* = Type.Int{ - .base = Type{ - .name = "u8", - .base = Value{ - .id = .Type, - .typ = &Type.MetaType.get(comp).base, - .ref_count = std.atomic.Int(usize).init(1), - }, - .id = .Int, - .abi_alignment = Type.AbiAlignment.init(), - }, - .key = Type.Int.Key{ - .is_signed = false, - .bit_count = 8, - }, - .garbage_node = undefined, - }; - assert((try comp.primitive_type_table.put(comp.u8_type.base.name, &comp.u8_type.base)) == null); - } - - pub fn destroy(self: *Compilation) void { - const allocator = self.gpa(); - self.cancelled = true; - await self.main_loop_frame; - resume self.destroy_frame; - allocator.destroy(self.destroy_frame); - } - - fn start(self: *Compilation) void { - self.main_loop_future.resolve(); - } - fn mainLoop(self: *Compilation) callconv(.Async) void { - // wait until start() is called - _ = self.main_loop_future.get(); - - var build_result = self.initialCompile(); - - while (!self.cancelled) { - const link_result = if (build_result) blk: { - break :blk self.maybeLink(); - } else |err| err; - // this makes a handy error return trace and stack trace in debug mode - if (std.debug.runtime_safety) { - link_result catch unreachable; - } - - const compile_errors = blk: { - const held = self.compile_errors.acquire(); - defer held.release(); - break :blk held.value.toOwnedSlice(); - }; - - if (link_result) |_| { - if (compile_errors.len == 0) { - self.events.put(Event.Ok); - } else { - self.events.put(Event{ .Fail = compile_errors }); - } - } else |err| { - // if there's an error then the compile errors have dangling references - self.gpa().free(compile_errors); - - self.events.put(Event{ .Error = err }); - } - - // First, get an item from the watch channel, waiting on the channel. - var group = event.Group(BuildError!void).init(self.gpa()); - { - const ev = (self.fs_watch.channel.get()) catch |err| { - build_result = err; - continue; - }; - const root_scope = ev.data; - group.call(rebuildFile, .{ self, root_scope }) catch |err| { - build_result = err; - continue; - }; - } - // Next, get all the items from the channel that are buffered up. - while (self.fs_watch.channel.getOrNull()) |ev_or_err| { - if (ev_or_err) |ev| { - const root_scope = ev.data; - group.call(rebuildFile, .{ self, root_scope }) catch |err| { - build_result = err; - continue; - }; - } else |err| { - build_result = err; - continue; - } - } - build_result = group.wait(); - } - } - fn rebuildFile(self: *Compilation, root_scope: *Scope.Root) callconv(.Async) BuildError!void { - const tree_scope = blk: { - const source_code = fs.cwd().readFileAlloc( - self.gpa(), - root_scope.realpath, - max_src_size, - ) catch |err| { - try self.addCompileErrorCli(root_scope.realpath, "unable to open: {}", .{@errorName(err)}); - return; - }; - errdefer self.gpa().free(source_code); - - const tree = try std.zig.parse(self.gpa(), source_code); - errdefer { - tree.deinit(); - } - - break :blk try Scope.AstTree.create(self, tree, root_scope); - }; - defer tree_scope.base.deref(self); - - var error_it = tree_scope.tree.errors.iterator(0); - while (error_it.next()) |parse_error| { - const msg = try Msg.createFromParseErrorAndScope(self, tree_scope, parse_error); - errdefer msg.destroy(); - - try self.addCompileErrorAsync(msg); - } - if (tree_scope.tree.errors.len != 0) { - return; - } - - const locked_table = root_scope.decls.table.acquireWrite(); - defer locked_table.release(); - - var decl_group = event.Group(BuildError!void).init(self.gpa()); - - try self.rebuildChangedDecls( - &decl_group, - locked_table.value, - root_scope.decls, - &tree_scope.tree.root_node.decls, - tree_scope, - ); - - try decl_group.wait(); - } - - fn rebuildChangedDecls( - self: *Compilation, - group: *event.Group(BuildError!void), - locked_table: *Decl.Table, - decl_scope: *Scope.Decls, - ast_decls: *ast.Node.Root.DeclList, - tree_scope: *Scope.AstTree, - ) !void { - var existing_decls = try locked_table.clone(); - defer existing_decls.deinit(); - - var ast_it = ast_decls.iterator(0); - while (ast_it.next()) |decl_ptr| { - const decl = decl_ptr.*; - switch (decl.id) { - .Comptime => { - const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", decl); - - // TODO connect existing comptime decls to updated source files - - try self.prelink_group.call(addCompTimeBlock, .{ self, tree_scope, &decl_scope.base, comptime_node }); - }, - .VarDecl => @panic("TODO"), - .FnProto => { - const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl); - - const name = if (fn_proto.name_token) |name_token| tree_scope.tree.tokenSlice(name_token) else { - try self.addCompileError(tree_scope, Span{ - .first = fn_proto.fn_token, - .last = fn_proto.fn_token + 1, - }, "missing function name", .{}); - continue; - }; - - if (existing_decls.remove(name)) |entry| { - // compare new code to existing - if (entry.value.cast(Decl.Fn)) |existing_fn_decl| { - // Just compare the old bytes to the new bytes of the top level decl. - // Even if the AST is technically the same, we want error messages to display - // from the most recent source. - const old_decl_src = existing_fn_decl.base.tree_scope.tree.getNodeSource( - &existing_fn_decl.fn_proto.base, - ); - const new_decl_src = tree_scope.tree.getNodeSource(&fn_proto.base); - if (mem.eql(u8, old_decl_src, new_decl_src)) { - // it's the same, we can skip this decl - continue; - } else { - @panic("TODO decl changed implementation"); - // Add the new thing before dereferencing the old thing. This way we don't end - // up pointlessly re-creating things we end up using in the new thing. - } - } else { - @panic("TODO decl changed kind"); - } - } else { - // add new decl - const fn_decl = try self.gpa().create(Decl.Fn); - fn_decl.* = Decl.Fn{ - .base = Decl{ - .id = Decl.Id.Fn, - .name = name, - .visib = parseVisibToken(tree_scope.tree, fn_proto.visib_token), - .resolution = event.Future(BuildError!void).init(), - .parent_scope = &decl_scope.base, - .tree_scope = tree_scope, - }, - .value = .Unresolved, - .fn_proto = fn_proto, - }; - tree_scope.base.ref(); - errdefer self.gpa().destroy(fn_decl); - - try group.call(addTopLevelDecl, .{ self, &fn_decl.base, locked_table }); - } - }, - .TestDecl => @panic("TODO"), - else => unreachable, - } - } - - var existing_decl_it = existing_decls.iterator(); - while (existing_decl_it.next()) |entry| { - // this decl was deleted - const existing_decl = entry.value; - @panic("TODO handle decl deletion"); - } - } - - fn initialCompile(self: *Compilation) !void { - if (self.root_src_path) |root_src_path| { - const root_scope = blk: { - // TODO async/await fs.realpath - const root_src_real_path = fs.realpathAlloc(self.gpa(), root_src_path) catch |err| { - try self.addCompileErrorCli(root_src_path, "unable to open: {}", .{@errorName(err)}); - return; - }; - errdefer self.gpa().free(root_src_real_path); - - break :blk try Scope.Root.create(self, root_src_real_path); - }; - defer root_scope.base.deref(self); - - // assert((try self.fs_watch.addFile(root_scope.realpath, root_scope)) == null); - try self.rebuildFile(root_scope); - } - } - - fn maybeLink(self: *Compilation) !void { - (self.prelink_group.wait()) catch |err| switch (err) { - error.SemanticAnalysisFailed => {}, - else => return err, - }; - - const any_prelink_errors = blk: { - const compile_errors = self.compile_errors.acquire(); - defer compile_errors.release(); - - break :blk compile_errors.value.len != 0; - }; - - if (!any_prelink_errors) { - try link(self); - } - } - /// caller takes ownership of resulting Code - fn genAndAnalyzeCode( - comp: *Compilation, - tree_scope: *Scope.AstTree, - scope: *Scope, - node: *ast.Node, - expected_type: ?*Type, - ) callconv(.Async) !*ir.Code { - const unanalyzed_code = try ir.gen( - comp, - node, - tree_scope, - scope, - ); - defer unanalyzed_code.destroy(comp.gpa()); - - if (comp.verbose_ir) { - std.debug.warn("unanalyzed:\n", .{}); - unanalyzed_code.dump(); - } - - const analyzed_code = try ir.analyze( - comp, - unanalyzed_code, - expected_type, - ); - errdefer analyzed_code.destroy(comp.gpa()); - - if (comp.verbose_ir) { - std.debug.warn("analyzed:\n", .{}); - analyzed_code.dump(); - } - - return analyzed_code; - } - fn addCompTimeBlock( - comp: *Compilation, - tree_scope: *Scope.AstTree, - scope: *Scope, - comptime_node: *ast.Node.Comptime, - ) callconv(.Async) BuildError!void { - const void_type = Type.Void.get(comp); - defer void_type.base.base.deref(comp); - - const analyzed_code = genAndAnalyzeCode( - comp, - tree_scope, - scope, - comptime_node.expr, - &void_type.base, - ) catch |err| switch (err) { - // This poison value should not cause the errdefers to run. It simply means - // that comp.compile_errors is populated. - error.SemanticAnalysisFailed => return {}, - else => return err, - }; - analyzed_code.destroy(comp.gpa()); - } - fn addTopLevelDecl( - self: *Compilation, - decl: *Decl, - locked_table: *Decl.Table, - ) callconv(.Async) BuildError!void { - const is_export = decl.isExported(decl.tree_scope.tree); - - if (is_export) { - try self.prelink_group.call(verifyUniqueSymbol, .{ self, decl }); - try self.prelink_group.call(resolveDecl, .{ self, decl }); - } - - const gop = try locked_table.getOrPut(decl.name); - if (gop.found_existing) { - try self.addCompileError(decl.tree_scope, decl.getSpan(), "redefinition of '{}'", .{decl.name}); - // TODO note: other definition here - } else { - gop.kv.value = decl; - } - } - - fn addCompileError(self: *Compilation, tree_scope: *Scope.AstTree, span: Span, comptime fmt: []const u8, args: var) !void { - const text = try std.fmt.allocPrint(self.gpa(), fmt, args); - errdefer self.gpa().free(text); - - const msg = try Msg.createFromScope(self, tree_scope, span, text); - errdefer msg.destroy(); - - try self.prelink_group.call(addCompileErrorAsync, .{ self, msg }); - } - - fn addCompileErrorCli(self: *Compilation, realpath: []const u8, comptime fmt: []const u8, args: var) !void { - const text = try std.fmt.allocPrint(self.gpa(), fmt, args); - errdefer self.gpa().free(text); - - const msg = try Msg.createFromCli(self, realpath, text); - errdefer msg.destroy(); - - try self.prelink_group.call(addCompileErrorAsync, .{ self, msg }); - } - fn addCompileErrorAsync( - self: *Compilation, - msg: *Msg, - ) callconv(.Async) BuildError!void { - errdefer msg.destroy(); - - const compile_errors = self.compile_errors.acquire(); - defer compile_errors.release(); - - try compile_errors.value.append(msg); - } - fn verifyUniqueSymbol(self: *Compilation, decl: *Decl) callconv(.Async) BuildError!void { - const exported_symbol_names = self.exported_symbol_names.acquire(); - defer exported_symbol_names.release(); - - if (try exported_symbol_names.value.put(decl.name, decl)) |other_decl| { - try self.addCompileError(decl.tree_scope, decl.getSpan(), "exported symbol collision: '{}'", .{ - decl.name, - }); - // TODO add error note showing location of other symbol - } - } - - pub fn haveLibC(self: *Compilation) bool { - return self.libc_link_lib != null; - } - - pub fn addLinkLib(self: *Compilation, name: []const u8, provided_explicitly: bool) !*LinkLib { - const is_libc = mem.eql(u8, name, "c"); - - if (is_libc) { - if (self.libc_link_lib) |libc_link_lib| { - return libc_link_lib; - } - } - - for (self.link_libs_list.span()) |existing_lib| { - if (mem.eql(u8, name, existing_lib.name)) { - return existing_lib; - } - } - - const link_lib = try self.gpa().create(LinkLib); - link_lib.* = LinkLib{ - .name = name, - .path = null, - .provided_explicitly = provided_explicitly, - .symbols = ArrayList([]u8).init(self.gpa()), - }; - try self.link_libs_list.append(link_lib); - if (is_libc) { - self.libc_link_lib = link_lib; - - // get a head start on looking for the native libc - // TODO this is missing a bunch of logic related to whether the target is native - // and whether we can build libc - if (self.override_libc == null) { - try self.deinit_group.call(startFindingNativeLibC, .{self}); - } - } - return link_lib; - } - fn startFindingNativeLibC(self: *Compilation) callconv(.Async) void { - event.Loop.startCpuBoundOperation(); - // we don't care if it fails, we're just trying to kick off the future resolution - _ = self.zig_compiler.getNativeLibC() catch return; - } - - /// General Purpose Allocator. Must free when done. - fn gpa(self: Compilation) *mem.Allocator { - return self.zig_compiler.allocator; - } - - /// Arena Allocator. Automatically freed when the Compilation is destroyed. - fn arena(self: *Compilation) *mem.Allocator { - return &self.arena_allocator.allocator; - } - - /// If the temporary directory for this compilation has not been created, it creates it. - /// Then it creates a random file name in that dir and returns it. - pub fn createRandomOutputPath(self: *Compilation, suffix: []const u8) !ArrayListSentineled(u8, 0) { - const tmp_dir = try self.getTmpDir(); - const file_prefix = self.getRandomFileName(); - - const file_name = try std.fmt.allocPrint(self.gpa(), "{}{}", .{ file_prefix[0..], suffix }); - defer self.gpa().free(file_name); - - const full_path = try fs.path.join(self.gpa(), &[_][]const u8{ tmp_dir, file_name[0..] }); - errdefer self.gpa().free(full_path); - - return ArrayListSentineled(u8, 0).fromOwnedSlice(self.gpa(), full_path); - } - - /// If the temporary directory for this Compilation has not been created, creates it. - /// Then returns it. The directory is unique to this Compilation and cleaned up when - /// the Compilation deinitializes. - fn getTmpDir(self: *Compilation) ![]const u8 { - if (self.tmp_dir.start()) |ptr| return ptr.*; - self.tmp_dir.data = self.getTmpDirImpl(); - self.tmp_dir.resolve(); - return self.tmp_dir.data; - } - - fn getTmpDirImpl(self: *Compilation) ![]u8 { - const comp_dir_name = self.getRandomFileName(); - const zig_dir_path = try getZigDir(self.gpa()); - defer self.gpa().free(zig_dir_path); - - const tmp_dir = try fs.path.join(self.arena(), &[_][]const u8{ zig_dir_path, comp_dir_name[0..] }); - try fs.cwd().makePath(tmp_dir); - return tmp_dir; - } - - fn getRandomFileName(self: *Compilation) [12]u8 { - // here we replace the standard +/ with -_ so that it can be used in a file name - const b64_fs_encoder = std.base64.Base64Encoder.init( - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", - std.base64.standard_pad_char, - ); - - var rand_bytes: [9]u8 = undefined; - - { - const held = self.zig_compiler.prng.acquire(); - defer held.release(); - - held.value.random.bytes(rand_bytes[0..]); - } - - var result: [12]u8 = undefined; - b64_fs_encoder.encode(result[0..], &rand_bytes); - return result; - } - - fn registerGarbage(comp: *Compilation, comptime T: type, node: *std.atomic.Stack(*T).Node) void { - // TODO put the garbage somewhere - } - - /// Returns a value which has been ref()'d once - fn analyzeConstValue( - comp: *Compilation, - tree_scope: *Scope.AstTree, - scope: *Scope, - node: *ast.Node, - expected_type: *Type, - ) !*Value { - var frame = try comp.gpa().create(@Frame(genAndAnalyzeCode)); - defer comp.gpa().destroy(frame); - frame.* = async comp.genAndAnalyzeCode(tree_scope, scope, node, expected_type); - const analyzed_code = try await frame; - defer analyzed_code.destroy(comp.gpa()); - - return analyzed_code.getCompTimeResult(comp); - } - - fn analyzeTypeExpr(comp: *Compilation, tree_scope: *Scope.AstTree, scope: *Scope, node: *ast.Node) !*Type { - const meta_type = &Type.MetaType.get(comp).base; - defer meta_type.base.deref(comp); - - const result_val = try comp.analyzeConstValue(tree_scope, scope, node, meta_type); - errdefer result_val.base.deref(comp); - - return result_val.cast(Type).?; - } - - /// This declaration has been blessed as going into the final code generation. - pub fn resolveDecl(comp: *Compilation, decl: *Decl) callconv(.Async) BuildError!void { - if (decl.resolution.start()) |ptr| return ptr.*; - - decl.resolution.data = try generateDecl(comp, decl); - decl.resolution.resolve(); - return decl.resolution.data; - } -}; - -fn parseVisibToken(tree: *ast.Tree, optional_token_index: ?ast.TokenIndex) Visib { - if (optional_token_index) |token_index| { - const token = tree.tokens.at(token_index); - assert(token.id == Token.Id.Keyword_pub); - return Visib.Pub; - } else { - return Visib.Private; - } -} - -/// The function that actually does the generation. -fn generateDecl(comp: *Compilation, decl: *Decl) !void { - switch (decl.id) { - .Var => @panic("TODO"), - .Fn => { - const fn_decl = @fieldParentPtr(Decl.Fn, "base", decl); - return generateDeclFn(comp, fn_decl); - }, - .CompTime => @panic("TODO"), - } -} - -fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { - const tree_scope = fn_decl.base.tree_scope; - - const body_node = fn_decl.fn_proto.body_node orelse return generateDeclFnProto(comp, fn_decl); - - const fndef_scope = try Scope.FnDef.create(comp, fn_decl.base.parent_scope); - defer fndef_scope.base.deref(comp); - - const fn_type = try analyzeFnType(comp, tree_scope, fn_decl.base.parent_scope, fn_decl.fn_proto); - defer fn_type.base.base.deref(comp); - - var symbol_name = try std.ArrayListSentineled(u8, 0).init(comp.gpa(), fn_decl.base.name); - var symbol_name_consumed = false; - errdefer if (!symbol_name_consumed) symbol_name.deinit(); - - // The Decl.Fn owns the initial 1 reference count - const fn_val = try Value.Fn.create(comp, fn_type, fndef_scope, symbol_name); - fn_decl.value = .{ .Fn = fn_val }; - symbol_name_consumed = true; - - // Define local parameter variables - for (fn_type.key.data.Normal.params) |param, i| { - //AstNode *param_decl_node = get_param_decl_node(fn_table_entry, i); - const param_decl = @fieldParentPtr(ast.Node.ParamDecl, "base", fn_decl.fn_proto.params.at(i).*); - const name_token = param_decl.name_token orelse { - try comp.addCompileError(tree_scope, Span{ - .first = param_decl.firstToken(), - .last = param_decl.type_node.firstToken(), - }, "missing parameter name", .{}); - return error.SemanticAnalysisFailed; - }; - const param_name = tree_scope.tree.tokenSlice(name_token); - - // if (is_noalias && get_codegen_ptr_type(param_type) == nullptr) { - // add_node_error(g, param_decl_node, buf_sprintf("noalias on non-pointer parameter")); - // } - - // TODO check for shadowing - - const var_scope = try Scope.Var.createParam( - comp, - fn_val.child_scope, - param_name, - ¶m_decl.base, - i, - param.typ, - ); - fn_val.child_scope = &var_scope.base; - - try fn_type.non_key.Normal.variable_list.append(var_scope); - } - - var frame = try comp.gpa().create(@Frame(Compilation.genAndAnalyzeCode)); - defer comp.gpa().destroy(frame); - frame.* = async comp.genAndAnalyzeCode( - tree_scope, - fn_val.child_scope, - body_node, - fn_type.key.data.Normal.return_type, - ); - const analyzed_code = try await frame; - errdefer analyzed_code.destroy(comp.gpa()); - - assert(fn_val.block_scope != null); - - // Kick off rendering to LLVM module, but it doesn't block the fn decl - // analysis from being complete. - try comp.prelink_group.call(codegen.renderToLlvm, .{ comp, fn_val, analyzed_code }); - try comp.prelink_group.call(addFnToLinkSet, .{ comp, fn_val }); -} -fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) callconv(.Async) Compilation.BuildError!void { - fn_val.base.ref(); - defer fn_val.base.deref(comp); - - fn_val.link_set_node.data = fn_val; - - const held = comp.fn_link_set.acquire(); - defer held.release(); - - held.value.append(fn_val.link_set_node); -} - -fn getZigDir(allocator: *mem.Allocator) ![]u8 { - return fs.getAppDataDir(allocator, "zig"); -} - -fn analyzeFnType( - comp: *Compilation, - tree_scope: *Scope.AstTree, - scope: *Scope, - fn_proto: *ast.Node.FnProto, -) !*Type.Fn { - const return_type_node = switch (fn_proto.return_type) { - .Explicit => |n| n, - .InferErrorSet => |n| n, - }; - const return_type = try comp.analyzeTypeExpr(tree_scope, scope, return_type_node); - return_type.base.deref(comp); - - var params = ArrayList(Type.Fn.Param).init(comp.gpa()); - var params_consumed = false; - defer if (!params_consumed) { - for (params.span()) |param| { - param.typ.base.deref(comp); - } - params.deinit(); - }; - - { - var it = fn_proto.params.iterator(0); - while (it.next()) |param_node_ptr| { - const param_node = param_node_ptr.*.cast(ast.Node.ParamDecl).?; - const param_type = try comp.analyzeTypeExpr(tree_scope, scope, param_node.type_node); - errdefer param_type.base.deref(comp); - try params.append(Type.Fn.Param{ - .typ = param_type, - .is_noalias = param_node.noalias_token != null, - }); - } - } - - const key = Type.Fn.Key{ - .alignment = null, - .data = Type.Fn.Key.Data{ - .Normal = Type.Fn.Key.Normal{ - .return_type = return_type, - .params = params.toOwnedSlice(), - .is_var_args = false, // TODO - .cc = .Unspecified, // TODO - }, - }, - }; - params_consumed = true; - var key_consumed = false; - defer if (!key_consumed) { - for (key.data.Normal.params) |param| { - param.typ.base.deref(comp); - } - comp.gpa().free(key.data.Normal.params); - }; - - const fn_type = try Type.Fn.get(comp, key); - key_consumed = true; - errdefer fn_type.base.base.deref(comp); - - return fn_type; -} - -fn generateDeclFnProto(comp: *Compilation, fn_decl: *Decl.Fn) !void { - const fn_type = try analyzeFnType( - comp, - fn_decl.base.tree_scope, - fn_decl.base.parent_scope, - fn_decl.fn_proto, - ); - defer fn_type.base.base.deref(comp); - - var symbol_name = try std.ArrayListSentineled(u8, 0).init(comp.gpa(), fn_decl.base.name); - var symbol_name_consumed = false; - defer if (!symbol_name_consumed) symbol_name.deinit(); - - // The Decl.Fn owns the initial 1 reference count - const fn_proto_val = try Value.FnProto.create(comp, fn_type, symbol_name); - fn_decl.value = .{ .FnProto = fn_proto_val }; - symbol_name_consumed = true; -} - -pub fn llvmTargetFromTriple(triple: [:0]const u8) !*llvm.Target { - var result: *llvm.Target = undefined; - var err_msg: [*:0]u8 = undefined; - if (llvm.GetTargetFromTriple(triple, &result, &err_msg) != 0) { - std.debug.warn("triple: {s} error: {s}\n", .{ triple, err_msg }); - return error.UnsupportedTarget; - } - return result; -} - -pub fn initializeAllTargets() void { - llvm.InitializeAllTargets(); - llvm.InitializeAllTargetInfos(); - llvm.InitializeAllTargetMCs(); - llvm.InitializeAllAsmPrinters(); - llvm.InitializeAllAsmParsers(); -} - -pub fn getLLVMTriple(allocator: *std.mem.Allocator, target: std.Target) ![:0]u8 { - var result = try std.ArrayListSentineled(u8, 0).initSize(allocator, 0); - defer result.deinit(); - - try result.outStream().print( - "{}-unknown-{}-{}", - .{ @tagName(target.cpu.arch), @tagName(target.os.tag), @tagName(target.abi) }, - ); - - return result.toOwnedSlice(); -} diff --git a/src-self-hosted/errmsg.zig b/src-self-hosted/errmsg.zig deleted file mode 100644 index 5775c1df83..0000000000 --- a/src-self-hosted/errmsg.zig +++ /dev/null @@ -1,284 +0,0 @@ -const std = @import("std"); -const mem = std.mem; -const fs = std.fs; -const process = std.process; -const Token = std.zig.Token; -const ast = std.zig.ast; -const TokenIndex = std.zig.ast.TokenIndex; -const Compilation = @import("compilation.zig").Compilation; -const Scope = @import("scope.zig").Scope; - -pub const Color = enum { - Auto, - Off, - On, -}; - -pub const Span = struct { - first: ast.TokenIndex, - last: ast.TokenIndex, - - pub fn token(i: TokenIndex) Span { - return Span{ - .first = i, - .last = i, - }; - } - - pub fn node(n: *ast.Node) Span { - return Span{ - .first = n.firstToken(), - .last = n.lastToken(), - }; - } -}; - -pub const Msg = struct { - text: []u8, - realpath: []u8, - data: Data, - - const Data = union(enum) { - Cli: Cli, - PathAndTree: PathAndTree, - ScopeAndComp: ScopeAndComp, - }; - - const PathAndTree = struct { - span: Span, - tree: *ast.Tree, - allocator: *mem.Allocator, - }; - - const ScopeAndComp = struct { - span: Span, - tree_scope: *Scope.AstTree, - compilation: *Compilation, - }; - - const Cli = struct { - allocator: *mem.Allocator, - }; - - pub fn destroy(self: *Msg) void { - switch (self.data) { - .Cli => |cli| { - cli.allocator.free(self.text); - cli.allocator.free(self.realpath); - cli.allocator.destroy(self); - }, - .PathAndTree => |path_and_tree| { - path_and_tree.allocator.free(self.text); - path_and_tree.allocator.free(self.realpath); - path_and_tree.allocator.destroy(self); - }, - .ScopeAndComp => |scope_and_comp| { - scope_and_comp.tree_scope.base.deref(scope_and_comp.compilation); - scope_and_comp.compilation.gpa().free(self.text); - scope_and_comp.compilation.gpa().free(self.realpath); - scope_and_comp.compilation.gpa().destroy(self); - }, - } - } - - fn getAllocator(self: *const Msg) *mem.Allocator { - switch (self.data) { - .Cli => |cli| return cli.allocator, - .PathAndTree => |path_and_tree| { - return path_and_tree.allocator; - }, - .ScopeAndComp => |scope_and_comp| { - return scope_and_comp.compilation.gpa(); - }, - } - } - - pub fn getTree(self: *const Msg) *ast.Tree { - switch (self.data) { - .Cli => unreachable, - .PathAndTree => |path_and_tree| { - return path_and_tree.tree; - }, - .ScopeAndComp => |scope_and_comp| { - return scope_and_comp.tree_scope.tree; - }, - } - } - - pub fn getSpan(self: *const Msg) Span { - return switch (self.data) { - .Cli => unreachable, - .PathAndTree => |path_and_tree| path_and_tree.span, - .ScopeAndComp => |scope_and_comp| scope_and_comp.span, - }; - } - - /// Takes ownership of text - /// References tree_scope, and derefs when the msg is freed - pub fn createFromScope(comp: *Compilation, tree_scope: *Scope.AstTree, span: Span, text: []u8) !*Msg { - const realpath = try mem.dupe(comp.gpa(), u8, tree_scope.root().realpath); - errdefer comp.gpa().free(realpath); - - const msg = try comp.gpa().create(Msg); - msg.* = Msg{ - .text = text, - .realpath = realpath, - .data = Data{ - .ScopeAndComp = ScopeAndComp{ - .tree_scope = tree_scope, - .compilation = comp, - .span = span, - }, - }, - }; - tree_scope.base.ref(); - return msg; - } - - /// Caller owns returned Msg and must free with `allocator` - /// allocator will additionally be used for printing messages later. - pub fn createFromCli(comp: *Compilation, realpath: []const u8, text: []u8) !*Msg { - const realpath_copy = try mem.dupe(comp.gpa(), u8, realpath); - errdefer comp.gpa().free(realpath_copy); - - const msg = try comp.gpa().create(Msg); - msg.* = Msg{ - .text = text, - .realpath = realpath_copy, - .data = Data{ - .Cli = Cli{ .allocator = comp.gpa() }, - }, - }; - return msg; - } - - pub fn createFromParseErrorAndScope( - comp: *Compilation, - tree_scope: *Scope.AstTree, - parse_error: *const ast.Error, - ) !*Msg { - const loc_token = parse_error.loc(); - var text_buf = std.ArrayList(u8).init(comp.gpa()); - defer text_buf.deinit(); - - const realpath_copy = try mem.dupe(comp.gpa(), u8, tree_scope.root().realpath); - errdefer comp.gpa().free(realpath_copy); - - try parse_error.render(&tree_scope.tree.tokens, text_buf.outStream()); - - const msg = try comp.gpa().create(Msg); - msg.* = Msg{ - .text = undefined, - .realpath = realpath_copy, - .data = Data{ - .ScopeAndComp = ScopeAndComp{ - .tree_scope = tree_scope, - .compilation = comp, - .span = Span{ - .first = loc_token, - .last = loc_token, - }, - }, - }, - }; - tree_scope.base.ref(); - msg.text = text_buf.toOwnedSlice(); - return msg; - } - - /// `realpath` must outlive the returned Msg - /// `tree` must outlive the returned Msg - /// Caller owns returned Msg and must free with `allocator` - /// allocator will additionally be used for printing messages later. - pub fn createFromParseError( - allocator: *mem.Allocator, - parse_error: *const ast.Error, - tree: *ast.Tree, - realpath: []const u8, - ) !*Msg { - const loc_token = parse_error.loc(); - var text_buf = std.ArrayList(u8).init(allocator); - defer text_buf.deinit(); - - const realpath_copy = try mem.dupe(allocator, u8, realpath); - errdefer allocator.free(realpath_copy); - - try parse_error.render(&tree.tokens, text_buf.outStream()); - - const msg = try allocator.create(Msg); - msg.* = Msg{ - .text = undefined, - .realpath = realpath_copy, - .data = Data{ - .PathAndTree = PathAndTree{ - .allocator = allocator, - .tree = tree, - .span = Span{ - .first = loc_token, - .last = loc_token, - }, - }, - }, - }; - msg.text = text_buf.toOwnedSlice(); - errdefer allocator.destroy(msg); - - return msg; - } - - pub fn printToStream(msg: *const Msg, stream: var, color_on: bool) !void { - switch (msg.data) { - .Cli => { - try stream.print("{}:-:-: error: {}\n", .{ msg.realpath, msg.text }); - return; - }, - else => {}, - } - - const allocator = msg.getAllocator(); - const tree = msg.getTree(); - - const cwd = try process.getCwdAlloc(allocator); - defer allocator.free(cwd); - - const relpath = try fs.path.relative(allocator, cwd, msg.realpath); - defer allocator.free(relpath); - - const path = if (relpath.len < msg.realpath.len) relpath else msg.realpath; - const span = msg.getSpan(); - - const first_token = tree.tokens.at(span.first); - const last_token = tree.tokens.at(span.last); - const start_loc = tree.tokenLocationPtr(0, first_token); - const end_loc = tree.tokenLocationPtr(first_token.end, last_token); - if (!color_on) { - try stream.print("{}:{}:{}: error: {}\n", .{ - path, - start_loc.line + 1, - start_loc.column + 1, - msg.text, - }); - return; - } - - try stream.print("{}:{}:{}: error: {}\n{}\n", .{ - path, - start_loc.line + 1, - start_loc.column + 1, - msg.text, - tree.source[start_loc.line_start..start_loc.line_end], - }); - try stream.writeByteNTimes(' ', start_loc.column); - try stream.writeByteNTimes('~', last_token.end - first_token.start); - try stream.writeAll("\n"); - } - - pub fn printToFile(msg: *const Msg, file: fs.File, color: Color) !void { - const color_on = switch (color) { - .Auto => file.isTty(), - .On => true, - .Off => false, - }; - return msg.printToStream(file.outStream(), color_on); - } -}; diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 92a5aa7fdf..cf2b65c719 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -922,7 +922,6 @@ pub const Module = struct { if (self.decl_table.get(hash)) |kv| { return kv.value; } else { - std.debug.warn("creating new decl for {}\n", .{old_inst.name}); const new_decl = blk: { try self.decl_table.ensureCapacity(self.decl_table.size + 1); const new_decl = try self.allocator.create(Decl); @@ -2161,101 +2160,3 @@ pub const ErrorMsg = struct { self.* = undefined; } }; - -pub fn main() anyerror!void { - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena.deinit(); - const allocator = if (std.builtin.link_libc) std.heap.c_allocator else &arena.allocator; - - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); - - const src_path = args[1]; - const bin_path = args[2]; - const debug_error_trace = false; - const output_zir = false; - const object_format: ?std.builtin.ObjectFormat = null; - - const native_info = try std.zig.system.NativeTargetInfo.detect(allocator, .{}); - - var bin_file = try link.openBinFilePath(allocator, std.fs.cwd(), bin_path, .{ - .target = native_info.target, - .output_mode = .Exe, - .link_mode = .Static, - .object_format = object_format orelse native_info.target.getObjectFormat(), - }); - defer bin_file.deinit(); - - var module = blk: { - const root_pkg = try Package.create(allocator, std.fs.cwd(), ".", src_path); - errdefer root_pkg.destroy(); - - const root_scope = try allocator.create(Module.Scope.ZIRModule); - errdefer allocator.destroy(root_scope); - root_scope.* = .{ - .sub_file_path = root_pkg.root_src_path, - .source = .{ .unloaded = {} }, - .contents = .{ .not_available = {} }, - .status = .never_loaded, - }; - - break :blk Module{ - .allocator = allocator, - .root_pkg = root_pkg, - .root_scope = root_scope, - .bin_file = &bin_file, - .optimize_mode = .Debug, - .decl_table = std.AutoHashMap(Module.Decl.Hash, *Module.Decl).init(allocator), - .decl_exports = std.AutoHashMap(*Module.Decl, []*Module.Export).init(allocator), - .export_owners = std.AutoHashMap(*Module.Decl, []*Module.Export).init(allocator), - .failed_decls = std.AutoHashMap(*Module.Decl, *ErrorMsg).init(allocator), - .failed_files = std.AutoHashMap(*Module.Scope.ZIRModule, *ErrorMsg).init(allocator), - .failed_exports = std.AutoHashMap(*Module.Export, *ErrorMsg).init(allocator), - .work_queue = std.fifo.LinearFifo(Module.WorkItem, .Dynamic).init(allocator), - }; - }; - defer module.deinit(); - - const stdin = std.io.getStdIn().inStream(); - const stderr = std.io.getStdErr().outStream(); - var repl_buf: [1024]u8 = undefined; - - while (true) { - try module.update(); - - var errors = try module.getAllErrorsAlloc(); - defer errors.deinit(allocator); - - if (errors.list.len != 0) { - for (errors.list) |full_err_msg| { - std.debug.warn("{}:{}:{}: error: {}\n", .{ - full_err_msg.src_path, - full_err_msg.line + 1, - full_err_msg.column + 1, - full_err_msg.msg, - }); - } - if (debug_error_trace) return error.AnalysisFail; - } - - try stderr.print("🦎 ", .{}); - if (try stdin.readUntilDelimiterOrEof(&repl_buf, '\n')) |line| { - if (mem.eql(u8, line, "update")) { - continue; - } else { - try stderr.print("unknown command: {}\n", .{line}); - } - } else { - break; - } - } - - if (output_zir) { - var new_zir_module = try text.emit_zir(allocator, module); - defer new_zir_module.deinit(allocator); - - var bos = std.io.bufferedOutStream(std.io.getStdOut().outStream()); - try new_zir_module.writeToStream(allocator, bos.outStream()); - try bos.flush(); - } -} diff --git a/src-self-hosted/ir/text.zig b/src-self-hosted/ir/text.zig index f283fb5410..e0cc4e122c 100644 --- a/src-self-hosted/ir/text.zig +++ b/src-self-hosted/ir/text.zig @@ -20,7 +20,7 @@ pub const Inst = struct { name: []const u8, /// Slice into the source of the part after the = and before the next instruction. - contents: []const u8, + contents: []const u8 = &[0]u8{}, /// These names are used directly as the instruction names in the text format. pub const Tag = enum { @@ -825,7 +825,6 @@ const Parser = struct { .name = inst_name, .src = self.i, .tag = InstType.base_tag, - .contents = undefined, }; if (@hasField(InstType, "ty")) { @@ -960,7 +959,6 @@ const Parser = struct { .name = try self.generateName(), .src = src, .tag = Inst.Str.base_tag, - .contents = undefined, }, .positionals = .{ .bytes = ident }, .kw_args = .{}, @@ -971,7 +969,6 @@ const Parser = struct { .name = try self.generateName(), .src = src, .tag = Inst.DeclRef.base_tag, - .contents = undefined, }, .positionals = .{ .name = &name.base }, .kw_args = .{}, diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index b7535fca6f..9215e37e8c 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -1,29 +1,30 @@ const std = @import("std"); -const builtin = @import("builtin"); - -const event = std.event; -const os = std.os; const io = std.io; const fs = std.fs; const mem = std.mem; const process = std.process; const Allocator = mem.Allocator; const ArrayList = std.ArrayList; +const ast = std.zig.ast; +const ir = @import("ir.zig"); +const link = @import("link.zig"); +const Package = @import("Package.zig"); -const c = @import("c.zig"); -const introspect = @import("introspect.zig"); -const ZigCompiler = @import("compilation.zig").ZigCompiler; -const Compilation = @import("compilation.zig").Compilation; -const Target = std.Target; -const errmsg = @import("errmsg.zig"); const LibCInstallation = @import("libc_installation.zig").LibCInstallation; -pub const io_mode = .evented; +// TODO Improve async I/O enough that we feel comfortable doing this. +//pub const io_mode = .evented; pub const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB +pub const Color = enum { + Auto, + Off, + On, +}; + const usage = - \\usage: zig [command] [options] + \\Usage: zig [command] [options] \\ \\Commands: \\ @@ -39,175 +40,154 @@ const usage = \\ ; -const Command = struct { - name: []const u8, - exec: async fn (*Allocator, []const []const u8) anyerror!void, -}; - pub fn main() !void { - const allocator = std.heap.c_allocator; + // TODO general purpose allocator in the zig std lib + const gpa = if (std.builtin.link_libc) std.heap.c_allocator else std.heap.page_allocator; + var arena_instance = std.heap.ArenaAllocator.init(gpa); + defer arena_instance.deinit(); + const arena = &arena_instance.allocator; - const stderr = io.getStdErr().outStream(); - - const args = try process.argsAlloc(allocator); - defer process.argsFree(allocator, args); + const args = try process.argsAlloc(arena); if (args.len <= 1) { - try stderr.writeAll("expected command argument\n\n"); - try stderr.writeAll(usage); + std.debug.warn("expected command argument\n\n{}", .{usage}); process.exit(1); } const cmd = args[1]; const cmd_args = args[2..]; if (mem.eql(u8, cmd, "build-exe")) { - return buildOutputType(allocator, cmd_args, .Exe); + return buildOutputType(gpa, arena, cmd_args, .Exe); } else if (mem.eql(u8, cmd, "build-lib")) { - return buildOutputType(allocator, cmd_args, .Lib); + return buildOutputType(gpa, arena, cmd_args, .Lib); } else if (mem.eql(u8, cmd, "build-obj")) { - return buildOutputType(allocator, cmd_args, .Obj); + return buildOutputType(gpa, arena, cmd_args, .Obj); } else if (mem.eql(u8, cmd, "fmt")) { - return cmdFmt(allocator, cmd_args); + return cmdFmt(gpa, cmd_args); } else if (mem.eql(u8, cmd, "libc")) { - return cmdLibC(allocator, cmd_args); + return cmdLibC(gpa, cmd_args); } else if (mem.eql(u8, cmd, "targets")) { - const info = try std.zig.system.NativeTargetInfo.detect(allocator, .{}); + const info = try std.zig.system.NativeTargetInfo.detect(arena, .{}); const stdout = io.getStdOut().outStream(); - return @import("print_targets.zig").cmdTargets(allocator, cmd_args, stdout, info.target); + return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target); } else if (mem.eql(u8, cmd, "version")) { - return cmdVersion(allocator, cmd_args); + // Need to set up the build script to give the version as a comptime value. + std.debug.warn("TODO version command not implemented yet\n", .{}); + return error.Unimplemented; } else if (mem.eql(u8, cmd, "zen")) { - return cmdZen(allocator, cmd_args); + try io.getStdOut().writeAll(info_zen); } else if (mem.eql(u8, cmd, "help")) { - return cmdHelp(allocator, cmd_args); - } else if (mem.eql(u8, cmd, "internal")) { - return cmdInternal(allocator, cmd_args); + try io.getStdOut().writeAll(usage); } else { - try stderr.print("unknown command: {}\n\n", .{args[1]}); - try stderr.writeAll(usage); + std.debug.warn("unknown command: {}\n\n{}", .{ args[1], usage }); process.exit(1); } } const usage_build_generic = - \\usage: zig build-exe [file] - \\ zig build-lib [file] - \\ zig build-obj [file] + \\Usage: zig build-exe [files] + \\ zig build-lib [files] + \\ zig build-obj [files] + \\ + \\Supported file types: + \\ (planned) .zig Zig source code + \\ .zir Zig Intermediate Representation code + \\ (planned) .o ELF object file + \\ (planned) .o MACH-O (macOS) object file + \\ (planned) .obj COFF (Windows) object file + \\ (planned) .lib COFF (Windows) static library + \\ (planned) .a ELF static library + \\ (planned) .so ELF shared object (dynamic link) + \\ (planned) .dll Windows Dynamic Link Library + \\ (planned) .dylib MACH-O (macOS) dynamic library + \\ (planned) .s Target-specific assembly source code + \\ (planned) .S Assembly with C preprocessor (requires LLVM extensions) + \\ (planned) .c C source code (requires LLVM extensions) + \\ (planned) .cpp C++ source code (requires LLVM extensions) + \\ Other C++ extensions: .C .cc .cxx \\ \\General Options: - \\ --help Print this help and exit - \\ --color [auto|off|on] Enable or disable colored error messages + \\ -h, --help Print this help and exit + \\ --watch Enable compiler REPL + \\ --color [auto|off|on] Enable or disable colored error messages + \\ -femit-bin[=path] (default) output machine code + \\ -fno-emit-bin Do not output machine code \\ \\Compile Options: - \\ --libc [file] Provide a file which specifies libc paths - \\ --assembly [source] Add assembly file to build - \\ --emit [filetype] Emit a specific file format as compilation output - \\ --enable-timing-info Print timing diagnostics - \\ --name [name] Override output name - \\ --output [file] Override destination path - \\ --output-h [file] Override generated header file path - \\ --pkg-begin [name] [path] Make package available to import and push current pkg - \\ --pkg-end Pop current pkg - \\ --mode [mode] Set the build mode - \\ debug (default) optimizations off, safety on - \\ release-fast optimizations on, safety off - \\ release-safe optimizations on, safety on - \\ release-small optimize for small binary, safety off - \\ --static Output will be statically linked - \\ --strip Exclude debug symbols - \\ -target [name] -- see the targets command - \\ --eh-frame-hdr enable C++ exception handling by passing --eh-frame-hdr to linker - \\ --verbose-tokenize Turn on compiler debug output for tokenization - \\ --verbose-ast-tree Turn on compiler debug output for parsing into an AST (tree view) - \\ --verbose-ast-fmt Turn on compiler debug output for parsing into an AST (render source) - \\ --verbose-link Turn on compiler debug output for linking - \\ --verbose-ir Turn on compiler debug output for Zig IR - \\ --verbose-llvm-ir Turn on compiler debug output for LLVM IR - \\ --verbose-cimport Turn on compiler debug output for C imports - \\ -dirafter [dir] Same as -isystem but do it last - \\ -isystem [dir] Add additional search path for other .h files - \\ -mllvm [arg] Additional arguments to forward to LLVM's option processing + \\ -target [name] -- see the targets command + \\ -mcpu [cpu] Specify target CPU and feature set + \\ --name [name] Override output name + \\ --mode [mode] Set the build mode + \\ Debug (default) optimizations off, safety on + \\ ReleaseFast optimizations on, safety off + \\ ReleaseSafe optimizations on, safety on + \\ ReleaseSmall optimize for small binary, safety off + \\ --dynamic Force output to be dynamically linked + \\ --strip Exclude debug symbols \\ \\Link Options: - \\ --ar-path [path] Set the path to ar - \\ --each-lib-rpath Add rpath for each used dynamic library - \\ --library [lib] Link against lib - \\ --forbid-library [lib] Make it an error to link against lib - \\ --library-path [dir] Add a directory to the library search path - \\ --linker-script [path] Use a custom linker script - \\ --object [obj] Add object file to build - \\ -rdynamic Add all symbols to the dynamic symbol table - \\ -rpath [path] Add directory to the runtime library search path - \\ -framework [name] (darwin) link against framework - \\ -mios-version-min [ver] (darwin) set iOS deployment target - \\ -mmacosx-version-min [ver] (darwin) set Mac OS X deployment target - \\ --ver-major [ver] Dynamic library semver major version - \\ --ver-minor [ver] Dynamic library semver minor version - \\ --ver-patch [ver] Dynamic library semver patch version + \\ -l[lib], --library [lib] Link against system library + \\ --dynamic-linker [path] Set the dynamic interpreter path (usually ld.so) + \\ --version [ver] Dynamic library semver \\ + \\Debug Options (Zig Compiler Development): + \\ -ftime-report Print timing diagnostics + \\ --debug-tokenize verbose tokenization + \\ --debug-ast-tree verbose parsing into an AST (tree view) + \\ --debug-ast-fmt verbose parsing into an AST (render source) + \\ --debug-ir verbose Zig IR + \\ --debug-link verbose linking + \\ --debug-codegen verbose machine code generation \\ ; -fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Compilation.Kind) !void { - const stderr = io.getStdErr().outStream(); +const Emit = union(enum) { + no, + yes_default_path, + yes: []const u8, +}; - var color: errmsg.Color = .Auto; +fn buildOutputType( + gpa: *Allocator, + arena: *Allocator, + args: []const []const u8, + output_mode: std.builtin.OutputMode, +) !void { + var color: Color = .Auto; var build_mode: std.builtin.Mode = .Debug; - var emit_bin = true; - var emit_asm = false; - var emit_llvm_ir = false; - var emit_h = false; var provided_name: ?[]const u8 = null; var is_dynamic = false; var root_src_file: ?[]const u8 = null; - var libc_arg: ?[]const u8 = null; var version: std.builtin.Version = .{ .major = 0, .minor = 0, .patch = 0 }; - var linker_script: ?[]const u8 = null; var strip = false; - var verbose_tokenize = false; - var verbose_ast_tree = false; - var verbose_ast_fmt = false; - var verbose_link = false; - var verbose_ir = false; - var verbose_llvm_ir = false; - var verbose_cimport = false; - var linker_rdynamic = false; - var link_eh_frame_hdr = false; - var macosx_version_min: ?[]const u8 = null; - var ios_version_min: ?[]const u8 = null; + var watch = false; + var debug_tokenize = false; + var debug_ast_tree = false; + var debug_ast_fmt = false; + var debug_link = false; + var debug_ir = false; + var debug_codegen = false; + var time_report = false; + var emit_bin: Emit = .yes_default_path; + var emit_zir: Emit = .no; + var target_arch_os_abi: []const u8 = "native"; + var target_mcpu: ?[]const u8 = null; + var target_dynamic_linker: ?[]const u8 = null; - var assembly_files = ArrayList([]const u8).init(allocator); - defer assembly_files.deinit(); - - var link_objects = ArrayList([]const u8).init(allocator); - defer link_objects.deinit(); - - var clang_argv_buf = ArrayList([]const u8).init(allocator); - defer clang_argv_buf.deinit(); - - var mllvm_flags = ArrayList([]const u8).init(allocator); - defer mllvm_flags.deinit(); - - var cur_pkg = try CliPkg.init(allocator, "", "", null); - defer cur_pkg.deinit(); - - var system_libs = ArrayList([]const u8).init(allocator); + var system_libs = std.ArrayList([]const u8).init(gpa); defer system_libs.deinit(); - var c_src_files = ArrayList([]const u8).init(allocator); - defer c_src_files.deinit(); - { var i: usize = 0; while (i < args.len) : (i += 1) { const arg = args[i]; if (mem.startsWith(u8, arg, "-")) { - if (mem.eql(u8, arg, "--help")) { + if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { try io.getStdOut().writeAll(usage_build_generic); process.exit(0); } else if (mem.eql(u8, arg, "--color")) { if (i + 1 >= args.len) { - try stderr.writeAll("expected [auto|on|off] after --color\n"); + std.debug.warn("expected [auto|on|off] after --color\n", .{}); process.exit(1); } i += 1; @@ -219,12 +199,12 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co } else if (mem.eql(u8, next_arg, "off")) { color = .Off; } else { - try stderr.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg}); + std.debug.warn("expected [auto|on|off] after --color, found '{}'\n", .{next_arg}); process.exit(1); } } else if (mem.eql(u8, arg, "--mode")) { if (i + 1 >= args.len) { - try stderr.writeAll("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode\n"); + std.debug.warn("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode\n", .{}); process.exit(1); } i += 1; @@ -238,289 +218,317 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co } else if (mem.eql(u8, next_arg, "ReleaseSmall")) { build_mode = .ReleaseSmall; } else { - try stderr.print("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode, found '{}'\n", .{next_arg}); + std.debug.warn("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode, found '{}'\n", .{next_arg}); process.exit(1); } } else if (mem.eql(u8, arg, "--name")) { if (i + 1 >= args.len) { - try stderr.writeAll("expected parameter after --name\n"); + std.debug.warn("expected parameter after --name\n", .{}); process.exit(1); } i += 1; provided_name = args[i]; - } else if (mem.eql(u8, arg, "--ver-major")) { + } else if (mem.eql(u8, arg, "--library")) { if (i + 1 >= args.len) { - try stderr.writeAll("expected parameter after --ver-major\n"); + std.debug.warn("expected parameter after --library\n", .{}); process.exit(1); } i += 1; - version.major = try std.fmt.parseInt(u32, args[i], 10); - } else if (mem.eql(u8, arg, "--ver-minor")) { + try system_libs.append(args[i]); + } else if (mem.eql(u8, arg, "--version")) { if (i + 1 >= args.len) { - try stderr.writeAll("expected parameter after --ver-minor\n"); + std.debug.warn("expected parameter after --version\n", .{}); process.exit(1); } i += 1; - version.minor = try std.fmt.parseInt(u32, args[i], 10); - } else if (mem.eql(u8, arg, "--ver-patch")) { + version = std.builtin.Version.parse(args[i]) catch |err| { + std.debug.warn("unable to parse --version '{}': {}\n", .{ args[i], @errorName(err) }); + process.exit(1); + }; + } else if (mem.eql(u8, arg, "-target")) { if (i + 1 >= args.len) { - try stderr.writeAll("expected parameter after --ver-patch\n"); + std.debug.warn("expected parameter after -target\n", .{}); process.exit(1); } i += 1; - version.patch = try std.fmt.parseInt(u32, args[i], 10); - } else if (mem.eql(u8, arg, "--linker-script")) { + target_arch_os_abi = args[i]; + } else if (mem.eql(u8, arg, "-mcpu")) { if (i + 1 >= args.len) { - try stderr.writeAll("expected parameter after --linker-script\n"); + std.debug.warn("expected parameter after -mcpu\n", .{}); process.exit(1); } i += 1; - linker_script = args[i]; - } else if (mem.eql(u8, arg, "--libc")) { + target_mcpu = args[i]; + } else if (mem.startsWith(u8, arg, "-mcpu=")) { + target_mcpu = arg["-mcpu=".len..]; + } else if (mem.eql(u8, arg, "--dynamic-linker")) { if (i + 1 >= args.len) { - try stderr.writeAll("expected parameter after --libc\n"); + std.debug.warn("expected parameter after --dynamic-linker\n", .{}); process.exit(1); } i += 1; - libc_arg = args[i]; - } else if (mem.eql(u8, arg, "-mllvm")) { - if (i + 1 >= args.len) { - try stderr.writeAll("expected parameter after -mllvm\n"); - process.exit(1); - } - i += 1; - try clang_argv_buf.append("-mllvm"); - try clang_argv_buf.append(args[i]); - - try mllvm_flags.append(args[i]); - } else if (mem.eql(u8, arg, "-mmacosx-version-min")) { - if (i + 1 >= args.len) { - try stderr.writeAll("expected parameter after -mmacosx-version-min\n"); - process.exit(1); - } - i += 1; - macosx_version_min = args[i]; - } else if (mem.eql(u8, arg, "-mios-version-min")) { - if (i + 1 >= args.len) { - try stderr.writeAll("expected parameter after -mios-version-min\n"); - process.exit(1); - } - i += 1; - ios_version_min = args[i]; + target_dynamic_linker = args[i]; + } else if (mem.eql(u8, arg, "--watch")) { + watch = true; + } else if (mem.eql(u8, arg, "-ftime-report")) { + time_report = true; } else if (mem.eql(u8, arg, "-femit-bin")) { - emit_bin = true; + emit_bin = .yes_default_path; + } else if (mem.startsWith(u8, arg, "-femit-bin=")) { + emit_bin = .{ .yes = arg["-femit-bin=".len..] }; } else if (mem.eql(u8, arg, "-fno-emit-bin")) { - emit_bin = false; - } else if (mem.eql(u8, arg, "-femit-asm")) { - emit_asm = true; - } else if (mem.eql(u8, arg, "-fno-emit-asm")) { - emit_asm = false; - } else if (mem.eql(u8, arg, "-femit-llvm-ir")) { - emit_llvm_ir = true; - } else if (mem.eql(u8, arg, "-fno-emit-llvm-ir")) { - emit_llvm_ir = false; + 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, "-dynamic")) { is_dynamic = true; } else if (mem.eql(u8, arg, "--strip")) { strip = true; - } else if (mem.eql(u8, arg, "--verbose-tokenize")) { - verbose_tokenize = true; - } else if (mem.eql(u8, arg, "--verbose-ast-tree")) { - verbose_ast_tree = true; - } else if (mem.eql(u8, arg, "--verbose-ast-fmt")) { - verbose_ast_fmt = true; - } else if (mem.eql(u8, arg, "--verbose-link")) { - verbose_link = true; - } else if (mem.eql(u8, arg, "--verbose-ir")) { - verbose_ir = true; - } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) { - verbose_llvm_ir = true; - } else if (mem.eql(u8, arg, "--eh-frame-hdr")) { - link_eh_frame_hdr = true; - } else if (mem.eql(u8, arg, "--verbose-cimport")) { - verbose_cimport = true; - } else if (mem.eql(u8, arg, "-rdynamic")) { - linker_rdynamic = true; - } else if (mem.eql(u8, arg, "--pkg-begin")) { - if (i + 2 >= args.len) { - try stderr.writeAll("expected [name] [path] after --pkg-begin\n"); - process.exit(1); - } - i += 1; - const new_pkg_name = args[i]; - i += 1; - const new_pkg_path = args[i]; - - var new_cur_pkg = try CliPkg.init(allocator, new_pkg_name, new_pkg_path, cur_pkg); - try cur_pkg.children.append(new_cur_pkg); - cur_pkg = new_cur_pkg; - } else if (mem.eql(u8, arg, "--pkg-end")) { - if (cur_pkg.parent) |parent| { - cur_pkg = parent; - } else { - try stderr.writeAll("encountered --pkg-end with no matching --pkg-begin\n"); - process.exit(1); - } + } else if (mem.eql(u8, arg, "--debug-tokenize")) { + debug_tokenize = true; + } else if (mem.eql(u8, arg, "--debug-ast-tree")) { + debug_ast_tree = true; + } else if (mem.eql(u8, arg, "--debug-ast-fmt")) { + debug_ast_fmt = true; + } else if (mem.eql(u8, arg, "--debug-link")) { + debug_link = true; + } else if (mem.eql(u8, arg, "--debug-ir")) { + debug_ir = true; + } else if (mem.eql(u8, arg, "--debug-codegen")) { + debug_codegen = true; } else if (mem.startsWith(u8, arg, "-l")) { try system_libs.append(arg[2..]); } else { - try stderr.print("unrecognized parameter: '{}'", .{arg}); + std.debug.warn("unrecognized parameter: '{}'", .{arg}); process.exit(1); } - } else if (mem.endsWith(u8, arg, ".s")) { - try assembly_files.append(arg); + } else if (mem.endsWith(u8, arg, ".s") or mem.endsWith(u8, arg, ".S")) { + std.debug.warn("assembly files not supported yet", .{}); + process.exit(1); } else if (mem.endsWith(u8, arg, ".o") or mem.endsWith(u8, arg, ".obj") or mem.endsWith(u8, arg, ".a") or mem.endsWith(u8, arg, ".lib")) { - try link_objects.append(arg); + std.debug.warn("object files and static libraries not supported yet", .{}); + process.exit(1); } else if (mem.endsWith(u8, arg, ".c") or mem.endsWith(u8, arg, ".cpp")) { - try c_src_files.append(arg); - } else if (mem.endsWith(u8, arg, ".zig")) { + std.debug.warn("compilation of C and C++ source code requires LLVM extensions which are not implemented yet", .{}); + process.exit(1); + } else if (mem.endsWith(u8, arg, ".so") or + mem.endsWith(u8, arg, ".dylib") or + mem.endsWith(u8, arg, ".dll")) + { + std.debug.warn("linking against dynamic libraries not yet supported", .{}); + process.exit(1); + } else if (mem.endsWith(u8, arg, ".zig") or mem.endsWith(u8, arg, ".zir")) { if (root_src_file) |other| { - try stderr.print("found another zig file '{}' after root source file '{}'", .{ - arg, - other, - }); + std.debug.warn("found another zig file '{}' after root source file '{}'", .{ arg, other }); process.exit(1); } else { root_src_file = arg; } } else { - try stderr.print("unrecognized file extension of parameter '{}'", .{arg}); + std.debug.warn("unrecognized file extension of parameter '{}'", .{arg}); } } } - if (cur_pkg.parent != null) { - try stderr.print("unmatched --pkg-begin\n", .{}); - process.exit(1); - } - const root_name = if (provided_name) |n| n else blk: { if (root_src_file) |file| { const basename = fs.path.basename(file); var it = mem.split(basename, "."); break :blk it.next() orelse basename; } else { - try stderr.writeAll("--name [name] not provided and unable to infer\n"); + std.debug.warn("--name [name] not provided and unable to infer\n", .{}); process.exit(1); } }; - if (root_src_file == null and link_objects.len == 0 and assembly_files.len == 0) { - try stderr.writeAll("Expected source file argument or at least one --object or --assembly argument\n"); + if (system_libs.items.len != 0) { + std.debug.warn("linking against system libraries not yet supported", .{}); process.exit(1); } - if (out_type == Compilation.Kind.Obj and link_objects.len != 0) { - try stderr.writeAll("When building an object file, --object arguments are invalid\n"); + var diags: std.zig.CrossTarget.ParseOptions.Diagnostics = .{}; + const cross_target = std.zig.CrossTarget.parse(.{ + .arch_os_abi = target_arch_os_abi, + .cpu_features = target_mcpu, + .dynamic_linker = target_dynamic_linker, + .diagnostics = &diags, + }) catch |err| switch (err) { + error.UnknownCpuModel => { + std.debug.warn("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{ + diags.cpu_name.?, + @tagName(diags.arch.?), + }); + for (diags.arch.?.allCpuModels()) |cpu| { + std.debug.warn(" {}\n", .{cpu.name}); + } + process.exit(1); + }, + error.UnknownCpuFeature => { + std.debug.warn( + \\Unknown CPU feature: '{}' + \\Available CPU features for architecture '{}': + \\ + , .{ + diags.unknown_feature_name, + @tagName(diags.arch.?), + }); + for (diags.arch.?.allFeaturesList()) |feature| { + std.debug.warn(" {}: {}\n", .{ feature.name, feature.description }); + } + process.exit(1); + }, + else => |e| return e, + }; + + const object_format: ?std.builtin.ObjectFormat = null; + var target_info = try std.zig.system.NativeTargetInfo.detect(gpa, cross_target); + if (target_info.cpu_detection_unimplemented) { + // TODO We want to just use detected_info.target but implementing + // CPU model & feature detection is todo so here we rely on LLVM. + std.debug.warn("CPU features detection is not yet available for this system without LLVM extensions\n", .{}); process.exit(1); } - try ZigCompiler.setLlvmArgv(allocator, mllvm_flags.span()); - - const zig_lib_dir = introspect.resolveZigLibDir(allocator) catch process.exit(1); - defer allocator.free(zig_lib_dir); - - var override_libc: LibCInstallation = undefined; - - var zig_compiler = try ZigCompiler.init(allocator); - defer zig_compiler.deinit(); - - var comp = try Compilation.create( - &zig_compiler, - root_name, - root_src_file, - .{}, - out_type, - build_mode, - !is_dynamic, - zig_lib_dir, - ); - defer comp.destroy(); - - if (libc_arg) |libc_path| { - parseLibcPaths(allocator, &override_libc, libc_path); - comp.override_libc = &override_libc; - } - - for (system_libs.span()) |lib| { - _ = try comp.addLinkLib(lib, true); - } - - comp.version = version; - comp.is_test = false; - comp.linker_script = linker_script; - comp.clang_argv = clang_argv_buf.span(); - comp.strip = strip; - - comp.verbose_tokenize = verbose_tokenize; - comp.verbose_ast_tree = verbose_ast_tree; - comp.verbose_ast_fmt = verbose_ast_fmt; - comp.verbose_link = verbose_link; - comp.verbose_ir = verbose_ir; - comp.verbose_llvm_ir = verbose_llvm_ir; - comp.verbose_cimport = verbose_cimport; - - comp.link_eh_frame_hdr = link_eh_frame_hdr; - - comp.err_color = color; - - comp.linker_rdynamic = linker_rdynamic; - - if (macosx_version_min != null and ios_version_min != null) { - try stderr.writeAll("-mmacosx-version-min and -mios-version-min options not allowed together\n"); + const src_path = root_src_file orelse { + std.debug.warn("expected at least one file argument", .{}); process.exit(1); - } + }; - if (macosx_version_min) |ver| { - comp.darwin_version_min = Compilation.DarwinVersionMin{ .MacOS = ver }; - } - if (ios_version_min) |ver| { - comp.darwin_version_min = Compilation.DarwinVersionMin{ .Ios = ver }; - } + const bin_path = switch (emit_bin) { + .no => { + std.debug.warn("-fno-emit-bin not supported yet", .{}); + process.exit(1); + }, + .yes_default_path => try std.fmt.allocPrint(arena, "{}{}", .{ root_name, target_info.target.exeFileExt() }), + .yes => |p| p, + }; - comp.emit_bin = emit_bin; - comp.emit_asm = emit_asm; - comp.emit_llvm_ir = emit_llvm_ir; - comp.emit_h = emit_h; - comp.assembly_files = assembly_files.span(); - comp.link_objects = link_objects.span(); - - comp.start(); - processBuildEvents(comp, color); -} - -fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void { - const stderr_file = io.getStdErr(); - const stderr = stderr_file.outStream(); - var count: usize = 0; - while (!comp.cancelled) { - const build_event = comp.events.get(); - count += 1; - - switch (build_event) { - .Ok => { - stderr.print("Build {} succeeded\n", .{count}) catch process.exit(1); - }, - .Error => |err| { - stderr.print("Build {} failed: {}\n", .{ count, @errorName(err) }) catch process.exit(1); - }, - .Fail => |msgs| { - stderr.print("Build {} compile errors:\n", .{count}) catch process.exit(1); - for (msgs) |msg| { - defer msg.destroy(); - msg.printToFile(stderr_file, color) catch process.exit(1); + const zir_out_path: ?[]const u8 = switch (emit_zir) { + .no => null, + .yes_default_path => blk: { + if (root_src_file) |rsf| { + if (mem.endsWith(u8, rsf, ".zir")) { + break :blk try std.fmt.allocPrint(arena, "{}.out.zir", .{root_name}); } - }, + } + break :blk try std.fmt.allocPrint(arena, "{}.zir", .{root_name}); + }, + .yes => |p| p, + }; + + var bin_file = try link.openBinFilePath(gpa, fs.cwd(), bin_path, .{ + .target = target_info.target, + .output_mode = output_mode, + .link_mode = if (is_dynamic) .Dynamic else .Static, + .object_format = object_format orelse target_info.target.getObjectFormat(), + }); + defer bin_file.deinit(); + + var module = blk: { + const root_pkg = try Package.create(gpa, fs.cwd(), ".", src_path); + errdefer root_pkg.destroy(); + + const root_scope = try gpa.create(ir.Module.Scope.ZIRModule); + errdefer gpa.destroy(root_scope); + root_scope.* = .{ + .sub_file_path = root_pkg.root_src_path, + .source = .{ .unloaded = {} }, + .contents = .{ .not_available = {} }, + .status = .never_loaded, + }; + + break :blk ir.Module{ + .allocator = gpa, + .root_pkg = root_pkg, + .root_scope = root_scope, + .bin_file = &bin_file, + .optimize_mode = .Debug, + .decl_table = std.AutoHashMap(ir.Module.Decl.Hash, *ir.Module.Decl).init(gpa), + .decl_exports = std.AutoHashMap(*ir.Module.Decl, []*ir.Module.Export).init(gpa), + .export_owners = std.AutoHashMap(*ir.Module.Decl, []*ir.Module.Export).init(gpa), + .failed_decls = std.AutoHashMap(*ir.Module.Decl, *ir.ErrorMsg).init(gpa), + .failed_files = std.AutoHashMap(*ir.Module.Scope.ZIRModule, *ir.ErrorMsg).init(gpa), + .failed_exports = std.AutoHashMap(*ir.Module.Export, *ir.ErrorMsg).init(gpa), + .work_queue = std.fifo.LinearFifo(ir.Module.WorkItem, .Dynamic).init(gpa), + }; + }; + defer module.deinit(); + + const stdin = std.io.getStdIn().inStream(); + const stderr = std.io.getStdErr().outStream(); + var repl_buf: [1024]u8 = undefined; + + try updateModule(gpa, &module, zir_out_path); + + while (watch) { + try stderr.print("🦎 ", .{}); + if (stdin.readUntilDelimiterOrEof(&repl_buf, '\n') catch |err| { + try stderr.print("\nUnable to parse command: {}\n", .{@errorName(err)}); + continue; + }) |line| { + if (mem.eql(u8, line, "update")) { + try updateModule(gpa, &module, zir_out_path); + } else if (mem.eql(u8, line, "exit")) { + break; + } else if (mem.eql(u8, line, "help")) { + try stderr.writeAll(repl_help); + } else { + try stderr.print("unknown command: {}\n", .{line}); + } + } else { + break; } } } +fn updateModule(gpa: *Allocator, module: *ir.Module, zir_out_path: ?[]const u8) !void { + try module.update(); + + var errors = try module.getAllErrorsAlloc(); + defer errors.deinit(module.allocator); + + if (errors.list.len != 0) { + for (errors.list) |full_err_msg| { + std.debug.warn("{}:{}:{}: error: {}\n", .{ + full_err_msg.src_path, + full_err_msg.line + 1, + full_err_msg.column + 1, + full_err_msg.msg, + }); + } + } + + if (zir_out_path) |zop| { + var new_zir_module = try ir.text.emit_zir(gpa, module.*); + defer new_zir_module.deinit(gpa); + + const baf = try io.BufferedAtomicFile.create(gpa, fs.cwd(), zop, .{}); + defer baf.destroy(); + + try new_zir_module.writeToStream(gpa, baf.stream()); + + try baf.finish(); + } +} + +const repl_help = + \\Commands: + \\ update Detect changes to source files and update output files. + \\ help Print this text + \\ exit Quit this repl + \\ +; + pub const usage_fmt = \\usage: zig fmt [file]... \\ @@ -539,17 +547,17 @@ pub const usage_fmt = ; const Fmt = struct { - seen: event.Locked(SeenMap), + seen: SeenMap, any_error: bool, - color: errmsg.Color, - allocator: *Allocator, + color: Color, + gpa: *Allocator, - const SeenMap = std.StringHashMap(void); + const SeenMap = std.BufSet; }; -fn parseLibcPaths(allocator: *Allocator, libc: *LibCInstallation, libc_paths_file: []const u8) void { +fn parseLibcPaths(gpa: *Allocator, libc: *LibCInstallation, libc_paths_file: []const u8) void { const stderr = io.getStdErr().outStream(); - libc.* = LibCInstallation.parse(allocator, libc_paths_file, stderr) catch |err| { + libc.* = LibCInstallation.parse(gpa, libc_paths_file, stderr) catch |err| { stderr.print("Unable to parse libc path file '{}': {}.\n" ++ "Try running `zig libc` to see an example for the native target.\n", .{ libc_paths_file, @@ -559,13 +567,13 @@ fn parseLibcPaths(allocator: *Allocator, libc: *LibCInstallation, libc_paths_fil }; } -fn cmdLibC(allocator: *Allocator, args: []const []const u8) !void { +fn cmdLibC(gpa: *Allocator, args: []const []const u8) !void { const stderr = io.getStdErr().outStream(); switch (args.len) { 0 => {}, 1 => { var libc_installation: LibCInstallation = undefined; - parseLibcPaths(allocator, &libc_installation, args[0]); + parseLibcPaths(gpa, &libc_installation, args[0]); return; }, else => { @@ -574,23 +582,20 @@ fn cmdLibC(allocator: *Allocator, args: []const []const u8) !void { }, } - var zig_compiler = try ZigCompiler.init(allocator); - defer zig_compiler.deinit(); - - const libc = zig_compiler.getNativeLibC() catch |err| { + const libc = LibCInstallation.findNative(.{ .allocator = gpa }) catch |err| { stderr.print("unable to find libc: {}\n", .{@errorName(err)}) catch {}; process.exit(1); }; + libc.render(io.getStdOut().outStream()) catch process.exit(1); } -fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { +pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { const stderr_file = io.getStdErr(); - const stderr = stderr_file.outStream(); - var color: errmsg.Color = .Auto; + var color: Color = .Auto; var stdin_flag: bool = false; var check_flag: bool = false; - var input_files = ArrayList([]const u8).init(allocator); + var input_files = ArrayList([]const u8).init(gpa); { var i: usize = 0; @@ -603,7 +608,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { process.exit(0); } else if (mem.eql(u8, arg, "--color")) { if (i + 1 >= args.len) { - try stderr.writeAll("expected [auto|on|off] after --color\n"); + std.debug.warn("expected [auto|on|off] after --color\n", .{}); process.exit(1); } i += 1; @@ -615,7 +620,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { } else if (mem.eql(u8, next_arg, "off")) { color = .Off; } else { - try stderr.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg}); + std.debug.warn("expected [auto|on|off] after --color, found '{}'\n", .{next_arg}); process.exit(1); } } else if (mem.eql(u8, arg, "--stdin")) { @@ -623,7 +628,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { } else if (mem.eql(u8, arg, "--check")) { check_flag = true; } else { - try stderr.print("unrecognized parameter: '{}'", .{arg}); + std.debug.warn("unrecognized parameter: '{}'", .{arg}); process.exit(1); } } else { @@ -633,60 +638,55 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { } if (stdin_flag) { - if (input_files.len != 0) { - try stderr.writeAll("cannot use --stdin with positional arguments\n"); + if (input_files.items.len != 0) { + std.debug.warn("cannot use --stdin with positional arguments\n", .{}); process.exit(1); } const stdin = io.getStdIn().inStream(); - const source_code = try stdin.readAllAlloc(allocator, max_src_size); - defer allocator.free(source_code); + const source_code = try stdin.readAllAlloc(gpa, max_src_size); + defer gpa.free(source_code); - const tree = std.zig.parse(allocator, source_code) catch |err| { - try stderr.print("error parsing stdin: {}\n", .{err}); + const tree = std.zig.parse(gpa, source_code) catch |err| { + std.debug.warn("error parsing stdin: {}\n", .{err}); process.exit(1); }; defer tree.deinit(); var error_it = tree.errors.iterator(0); while (error_it.next()) |parse_error| { - const msg = try errmsg.Msg.createFromParseError(allocator, parse_error, tree, ""); - defer msg.destroy(); - - try msg.printToFile(io.getStdErr(), color); + try printErrMsgToFile(gpa, parse_error, tree, "", stderr_file, color); } if (tree.errors.len != 0) { process.exit(1); } if (check_flag) { - const anything_changed = try std.zig.render(allocator, io.null_out_stream, tree); - const code: u8 = if (anything_changed) 1 else 0; + const anything_changed = try std.zig.render(gpa, io.null_out_stream, tree); + const code = if (anything_changed) @as(u8, 1) else @as(u8, 0); process.exit(code); } const stdout = io.getStdOut().outStream(); - _ = try std.zig.render(allocator, stdout, tree); + _ = try std.zig.render(gpa, stdout, tree); return; } - if (input_files.len == 0) { - try stderr.writeAll("expected at least one source file argument\n"); + if (input_files.items.len == 0) { + std.debug.warn("expected at least one source file argument\n", .{}); process.exit(1); } var fmt = Fmt{ - .allocator = allocator, - .seen = event.Locked(Fmt.SeenMap).init(Fmt.SeenMap.init(allocator)), + .gpa = gpa, + .seen = Fmt.SeenMap.init(gpa), .any_error = false, .color = color, }; - var group = event.Group(FmtError!void).init(allocator); for (input_files.span()) |file_path| { - try group.call(fmtPath, .{ &fmt, file_path, check_flag }); + try fmtPath(&fmt, file_path, check_flag); } - try group.wait(); if (fmt.any_error) { process.exit(1); } @@ -711,54 +711,45 @@ const FmtError = error{ ReadOnlyFileSystem, LinkQuotaExceeded, FileBusy, - CurrentWorkingDirectoryUnlinked, } || fs.File.OpenError; -async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void { - const stderr_file = io.getStdErr(); - const stderr = stderr_file.outStream(); +fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void { + // get the real path here to avoid Windows failing on relative file paths with . or .. in them + var real_path = fs.realpathAlloc(fmt.gpa, file_path) catch |err| { + std.debug.warn("unable to open '{}': {}\n", .{ file_path, err }); + fmt.any_error = true; + return; + }; + defer fmt.gpa.free(real_path); - const file_path = try std.mem.dupe(fmt.allocator, u8, file_path_ref); - defer fmt.allocator.free(file_path); + if (fmt.seen.exists(real_path)) return; + try fmt.seen.put(real_path); - { - const held = fmt.seen.acquire(); - defer held.release(); - - if (try held.value.put(file_path, {})) |_| return; - } - - const source_code = fs.cwd().readFileAlloc( - fmt.allocator, - file_path, - max_src_size, - ) catch |err| switch (err) { + const source_code = fs.cwd().readFileAlloc(fmt.gpa, real_path, max_src_size) catch |err| switch (err) { error.IsDir, error.AccessDenied => { var dir = try fs.cwd().openDir(file_path, .{ .iterate = true }); defer dir.close(); - var group = event.Group(FmtError!void).init(fmt.allocator); - var it = dir.iterate(); - while (try it.next()) |entry| { + var dir_it = dir.iterate(); + + while (try dir_it.next()) |entry| { if (entry.kind == .Directory or mem.endsWith(u8, entry.name, ".zig")) { - const full_path = try fs.path.join(fmt.allocator, &[_][]const u8{ file_path, entry.name }); - @panic("TODO https://github.com/ziglang/zig/issues/3777"); - // try group.call(fmtPath, .{fmt, full_path, check_mode}); + const full_path = try fs.path.join(fmt.gpa, &[_][]const u8{ file_path, entry.name }); + try fmtPath(fmt, full_path, check_mode); } } - return group.wait(); + return; }, else => { - // TODO lock stderr printing - try stderr.print("unable to open '{}': {}\n", .{ file_path, err }); + std.debug.warn("unable to open '{}': {}\n", .{ file_path, err }); fmt.any_error = true; return; }, }; - defer fmt.allocator.free(source_code); + defer fmt.gpa.free(source_code); - const tree = std.zig.parse(fmt.allocator, source_code) catch |err| { - try stderr.print("error parsing file '{}': {}\n", .{ file_path, err }); + const tree = std.zig.parse(fmt.gpa, source_code) catch |err| { + std.debug.warn("error parsing file '{}': {}\n", .{ file_path, err }); fmt.any_error = true; return; }; @@ -766,10 +757,7 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtErro var error_it = tree.errors.iterator(0); while (error_it.next()) |parse_error| { - const msg = try errmsg.Msg.createFromParseError(fmt.allocator, parse_error, tree, file_path); - defer fmt.allocator.destroy(msg); - - try msg.printToFile(stderr_file, fmt.color); + try printErrMsgToFile(fmt.gpa, parse_error, tree, file_path, std.io.getStdErr(), fmt.color); } if (tree.errors.len != 0) { fmt.any_error = true; @@ -777,32 +765,67 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtErro } if (check_mode) { - const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, tree); + const anything_changed = try std.zig.render(fmt.gpa, io.null_out_stream, tree); if (anything_changed) { - try stderr.print("{}\n", .{file_path}); + std.debug.warn("{}\n", .{file_path}); fmt.any_error = true; } } else { - // TODO make this evented - const baf = try io.BufferedAtomicFile.create(fmt.allocator, file_path); + const baf = try io.BufferedAtomicFile.create(fmt.gpa, fs.cwd(), real_path, .{}); defer baf.destroy(); - const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), tree); + const anything_changed = try std.zig.render(fmt.gpa, baf.stream(), tree); if (anything_changed) { - try stderr.print("{}\n", .{file_path}); + std.debug.warn("{}\n", .{file_path}); try baf.finish(); } } } -fn cmdVersion(allocator: *Allocator, args: []const []const u8) !void { - const stdout = io.getStdOut().outStream(); - try stdout.print("{}\n", .{c.ZIG_VERSION_STRING}); -} +fn printErrMsgToFile( + gpa: *mem.Allocator, + parse_error: *const ast.Error, + tree: *ast.Tree, + path: []const u8, + file: fs.File, + color: Color, +) !void { + const color_on = switch (color) { + .Auto => file.isTty(), + .On => true, + .Off => false, + }; + const lok_token = parse_error.loc(); + const span_first = lok_token; + const span_last = lok_token; -fn cmdHelp(allocator: *Allocator, args: []const []const u8) !void { - const stdout = io.getStdOut(); - try stdout.writeAll(usage); + const first_token = tree.tokens.at(span_first); + const last_token = tree.tokens.at(span_last); + const start_loc = tree.tokenLocationPtr(0, first_token); + const end_loc = tree.tokenLocationPtr(first_token.end, last_token); + + var text_buf = std.ArrayList(u8).init(gpa); + defer text_buf.deinit(); + const out_stream = text_buf.outStream(); + try parse_error.render(&tree.tokens, out_stream); + const text = text_buf.span(); + + const stream = file.outStream(); + try stream.print("{}:{}:{}: error: {}\n", .{ path, start_loc.line + 1, start_loc.column + 1, text }); + + if (!color_on) return; + + // Print \r and \t as one space each so that column counts line up + for (tree.source[start_loc.line_start..start_loc.line_end]) |byte| { + try stream.writeByte(switch (byte) { + '\r', '\t' => ' ', + else => byte, + }); + } + try stream.writeByte('\n'); + try stream.writeByteNTimes(' ', start_loc.column); + try stream.writeByteNTimes('~', last_token.end - first_token.start); + try stream.writeByte('\n'); } pub const info_zen = @@ -817,90 +840,8 @@ pub const info_zen = \\ * Avoid local maximums. \\ * Reduce the amount one must remember. \\ * Minimize energy spent on coding style. + \\ * Resource deallocation must succeed. \\ * Together we serve end users. \\ \\ ; - -fn cmdZen(allocator: *Allocator, args: []const []const u8) !void { - try io.getStdOut().writeAll(info_zen); -} - -const usage_internal = - \\usage: zig internal [subcommand] - \\ - \\Sub-Commands: - \\ build-info Print static compiler build-info - \\ - \\ -; - -fn cmdInternal(allocator: *Allocator, args: []const []const u8) !void { - const stderr = io.getStdErr().outStream(); - if (args.len == 0) { - try stderr.writeAll(usage_internal); - process.exit(1); - } - - const sub_commands = [_]Command{Command{ - .name = "build-info", - .exec = cmdInternalBuildInfo, - }}; - - inline for (sub_commands) |sub_command| { - if (mem.eql(u8, sub_command.name, args[0])) { - var frame = try allocator.create(@Frame(sub_command.exec)); - defer allocator.destroy(frame); - frame.* = async sub_command.exec(allocator, args[1..]); - return await frame; - } - } - - try stderr.print("unknown sub command: {}\n\n", .{args[0]}); - try stderr.writeAll(usage_internal); -} - -fn cmdInternalBuildInfo(allocator: *Allocator, args: []const []const u8) !void { - const stdout = io.getStdOut().outStream(); - try stdout.print( - \\ZIG_CMAKE_BINARY_DIR {} - \\ZIG_CXX_COMPILER {} - \\ZIG_LLD_INCLUDE_PATH {} - \\ZIG_LLD_LIBRARIES {} - \\ZIG_LLVM_CONFIG_EXE {} - \\ZIG_DIA_GUIDS_LIB {} - \\ - , .{ - c.ZIG_CMAKE_BINARY_DIR, - c.ZIG_CXX_COMPILER, - c.ZIG_LLD_INCLUDE_PATH, - c.ZIG_LLD_LIBRARIES, - c.ZIG_LLVM_CONFIG_EXE, - c.ZIG_DIA_GUIDS_LIB, - }); -} - -const CliPkg = struct { - name: []const u8, - path: []const u8, - children: ArrayList(*CliPkg), - parent: ?*CliPkg, - - pub fn init(allocator: *mem.Allocator, name: []const u8, path: []const u8, parent: ?*CliPkg) !*CliPkg { - var pkg = try allocator.create(CliPkg); - pkg.* = CliPkg{ - .name = name, - .path = path, - .children = ArrayList(*CliPkg).init(allocator), - .parent = parent, - }; - return pkg; - } - - pub fn deinit(self: *CliPkg) void { - for (self.children.span()) |child| { - child.deinit(); - } - self.children.deinit(); - } -}; diff --git a/src-self-hosted/stage2.zig b/src-self-hosted/stage2.zig index 38ab49ccc4..d1bf73bb9e 100644 --- a/src-self-hosted/stage2.zig +++ b/src-self-hosted/stage2.zig @@ -12,7 +12,6 @@ const ArrayListSentineled = std.ArrayListSentineled; const Target = std.Target; const CrossTarget = std.zig.CrossTarget; const self_hosted_main = @import("main.zig"); -const errmsg = @import("errmsg.zig"); const DepTokenizer = @import("dep_tokenizer.zig").Tokenizer; const assert = std.debug.assert; const LibCInstallation = @import("libc_installation.zig").LibCInstallation; @@ -168,8 +167,6 @@ export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error { return .None; } -// TODO: just use the actual self-hosted zig fmt. Until https://github.com/ziglang/zig/issues/2377, -// we use a blocking implementation. export fn stage2_fmt(argc: c_int, argv: [*]const [*:0]const u8) c_int { if (std.debug.runtime_safety) { fmtMain(argc, argv) catch unreachable; @@ -191,258 +188,9 @@ fn fmtMain(argc: c_int, argv: [*]const [*:0]const u8) !void { try args_list.append(mem.spanZ(argv[arg_i])); } - stdout = std.io.getStdOut().outStream(); - stderr_file = std.io.getStdErr(); - stderr = stderr_file.outStream(); - const args = args_list.span()[2..]; - var color: errmsg.Color = .Auto; - var stdin_flag: bool = false; - var check_flag: bool = false; - var input_files = ArrayList([]const u8).init(allocator); - - { - var i: usize = 0; - while (i < args.len) : (i += 1) { - const arg = args[i]; - if (mem.startsWith(u8, arg, "-")) { - if (mem.eql(u8, arg, "--help")) { - try stdout.writeAll(self_hosted_main.usage_fmt); - process.exit(0); - } else if (mem.eql(u8, arg, "--color")) { - if (i + 1 >= args.len) { - try stderr.writeAll("expected [auto|on|off] after --color\n"); - process.exit(1); - } - i += 1; - const next_arg = args[i]; - if (mem.eql(u8, next_arg, "auto")) { - color = .Auto; - } else if (mem.eql(u8, next_arg, "on")) { - color = .On; - } else if (mem.eql(u8, next_arg, "off")) { - color = .Off; - } else { - try stderr.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg}); - process.exit(1); - } - } else if (mem.eql(u8, arg, "--stdin")) { - stdin_flag = true; - } else if (mem.eql(u8, arg, "--check")) { - check_flag = true; - } else { - try stderr.print("unrecognized parameter: '{}'", .{arg}); - process.exit(1); - } - } else { - try input_files.append(arg); - } - } - } - - if (stdin_flag) { - if (input_files.items.len != 0) { - try stderr.writeAll("cannot use --stdin with positional arguments\n"); - process.exit(1); - } - - const stdin_file = io.getStdIn(); - var stdin = stdin_file.inStream(); - - const source_code = try stdin.readAllAlloc(allocator, self_hosted_main.max_src_size); - defer allocator.free(source_code); - - const tree = std.zig.parse(allocator, source_code) catch |err| { - try stderr.print("error parsing stdin: {}\n", .{err}); - process.exit(1); - }; - defer tree.deinit(); - - var error_it = tree.errors.iterator(0); - while (error_it.next()) |parse_error| { - try printErrMsgToFile(allocator, parse_error, tree, "", stderr_file, color); - } - if (tree.errors.len != 0) { - process.exit(1); - } - if (check_flag) { - const anything_changed = try std.zig.render(allocator, io.null_out_stream, tree); - const code = if (anything_changed) @as(u8, 1) else @as(u8, 0); - process.exit(code); - } - - _ = try std.zig.render(allocator, stdout, tree); - return; - } - - if (input_files.items.len == 0) { - try stderr.writeAll("expected at least one source file argument\n"); - process.exit(1); - } - - var fmt = Fmt{ - .seen = Fmt.SeenMap.init(allocator), - .any_error = false, - .color = color, - .allocator = allocator, - }; - - for (input_files.span()) |file_path| { - try fmtPath(&fmt, file_path, check_flag); - } - if (fmt.any_error) { - process.exit(1); - } -} - -const FmtError = error{ - SystemResources, - OperationAborted, - IoPending, - BrokenPipe, - Unexpected, - WouldBlock, - FileClosed, - DestinationAddressRequired, - DiskQuota, - FileTooBig, - InputOutput, - NoSpaceLeft, - AccessDenied, - OutOfMemory, - RenameAcrossMountPoints, - ReadOnlyFileSystem, - LinkQuotaExceeded, - FileBusy, -} || fs.File.OpenError; - -fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void { - // get the real path here to avoid Windows failing on relative file paths with . or .. in them - var real_path = fs.realpathAlloc(fmt.allocator, file_path) catch |err| { - try stderr.print("unable to open '{}': {}\n", .{ file_path, err }); - fmt.any_error = true; - return; - }; - defer fmt.allocator.free(real_path); - - if (fmt.seen.exists(real_path)) return; - try fmt.seen.put(real_path); - - const source_code = fs.cwd().readFileAlloc(fmt.allocator, real_path, self_hosted_main.max_src_size) catch |err| switch (err) { - error.IsDir, error.AccessDenied => { - // TODO make event based (and dir.next()) - var dir = try fs.cwd().openDir(file_path, .{ .iterate = true }); - defer dir.close(); - - var dir_it = dir.iterate(); - - while (try dir_it.next()) |entry| { - if (entry.kind == .Directory or mem.endsWith(u8, entry.name, ".zig")) { - const full_path = try fs.path.join(fmt.allocator, &[_][]const u8{ file_path, entry.name }); - try fmtPath(fmt, full_path, check_mode); - } - } - return; - }, - else => { - // TODO lock stderr printing - try stderr.print("unable to open '{}': {}\n", .{ file_path, err }); - fmt.any_error = true; - return; - }, - }; - defer fmt.allocator.free(source_code); - - const tree = std.zig.parse(fmt.allocator, source_code) catch |err| { - try stderr.print("error parsing file '{}': {}\n", .{ file_path, err }); - fmt.any_error = true; - return; - }; - defer tree.deinit(); - - var error_it = tree.errors.iterator(0); - while (error_it.next()) |parse_error| { - try printErrMsgToFile(fmt.allocator, parse_error, tree, file_path, stderr_file, fmt.color); - } - if (tree.errors.len != 0) { - fmt.any_error = true; - return; - } - - if (check_mode) { - const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, tree); - if (anything_changed) { - try stderr.print("{}\n", .{file_path}); - fmt.any_error = true; - } - } else { - const baf = try io.BufferedAtomicFile.create(fmt.allocator, fs.cwd(), real_path, .{}); - defer baf.destroy(); - - const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), tree); - if (anything_changed) { - try stderr.print("{}\n", .{file_path}); - try baf.finish(); - } - } -} - -const Fmt = struct { - seen: SeenMap, - any_error: bool, - color: errmsg.Color, - allocator: *mem.Allocator, - - const SeenMap = std.BufSet; -}; - -fn printErrMsgToFile( - allocator: *mem.Allocator, - parse_error: *const ast.Error, - tree: *ast.Tree, - path: []const u8, - file: fs.File, - color: errmsg.Color, -) !void { - const color_on = switch (color) { - .Auto => file.isTty(), - .On => true, - .Off => false, - }; - const lok_token = parse_error.loc(); - const span = errmsg.Span{ - .first = lok_token, - .last = lok_token, - }; - - const first_token = tree.tokens.at(span.first); - const last_token = tree.tokens.at(span.last); - const start_loc = tree.tokenLocationPtr(0, first_token); - const end_loc = tree.tokenLocationPtr(first_token.end, last_token); - - var text_buf = std.ArrayList(u8).init(allocator); - defer text_buf.deinit(); - const out_stream = text_buf.outStream(); - try parse_error.render(&tree.tokens, out_stream); - const text = text_buf.span(); - - const stream = file.outStream(); - try stream.print("{}:{}:{}: error: {}\n", .{ path, start_loc.line + 1, start_loc.column + 1, text }); - - if (!color_on) return; - - // Print \r and \t as one space each so that column counts line up - for (tree.source[start_loc.line_start..start_loc.line_end]) |byte| { - try stream.writeByte(switch (byte) { - '\r', '\t' => ' ', - else => byte, - }); - } - try stream.writeByte('\n'); - try stream.writeByteNTimes(' ', start_loc.column); - try stream.writeByteNTimes('~', last_token.end - first_token.start); - try stream.writeByte('\n'); + return self_hosted_main.cmdFmt(allocator, args); } export fn stage2_DepTokenizer_init(input: [*]const u8, len: usize) stage2_DepTokenizer {