pkg/highway: drop libc++ dependency from vendored hwy (#12238)

The vendored Highway package was being built with libc++ even though
Ghostty only uses its runtime target selection and dispatch support.
That pulled in extra C++ runtime baggage from upstream support files
such as abort, timer, print, and benchmark helpers.

Build Highway in HWY_NO_LIBCXX mode, only compile the target dispatch
sources we actually need, and compile Ghostty's SIMD translation units
with the same define so the header ABI stays consistent. Replace the
upstream abort implementation with a small local bridge that provides
Highway's Warn/Abort hooks and the target-query shim without depending
on libc++.

This keeps the Highway archive down to the dispatch pieces Ghostty uses
while preserving the existing dynamic dispatch behavior. The bridge is
documented so it is clear why Ghostty carries this small local
replacement.

We still depend on libc++ for other reasons, but I figure we should just
trim it down as needed. 😄
This commit is contained in:
Mitchell Hashimoto
2026-04-11 14:41:55 -07:00
committed by GitHub
4 changed files with 99 additions and 15 deletions

View File

@@ -1,8 +1,89 @@
#include <hwy/abort.h>
#include <hwy/base.h>
#include <hwy/targets.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
namespace hwy {
namespace {
// Highway's upstream abort.cc pulls in libc++ even when the rest of the
// library is compiled with HWY_NO_LIBCXX. Ghostty only needs Highway's dynamic
// dispatch/runtime target selection, so we provide the tiny Warn/Abort surface
// that targets.cc/per_target.cc expect and keep the package free of libc++.
WarnFunc g_warn_func = nullptr;
AbortFunc g_abort_func = nullptr;
// Mirror the upstream behavior closely enough for Highway's internal callers:
// format into a fixed buffer, fall back to a generic error if formatting fails,
// and then dispatch to either the registered hook or stderr.
void format_message(const char* format, va_list args, char* buffer, size_t size) {
const int written = vsnprintf(buffer, size, format, args);
if (written < 0) {
snprintf(buffer, size, "%s", "failed to format highway message");
}
}
} // namespace
WarnFunc& GetWarnFunc() {
return g_warn_func;
}
AbortFunc& GetAbortFunc() {
return g_abort_func;
}
WarnFunc SetWarnFunc(WarnFunc func) {
// Highway documents these setters as thread-safe. Using the compiler builtin
// keeps that guarantee without depending on std::atomic.
return __atomic_exchange_n(&g_warn_func, func, __ATOMIC_SEQ_CST);
}
AbortFunc SetAbortFunc(AbortFunc func) {
return __atomic_exchange_n(&g_abort_func, func, __ATOMIC_SEQ_CST);
}
void Warn(const char* file, int line, const char* format, ...) {
char message[1024];
va_list args;
va_start(args, format);
format_message(format, args, message, sizeof(message));
va_end(args);
if (WarnFunc func = g_warn_func) {
func(file, line, message);
return;
}
fprintf(stderr, "%s:%d: %s\n", file, line, message);
}
HWY_NORETURN void Abort(const char* file, int line, const char* format, ...) {
char message[1024];
va_list args;
va_start(args, format);
format_message(format, args, message, sizeof(message));
va_end(args);
if (AbortFunc func = g_abort_func) {
func(file, line, message);
} else {
fprintf(stderr, "%s:%d: %s\n", file, line, message);
}
abort();
}
} // namespace hwy
extern "C" {
// Zig reads HWY_SUPPORTED_TARGETS via this C shim so it can keep its target
// enum in sync with the vendored Highway build without parsing C++ headers.
int64_t hwy_supported_targets() {
return HWY_SUPPORTED_TARGETS;
}

View File

@@ -21,14 +21,6 @@ pub fn build(b: *std.Build) !void {
.linkage = .static,
});
lib.linkLibC();
// On MSVC, we must not use linkLibCpp because Zig unconditionally
// passes -nostdinc++ and then adds its bundled libc++/libc++abi
// include paths, which conflict with MSVC's own C++ runtime headers.
// The MSVC SDK include directories (added via linkLibC) contain
// both C and C++ headers, so linkLibCpp is not needed.
if (target.result.abi != .msvc) {
lib.linkLibCpp();
}
if (upstream_) |upstream| {
lib.addIncludePath(upstream.path(""));
module.addIncludePath(upstream.path(""));
@@ -47,6 +39,10 @@ pub fn build(b: *std.Build) !void {
var flags: std.ArrayList([]const u8) = .empty;
defer flags.deinit(b.allocator);
try flags.appendSlice(b.allocator, &.{
// Highway can avoid libc++ entirely as long as all users compile
// against the headers with the same define.
"-DHWY_NO_LIBCXX",
// Avoid changing binaries based on the current time and date.
"-Wno-builtin-macro-redefined",
"-D__DATE__=\"redacted\"",
@@ -103,13 +99,11 @@ pub fn build(b: *std.Build) !void {
.root = upstream.path(""),
.flags = flags.items,
.files = &.{
"hwy/abort.cc",
"hwy/aligned_allocator.cc",
"hwy/nanobenchmark.cc",
// These provide the runtime target selection used by
// HWY_DYNAMIC_DISPATCH. The benchmark, timer, print, and
// aligned allocator support files are unused by Ghostty.
"hwy/per_target.cc",
"hwy/print.cc",
"hwy/targets.cc",
"hwy/timer.cc",
},
});
lib.installHeadersDirectory(

View File

@@ -753,6 +753,7 @@ pub fn addSimd(
) !void {
const target = m.resolved_target.?;
const optimize = m.optimize.?;
const system_highway = b.systemIntegrationOption("highway", .{ .default = false });
// Simdutf
if (b.systemIntegrationOption("simdutf", .{})) {
@@ -771,7 +772,7 @@ pub fn addSimd(
}
// Highway
if (b.systemIntegrationOption("highway", .{ .default = false })) {
if (system_highway) {
m.linkSystemLibrary("libhwy", dynamic_link_opts);
} else {
if (b.lazyDependency("highway", .{
@@ -830,6 +831,14 @@ pub fn addSimd(
"-std=c++17",
);
// Keep our SIMD sources in the same Highway header mode as the
// vendored package build so HWY's inline dispatch/runtime helpers
// have a consistent ABI.
if (!system_highway) try flags.append(
b.allocator,
"-DHWY_NO_LIBCXX",
);
// Disable ubsan for MSVC to avoid undefined references to
// __ubsan_handle_* symbols that require a runtime we don't link
// and bundle. Hopefully we can fix this one day since ubsan is nice!

View File

@@ -3,8 +3,8 @@
#define HWY_TARGET_INCLUDE "simd/codepoint_width.cpp" // this file
#include <hwy/foreach_target.h> // must come before highway.h
#include <hwy/highway.h>
#include <hwy/print-inl.h>
#include <algorithm>
#include <cassert>
#include <iterator>