From 26182611c616f6c5dd9f8bfb080c5b0c7a8c6729 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 15 Feb 2023 15:38:51 -0800 Subject: [PATCH] move allocator to global state --- include/ghostty.h | 11 +++-- macos/Sources/GhosttyApp.swift | 21 ++++---- src/build_config.zig | 35 +++++++++++++ src/config.zig | 21 ++++---- src/main.zig | 89 ++++++++++++++++++++++++++++++++- src/main_c.zig | 90 ++-------------------------------- 6 files changed, 152 insertions(+), 115 deletions(-) create mode 100644 src/build_config.zig diff --git a/include/ghostty.h b/include/ghostty.h index 36935c815..ea8df672d 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -14,13 +14,14 @@ extern "C" { #include -typedef void *ghostty_t; +#define GHOSTTY_SUCCESS 0 + typedef void *ghostty_config_t; -ghostty_t ghostty_init(void); -ghostty_config_t ghostty_config_new(ghostty_t); -void ghostty_config_free(ghostty_t, ghostty_config_t); -void ghostty_config_load_string(ghostty_t, ghostty_config_t, const char *, uintptr_t); +int ghostty_init(void); +ghostty_config_t ghostty_config_new(); +void ghostty_config_free(ghostty_config_t); +void ghostty_config_load_string(ghostty_config_t, const char *, uintptr_t); void ghostty_config_finalize(ghostty_config_t); #ifdef __cplusplus diff --git a/macos/Sources/GhosttyApp.swift b/macos/Sources/GhosttyApp.swift index 819c6f1eb..e9429df57 100644 --- a/macos/Sources/GhosttyApp.swift +++ b/macos/Sources/GhosttyApp.swift @@ -15,6 +15,8 @@ struct GhosttyApp: App { var body: some Scene { WindowGroup { switch ghostty.readiness { + case .loading: + Text("Loading") case .error: ErrorView() case .ready: @@ -26,28 +28,27 @@ struct GhosttyApp: App { class GhosttyState: ObservableObject { enum Readiness { - case error, ready + case loading, error, ready } /// The readiness value of the state. - var readiness: Readiness { ghostty != nil ? .ready : .error } - - /// The ghostty global state. - var ghostty: ghostty_t? = nil + @Published var readiness: Readiness = .loading /// The ghostty global configuration. var config: ghostty_config_t? = nil init() { // Initialize ghostty global state. This happens once per process. - guard let g = ghostty_init() else { + guard ghostty_init() == GHOSTTY_SUCCESS else { GhosttyApp.logger.critical("ghostty_init failed") + readiness = .error return } // Initialize the global configuration. - guard let cfg = ghostty_config_new(g) else { + guard let cfg = ghostty_config_new() else { GhosttyApp.logger.critical("ghostty_config_new failed") + readiness = .error return } @@ -58,11 +59,11 @@ class GhosttyState: ObservableObject { // Finalize will make our defaults available. ghostty_config_finalize(cfg) - ghostty = g; - config = cfg; + config = cfg + readiness = .ready } deinit { - ghostty_config_free(ghostty, config) + ghostty_config_free(config) } } diff --git a/src/build_config.zig b/src/build_config.zig new file mode 100644 index 000000000..36a0ebec7 --- /dev/null +++ b/src/build_config.zig @@ -0,0 +1,35 @@ +//! Build options, available at comptime. Used to configure features. This +//! will reproduce some of the fields from builtin and build_options just +//! so we can limit the amount of imports we need AND give us the ability +//! to shim logic and values into them later. +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; + +/// The artifact we're producing. This can be used to determine if we're +/// building a standalone exe, an embedded lib, etc. +pub const artifact = Artifact.detect(); + +pub const Artifact = enum { + /// Standalone executable + exe, + + /// Embeddable library + lib, + + /// The WASM-targetted module. + wasm_module, + + pub fn detect() Artifact { + if (builtin.target.isWasm()) { + assert(builtin.output_mode == .Obj); + assert(builtin.link_mode == .Static); + return .wasm_module; + } + + return switch (builtin.output_mode) { + .Exe => .exe, + .Obj => .lib, + }; + } +}; diff --git a/src/config.zig b/src/config.zig index ece999dac..360959428 100644 --- a/src/config.zig +++ b/src/config.zig @@ -583,19 +583,19 @@ pub const Wasm = if (!builtin.target.isWasm()) struct {} else struct { } }; -// Wasm API. +// C API. pub const CAPI = struct { - const Ghostty = @import("main_c.zig").Ghostty; + const global = &@import("main.zig").state; const cli_args = @import("cli_args.zig"); /// Create a new configuration filled with the initial default values. - export fn ghostty_config_new(g: *Ghostty) ?*Config { - const result = g.alloc.create(Config) catch |err| { + export fn ghostty_config_new() ?*Config { + const result = global.alloc.create(Config) catch |err| { log.err("error allocating config err={}", .{err}); return null; }; - result.* = Config.default(g.alloc) catch |err| { + result.* = Config.default(global.alloc) catch |err| { log.err("error creating config err={}", .{err}); return null; }; @@ -603,30 +603,29 @@ pub const CAPI = struct { return result; } - export fn ghostty_config_free(g: *Ghostty, ptr: ?*Config) void { + export fn ghostty_config_free(ptr: ?*Config) void { if (ptr) |v| { v.deinit(); - g.alloc.destroy(v); + global.alloc.destroy(v); } } /// Load the configuration from a string in the same format as /// the file-based syntax for the desktop version of the terminal. export fn ghostty_config_load_string( - g: *Ghostty, self: *Config, str: [*]const u8, len: usize, ) void { - config_load_string_(g, self, str[0..len]) catch |err| { + config_load_string_(self, str[0..len]) catch |err| { log.err("error loading config err={}", .{err}); }; } - fn config_load_string_(g: *Ghostty, self: *Config, str: []const u8) !void { + fn config_load_string_(self: *Config, str: []const u8) !void { var fbs = std.io.fixedBufferStream(str); var iter = cli_args.lineIterator(fbs.reader()); - try cli_args.parse(Config, g.alloc, self, &iter); + try cli_args.parse(Config, global.alloc, self, &iter); } export fn ghostty_config_finalize(self: *Config) void { diff --git a/src/main.zig b/src/main.zig index b4b1b7baf..fc9b4d302 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3,6 +3,12 @@ const builtin = @import("builtin"); const options = @import("build_options"); const glfw = @import("glfw"); const macos = @import("macos"); +const tracy = @import("tracy"); +const internal_os = @import("os/main.zig"); +const xev = @import("xev"); +const fontconfig = @import("fontconfig"); +const harfbuzz = @import("harfbuzz"); +const renderer = @import("renderer.zig"); const xdg = @import("xdg.zig"); const App = @import("App.zig"); @@ -10,9 +16,14 @@ const cli_args = @import("cli_args.zig"); const Config = @import("config.zig").Config; const Ghostty = @import("main_c.zig").Ghostty; +/// Global process state. This is initialized in main() for exe artifacts +/// and by ghostty_init() for lib artifacts. This should ONLY be used by +/// the C API. The Zig API should NOT use any global state and should +/// rely on allocators being passed in as parameters. +pub var state: GlobalState = undefined; + pub fn main() !void { - var state: Ghostty = undefined; - Ghostty.init(&state); + state.init(); defer state.deinit(); const alloc = state.alloc; @@ -153,6 +164,80 @@ fn glfwErrorCallback(code: glfw.ErrorCode, desc: [:0]const u8) void { } } +/// This represents the global process state. There should only +/// be one of these at any given moment. This is extracted into a dedicated +/// struct because it is reused by main and the static C lib. +pub const GlobalState = struct { + const GPA = std.heap.GeneralPurposeAllocator(.{}); + + gpa: ?GPA, + alloc: std.mem.Allocator, + + pub fn init(self: *GlobalState) void { + // Output some debug information right away + std.log.info("dependency harfbuzz={s}", .{harfbuzz.versionString()}); + if (options.fontconfig) { + std.log.info("dependency fontconfig={d}", .{fontconfig.version()}); + } + std.log.info("renderer={}", .{renderer.Renderer}); + std.log.info("libxev backend={}", .{xev.backend}); + + // First things first, we fix our file descriptors + internal_os.fixMaxFiles(); + + // We need to make sure the process locale is set properly. Locale + // affects a lot of behaviors in a shell. + internal_os.ensureLocale(); + + // Initialize ourself to nothing so we don't have any extra state. + self.* = .{ + .gpa = null, + .alloc = undefined, + }; + errdefer self.deinit(); + + self.gpa = gpa: { + // Use the libc allocator if it is available beacuse it is WAY + // faster than GPA. We only do this in release modes so that we + // can get easy memory leak detection in debug modes. + if (builtin.link_libc) { + if (switch (builtin.mode) { + .ReleaseSafe, .ReleaseFast => true, + + // We also use it if we can detect we're running under + // Valgrind since Valgrind only instruments the C allocator + else => std.valgrind.runningOnValgrind() > 0, + }) break :gpa null; + } + + break :gpa GPA{}; + }; + + self.alloc = alloc: { + const base = if (self.gpa) |*value| + value.allocator() + else if (builtin.link_libc) + std.heap.c_allocator + else + unreachable; + + // If we're tracing, wrap the allocator + if (!tracy.enabled) break :alloc base; + var tracy_alloc = tracy.allocator(base, null); + break :alloc tracy_alloc.allocator(); + }; + } + + /// Cleans up the global state. This doesn't _need_ to be called but + /// doing so in dev modes will check for memory leaks. + pub fn deinit(self: *GlobalState) void { + if (self.gpa) |*value| { + // We want to ensure that we deinit the GPA because this is + // the point at which it will output if there were safety violations. + _ = value.deinit(); + } + } +}; test { _ = @import("Pty.zig"); _ = @import("Command.zig"); diff --git a/src/main_c.zig b/src/main_c.zig index fba2a61d3..a6823260b 100644 --- a/src/main_c.zig +++ b/src/main_c.zig @@ -9,13 +9,6 @@ const std = @import("std"); const assert = std.debug.assert; const builtin = @import("builtin"); -const options = @import("build_options"); -const fontconfig = @import("fontconfig"); -const harfbuzz = @import("harfbuzz"); -const renderer = @import("renderer.zig"); -const tracy = @import("tracy"); -const xev = @import("xev"); -const internal_os = @import("os/main.zig"); const main = @import("main.zig"); /// Global options so we can log. This is identical to main. @@ -25,85 +18,8 @@ pub usingnamespace @import("config.zig").CAPI; /// Initialize ghostty global state. It is possible to have more than /// one global state but it has zero practical benefit. -export fn ghostty_init() ?*Ghostty { +export fn ghostty_init() c_int { assert(builtin.link_libc); - const alloc = std.heap.c_allocator; - const g = alloc.create(Ghostty) catch return null; - Ghostty.init(g); - return g; + main.state.init(); + return 0; } - -/// This represents the global process state. There should only -/// be one of these at any given moment. This is extracted into a dedicated -/// struct because it is reused by main and the static C lib. -/// -/// init should be one of the first things ever called when using Ghostty. -pub const Ghostty = struct { - const GPA = std.heap.GeneralPurposeAllocator(.{}); - - gpa: ?GPA, - alloc: std.mem.Allocator, - - pub fn init(self: *Ghostty) void { - // Output some debug information right away - std.log.info("dependency harfbuzz={s}", .{harfbuzz.versionString()}); - if (options.fontconfig) { - std.log.info("dependency fontconfig={d}", .{fontconfig.version()}); - } - std.log.info("renderer={}", .{renderer.Renderer}); - std.log.info("libxev backend={}", .{xev.backend}); - - // First things first, we fix our file descriptors - internal_os.fixMaxFiles(); - - // We need to make sure the process locale is set properly. Locale - // affects a lot of behaviors in a shell. - internal_os.ensureLocale(); - - // Initialize ourself to nothing so we don't have any extra state. - self.* = .{ - .gpa = null, - .alloc = undefined, - }; - errdefer self.deinit(); - - self.gpa = gpa: { - // Use the libc allocator if it is available beacuse it is WAY - // faster than GPA. We only do this in release modes so that we - // can get easy memory leak detection in debug modes. - if (builtin.link_libc) { - if (switch (builtin.mode) { - .ReleaseSafe, .ReleaseFast => true, - - // We also use it if we can detect we're running under - // Valgrind since Valgrind only instruments the C allocator - else => std.valgrind.runningOnValgrind() > 0, - }) break :gpa null; - } - - break :gpa GPA{}; - }; - - self.alloc = alloc: { - const base = if (self.gpa) |*value| - value.allocator() - else if (builtin.link_libc) - std.heap.c_allocator - else - unreachable; - - // If we're tracing, wrap the allocator - if (!tracy.enabled) break :alloc base; - var tracy_alloc = tracy.allocator(base, null); - break :alloc tracy_alloc.allocator(); - }; - } - - pub fn deinit(self: *Ghostty) void { - if (self.gpa) |*value| { - // We want to ensure that we deinit the GPA because this is - // the point at which it will output if there were safety violations. - _ = value.deinit(); - } - } -};