freestanding support (#14076)

This commit is contained in:
Mitchell Hashimoto
2026-08-31 13:17:43 -07:00
committed by GitHub
9 changed files with 119 additions and 27 deletions

View File

@@ -94,6 +94,7 @@ jobs:
- build-cmake
- build-flatpak
- build-libghostty-vt
- build-libghostty-vt-freestanding
- build-libghostty-vt-wasm
- build-libghostty-vt-features
- build-libghostty-vt-android
@@ -649,6 +650,45 @@ jobs:
nix develop -c zig build -Demit-lib-vt \
-Dtarget=${{ matrix.target }}
build-libghostty-vt-freestanding:
strategy:
matrix:
include:
- target: riscv32-freestanding-eabi
cpu: baseline
- target: thumb-freestanding-eabi
cpu: cortex_m4
runs-on: namespace-profile-ghostty-sm
needs: test
env:
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Cache
uses: namespacelabs/nscloud-cache-action@c5f8dab7560444c4bf8dbc64f1b203431873c547 # v1.6.1
with:
path: |
/nix
/zig
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17
with:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Build
run: |
nix develop -c zig build -Demit-lib-vt \
-Dtarget=${{ matrix.target }} \
-Dcpu=${{ matrix.cpu }} \
-Doptimize=ReleaseSafe
build-libghostty-vt-wasm:
runs-on: namespace-profile-ghostty-sm
needs: test

View File

@@ -125,26 +125,36 @@ pub fn build(b: *std.Build) !void {
}
// libghostty-vt
const libghostty_vt_shared = shared: {
const native_freestanding = config.target.result.os.tag == .freestanding and
!config.target.result.cpu.arch.isWasm();
const libghostty_vt_shared: ?buildpkg.GhosttyLibVt = shared: {
if (config.target.result.cpu.arch.isWasm()) {
break :shared try buildpkg.GhosttyLibVt.initWasm(
b,
&mod,
);
}
if (native_freestanding) break :shared null;
break :shared try buildpkg.GhosttyLibVt.initShared(
b,
&mod,
);
};
libghostty_vt_shared.install(b.getInstallStep());
if (libghostty_vt_shared) |shared| {
shared.install(b.getInstallStep());
const type_schema_test = b.addSystemCommand(&.{"python3"});
type_schema_test.addFileArg(b.path("src/terminal/c/types-schema-verify.py"));
type_schema_test.addFileArg(b.path("src/terminal/c/types.schema.json"));
type_schema_test.addFileArg(libghostty_vt_shared.output);
test_lib_vt_schema_step.dependOn(&type_schema_test.step);
const type_schema_test = b.addSystemCommand(&.{"python3"});
type_schema_test.addFileArg(b.path("src/terminal/c/types-schema-verify.py"));
type_schema_test.addFileArg(b.path("src/terminal/c/types.schema.json"));
type_schema_test.addFileArg(shared.output);
test_lib_vt_schema_step.dependOn(&type_schema_test.step);
} else {
try test_lib_vt_schema_step.addError(
"cannot execute the ABI manifest for a native freestanding target",
.{},
);
}
// libghostty-vt static lib
const libghostty_vt_static = try buildpkg.GhosttyLibVt.initStatic(
@@ -167,6 +177,15 @@ pub fn build(b: *std.Build) !void {
libghostty_vt_static.output,
static_lib_name,
).step);
if (native_freestanding) {
b.getInstallStep().dependOn(&b.addInstallDirectory(.{
.source_dir = b.path("include/ghostty"),
.install_dir = .header,
.install_subdir = "ghostty",
.include_extensions = &.{".h"},
}).step);
}
}
// libghostty-vt xcframework (Apple only, universal binary).

View File

@@ -31,14 +31,17 @@
* function that accepts one,** and libghostty will use a default allocator.
* The default allocator will be libc malloc/free if libc is linked.
* Otherwise, a custom allocator is used (currently Zig's SMP allocator)
* that doesn't require any external dependencies.
* that doesn't require any external dependencies. On native freestanding
* targets, the default allocator always fails instead.
*
* ## Basic Usage
*
* For simple use cases, you can ignore this interface entirely by passing NULL
* as the allocator parameter to functions that accept one. This will use the
* default allocator (typically libc malloc/free, if libc is linked, but
* we provide our own default allocator if libc isn't linked).
* we provide our own default allocator if libc isn't linked). Native
* freestanding builds must provide a custom allocator for operations that
* allocate memory.
*
* To use a custom allocator:
* 1. Implement the GhosttyAllocatorVtable function pointers
@@ -76,7 +79,8 @@
*
* If you're not going to use a custom allocator, you can ignore all of
* this. All functions that take an allocator pointer allow NULL to use a
* default allocator.
* default allocator. Native freestanding builds must provide an allocator
* for operations that allocate memory.
*
* The interface is based on the Zig allocator interface. I'll say up front
* that it is easy to look at this interface and think "wow, this is really
@@ -183,7 +187,8 @@ typedef struct {
* For functions that take an allocator pointer, a NULL pointer indicates
* that the default allocator should be used. The default allocator will
* be libc malloc/free if we're linking to libc. If libc isn't linked,
* a custom allocator is used (currently Zig's SMP allocator).
* a custom allocator is used (currently Zig's SMP allocator). On native
* freestanding targets, the default allocator always fails instead.
*
* @ingroup allocator
*

View File

@@ -240,9 +240,10 @@ pub fn init(b: *std.Build, appVersion: []const u8, libVersion: []const u8) !Conf
"simd",
"Build with SIMD-accelerated code paths. Results in significant performance improvements.",
) orelse simd: {
// We can't build our SIMD dependencies for Wasm. Note that we may
// still use SIMD features in the Wasm-builds.
if (target.result.cpu.arch.isWasm()) break :simd false;
// We can't build our SIMD dependencies for Wasm or freestanding
// targets. Note that we may still use SIMD features in the Wasm-builds.
if (target.result.cpu.arch.isWasm() or
target.result.os.tag == .freestanding) break :simd false;
break :simd true;
};

View File

@@ -246,7 +246,9 @@ fn initLib(
// Enable PIC so the static library can be linked into PIE
// executables, which is the default on most Linux distributions.
lib.root_module.pic = true;
// Native freestanding targets don't have a dynamic loader and some,
// such as Xtensa, don't support PIC relocations.
lib.root_module.pic = target.result.os.tag != .freestanding;
}
if (target.result.os.tag == .windows) {

View File

@@ -34,6 +34,10 @@ pub fn default(c_alloc_: ?*const Allocator) std.mem.Allocator {
// Wasm
if (comptime builtin.target.cpu.arch.isWasm()) return std.heap.wasm_allocator;
// Freestanding targets don't have a default heap. Using the failing
// allocator makes a missing allocator show up as out-of-memory.
if (comptime builtin.os.tag == .freestanding) return std.mem.Allocator.failing;
// No libc, use the preferred allocator for releases which is the
// Zig SMP allocator.
return std.heap.smp_allocator;

View File

@@ -424,6 +424,15 @@ comptime {
pub const std_options: std.Options = opts: {
var options: std.Options = .{};
if (native_freestanding) {
// Freestanding targets don't have an OS page size. We still need an
// alignment for terminal page allocations, and 16 covers everything
// stored in a page without requiring 4 KiB-aligned embedded heaps.
options.page_size_min = 16;
options.page_size_max = 16;
options.allow_stack_tracing = false;
}
if (builtin.target.cpu.arch.isWasm()) {
// In non-debug modes, we want to ship effectively no logging
// warn and lower add ~200KB at the time of this comment.
@@ -464,10 +473,14 @@ pub const std_options: std.Options = opts: {
/// traces on panic, std.debug.print, etc.). These builds are for
/// development, where the roughly 160KB of binary size it costs is
/// worth it.
const debug_machinery: bool = builtin.is_test or switch (builtin.mode) {
.Debug, .ReleaseSafe => true,
.ReleaseFast, .ReleaseSmall => false,
};
const native_freestanding = builtin.target.os.tag == .freestanding and
!builtin.target.cpu.arch.isWasm();
const debug_machinery: bool = !native_freestanding and
(builtin.is_test or switch (builtin.mode) {
.Debug, .ReleaseSafe => true,
.ReleaseFast, .ReleaseSmall => false,
});
/// The panic handler for when this file is the root module.
///

View File

@@ -29,6 +29,8 @@ const Page = pagepkg.Page;
const Row = pagepkg.Row;
const log = std.log.scoped(.page_list);
const native_freestanding = builtin.os.tag == .freestanding and
!builtin.target.cpu.arch.isWasm();
/// The number of PageList.Nodes we preheat the pool with. A node is
/// a very small struct so we can afford to preheat many, but the exact
@@ -538,12 +540,19 @@ fn initialCapacity(cols: size.CellCountInt) Capacity {
return cap;
}
/// This is the page allocator we'll use for all our underlying
/// VM page allocations.
inline fn pageAllocator() Allocator {
/// Returns the allocator used for underlying page allocations.
///
/// `alloc` is the caller-provided allocator. It is used on native freestanding
/// targets, where no OS page allocator is available. Other targets select a
/// platform-specific allocator below.
inline fn pageAllocator(alloc: Allocator) Allocator {
// In tests we use our testing allocator so we can detect leaks.
if (builtin.is_test) return std.testing.allocator;
// Native freestanding targets don't have an OS page allocator, so use
// the allocator provided by the embedder.
if (native_freestanding) return alloc;
// On non-macOS we use our standard Zig page allocator.
if (!builtin.target.os.tag.isDarwin()) return std.heap.page_allocator;
@@ -614,7 +623,7 @@ pub fn init(
try tw.check(.init_memory_pool);
var pool = try MemoryPool.init(
alloc,
pageAllocator(),
pageAllocator(alloc),
page_preheat,
);
errdefer pool.deinit();
@@ -1117,7 +1126,7 @@ pub fn clone(
// Setup our pool
var pool: MemoryPool = try .init(
alloc,
pageAllocator(),
pageAllocator(alloc),
page_count,
);
errdefer pool.deinit();
@@ -7499,7 +7508,7 @@ pub const Builder = struct {
return .{
.pool = try MemoryPool.init(
alloc,
pageAllocator(),
pageAllocator(alloc),
page_preheat,
),
.options = options,

View File

@@ -263,8 +263,7 @@ pub const Options = struct {
/// targets, so it is always disabled there regardless of the
/// feature setting.
pub fn kittyGraphics(self: Options, target: std.Target) bool {
if (target.cpu.arch == .wasm32 and target.os.tag == .freestanding)
return false;
if (target.os.tag == .freestanding) return false;
return self.features.kitty_graphics;
}