From f3f9af612967154c419b63976bc5b0e718d57ab6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Apr 2026 20:23:45 -0700 Subject: [PATCH] pkg/highway: vendor and modify to remain all libc usage --- pkg/highway/build.zig | 12 +--- pkg/highway/src/cpp/abort.cc | 70 ++++++++++++++++++++++++ pkg/highway/src/cpp/bridge.cpp | 60 -------------------- pkg/highway/src/cpp/per_target.cc | 91 +++++++++++++++++++++++++++++++ pkg/highway/src/cpp/targets.cpp | 48 +++++++++++----- src/simd/codepoint_width.cpp | 20 ++++--- src/simd/vt.cpp | 2 - 7 files changed, 210 insertions(+), 93 deletions(-) create mode 100644 pkg/highway/src/cpp/abort.cc delete mode 100644 pkg/highway/src/cpp/bridge.cpp create mode 100644 pkg/highway/src/cpp/per_target.cc diff --git a/pkg/highway/build.zig b/pkg/highway/build.zig index e35349730..19b42da50 100644 --- a/pkg/highway/build.zig +++ b/pkg/highway/build.zig @@ -21,7 +21,7 @@ pub fn build(b: *std.Build) !void { }), .linkage = .static, }); - lib.linkLibC(); + lib.addIncludePath(b.path("src/cpp")); if (upstream_) |upstream| { lib.addIncludePath(upstream.path("")); module.addIncludePath(upstream.path("")); @@ -90,18 +90,12 @@ pub fn build(b: *std.Build) !void { } lib.addCSourceFiles(.{ .flags = flags.items, .files = &.{ - "src/cpp/bridge.cpp", + "src/cpp/abort.cc", + "src/cpp/per_target.cc", "src/cpp/targets.cpp", } }); if (upstream_) |upstream| { - lib.addCSourceFiles(.{ - .root = upstream.path(""), - .flags = flags.items, - .files = &.{ - "hwy/per_target.cc", - }, - }); lib.installHeadersDirectory( upstream.path("hwy"), "hwy", diff --git a/pkg/highway/src/cpp/abort.cc b/pkg/highway/src/cpp/abort.cc new file mode 100644 index 000000000..152619b0d --- /dev/null +++ b/pkg/highway/src/cpp/abort.cc @@ -0,0 +1,70 @@ +// Copyright 2019 Google LLC +// Copyright 2024 Arm Limited and/or its affiliates +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: BSD-3-Clause + +// Vendored from google/highway hwy/abort.cc at commit: +// 66486a10623fa0d72fe91260f96c892e41aceb06 +// +// Local modifications: +// - Removed stdio/stdlib/string/sanitizer-backed formatting and logging paths +// so this file no longer pulls in libc/libc++ symbols. +// - Replaced std::atomic storage with compiler atomics on plain function +// pointers to preserve thread-safe handler installation without libc++. +// - Kept only the Warn/Abort symbol surface Highway's runtime dispatch needs, +// with a trap-only fallback when no abort handler is installed. +// +// Why: +// - Ghostty only needs Highway's runtime dispatch support here, not its +// formatted stderr diagnostics. +// - Keeping this translation unit libc/libc++ free lets pkg/highway build as a +// small vendored shim around Zig-driven target detection. + +#include "hwy/abort.h" + +#include "hwy/base.h" + +namespace hwy { + +namespace { + +WarnFunc g_warn_func = nullptr; +AbortFunc g_abort_func = nullptr; + +} // namespace + +HWY_DLLEXPORT WarnFunc& GetWarnFunc() { + return g_warn_func; +} + +HWY_DLLEXPORT AbortFunc& GetAbortFunc() { + return g_abort_func; +} + +HWY_DLLEXPORT WarnFunc SetWarnFunc(WarnFunc func) { + return __atomic_exchange_n(&g_warn_func, func, __ATOMIC_SEQ_CST); +} + +HWY_DLLEXPORT AbortFunc SetAbortFunc(AbortFunc func) { + return __atomic_exchange_n(&g_abort_func, func, __ATOMIC_SEQ_CST); +} + +HWY_DLLEXPORT void HWY_FORMAT(3, 4) + Warn(const char* file, int line, const char* format, ...) { + WarnFunc handler = __atomic_load_n(&g_warn_func, __ATOMIC_SEQ_CST); + if (handler != nullptr) { + handler(file, line, format); + } +} + +HWY_DLLEXPORT HWY_NORETURN void HWY_FORMAT(3, 4) + Abort(const char* file, int line, const char* format, ...) { + AbortFunc handler = __atomic_load_n(&g_abort_func, __ATOMIC_SEQ_CST); + if (handler != nullptr) { + handler(file, line, format); + } + + __builtin_trap(); +} + +} // namespace hwy diff --git a/pkg/highway/src/cpp/bridge.cpp b/pkg/highway/src/cpp/bridge.cpp deleted file mode 100644 index 1ac0c0752..000000000 --- a/pkg/highway/src/cpp/bridge.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include -#include -#include - -#include - -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; - -} // 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, ...) { - if (WarnFunc func = __atomic_load_n(&g_warn_func, __ATOMIC_SEQ_CST)) { - func(file, line, format); - } -} - -HWY_NORETURN void Abort(const char* file, int line, const char* format, ...) { - if (AbortFunc func = __atomic_load_n(&g_abort_func, __ATOMIC_SEQ_CST)) { - func(file, line, format); - } - - __builtin_trap(); -} - -} // 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; -} -} diff --git a/pkg/highway/src/cpp/per_target.cc b/pkg/highway/src/cpp/per_target.cc new file mode 100644 index 000000000..44973ad3f --- /dev/null +++ b/pkg/highway/src/cpp/per_target.cc @@ -0,0 +1,91 @@ +// Copyright 2022 Google LLC +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Vendored from google/highway hwy/per_target.cc at commit: +// 66486a10623fa0d72fe91260f96c892e41aceb06 +// +// Local modifications: +// - Changed HWY_TARGET_INCLUDE from the upstream path to the local vendored +// filename so Highway's multi-pass include machinery resolves this copy. +// - Left the implementation otherwise identical to upstream. +// +// Why: +// - Ghostty vendors only the specific Highway .cc files it needs in this +// directory, so the original source-relative include path no longer exists. +// - Keeping the logic unchanged aside from the include path reduces fork +// maintenance cost while still allowing a minimal vendored source set. + +// Enable all targets so that calling Have* does not call into a null pointer. +#ifndef HWY_COMPILE_ALL_ATTAINABLE +#define HWY_COMPILE_ALL_ATTAINABLE +#endif +#include "hwy/per_target.h" + +#include +#include + +#undef HWY_TARGET_INCLUDE +#define HWY_TARGET_INCLUDE "per_target.cc" +#include "hwy/foreach_target.h" // IWYU pragma: keep +#include "hwy/highway.h" + +HWY_BEFORE_NAMESPACE(); +namespace hwy { +namespace HWY_NAMESPACE { +namespace { +int64_t GetTarget() { return HWY_TARGET; } +size_t GetVectorBytes() { return Lanes(ScalableTag()); } +bool GetHaveInteger64() { return HWY_HAVE_INTEGER64 != 0; } +bool GetHaveFloat16() { return HWY_HAVE_FLOAT16 != 0; } +bool GetHaveFloat64() { return HWY_HAVE_FLOAT64 != 0; } +} // namespace +// NOLINTNEXTLINE(google-readability-namespace-comments) +} // namespace HWY_NAMESPACE + +} // namespace hwy +HWY_AFTER_NAMESPACE(); + +#if HWY_ONCE +namespace hwy { +namespace { +HWY_EXPORT(GetTarget); +HWY_EXPORT(GetVectorBytes); +HWY_EXPORT(GetHaveInteger64); +HWY_EXPORT(GetHaveFloat16); +HWY_EXPORT(GetHaveFloat64); +} // namespace + +HWY_DLLEXPORT int64_t DispatchedTarget() { + return HWY_DYNAMIC_DISPATCH(GetTarget)(); +} + +HWY_DLLEXPORT size_t VectorBytes() { + return HWY_DYNAMIC_DISPATCH(GetVectorBytes)(); +} + +HWY_DLLEXPORT bool HaveInteger64() { + return HWY_DYNAMIC_DISPATCH(GetHaveInteger64)(); +} + +HWY_DLLEXPORT bool HaveFloat16() { + return HWY_DYNAMIC_DISPATCH(GetHaveFloat16)(); +} + +HWY_DLLEXPORT bool HaveFloat64() { + return HWY_DYNAMIC_DISPATCH(GetHaveFloat64)(); +} + +} // namespace hwy +#endif // HWY_ONCE diff --git a/pkg/highway/src/cpp/targets.cpp b/pkg/highway/src/cpp/targets.cpp index c5a64b50a..7977cd573 100644 --- a/pkg/highway/src/cpp/targets.cpp +++ b/pkg/highway/src/cpp/targets.cpp @@ -1,35 +1,49 @@ -#include -#include -#include -#include +// Vendored from google/highway hwy/targets.cc at commit: +// 66486a10623fa0d72fe91260f96c892e41aceb06 +// +// Local modifications: +// - Dropped upstream CPU feature probing and platform-specific detection code +// in favor of Ghostty's Zig-provided ghostty_hwy_detect_targets(). +// - Removed the HWY_WARN baseline-mismatch diagnostic path so this file does +// not depend on libc-backed formatting/logging. +// - Kept only the chosen-target bookkeeping and runtime dispatch state that +// Highway's HWY_DYNAMIC_DISPATCH machinery needs. +// - Added hwy_supported_targets() as a small C shim for Zig to query the final +// supported target mask. +// +// Why: +// - Ghostty wants a minimal vendored Highway runtime that avoids direct libc +// usage and lets Zig own target detection policy. +// - Narrowing this file to dispatch state makes the local fork easier to audit +// and maintain than carrying upstream's full platform detection surface. + +#include "hwy/targets.h" namespace hwy { extern "C" int64_t ghostty_hwy_detect_targets(); +// Vendored from Highway's hwy/targets.cc. Ghostty provides target detection in +// Zig, so this TU only retains the runtime dispatch/chosen-target state. static int64_t DetectTargets() { int64_t bits = HWY_SCALAR | HWY_EMU128; -#if (HWY_ARCH_X86 || HWY_ARCH_ARM) && HWY_HAVE_RUNTIME_DISPATCH +#if (HWY_ARCH_X86 || HWY_ARCH_ARM || HWY_ARCH_PPC || HWY_ARCH_S390X || \ + HWY_ARCH_RISCV || HWY_ARCH_LOONGARCH) && \ + HWY_HAVE_RUNTIME_DISPATCH bits |= ghostty_hwy_detect_targets(); #else bits |= HWY_ENABLED_BASELINE; #endif - if ((bits & HWY_ENABLED_BASELINE) != HWY_ENABLED_BASELINE) { - const uint64_t bits_u = static_cast(bits); - const uint64_t enabled = static_cast(HWY_ENABLED_BASELINE); - HWY_WARN("CPU supports 0x%08x%08x, software requires 0x%08x%08x\n", - static_cast(bits_u >> 32), - static_cast(bits_u & 0xFFFFFFFF), - static_cast(enabled >> 32), - static_cast(enabled & 0xFFFFFFFF)); - } - return bits; } +// When running tests, this value can be set to the mocked supported targets +// mask. Only written to from a single thread before the test starts. static int64_t supported_targets_for_test_ = 0; + +// Mask of targets disabled at runtime with DisableTargets. static int64_t supported_mask_ = LimitsMax(); HWY_DLLEXPORT void DisableTargets(int64_t disabled_targets) { @@ -59,3 +73,7 @@ HWY_DLLEXPORT ChosenTarget& GetChosenTarget() { } } // namespace hwy + +extern "C" int64_t hwy_supported_targets() { + return hwy::SupportedTargets(); +} diff --git a/src/simd/codepoint_width.cpp b/src/simd/codepoint_width.cpp index 7e5fe7d2f..7f0dfd87c 100644 --- a/src/simd/codepoint_width.cpp +++ b/src/simd/codepoint_width.cpp @@ -7,8 +7,14 @@ #ifndef GHOSTTY_SIMD_CPW_HELPERS_ #define GHOSTTY_SIMD_CPW_HELPERS_ -#include -#include +#ifdef NDEBUG +#define GHOSTTY_SIMD_ASSERT(cond) ((void)0) +#else +#define GHOSTTY_SIMD_ASSERT(cond) \ + do { \ + if (!(cond)) __builtin_trap();\ + } while (0) +#endif // Replacement for std::size() that works without libc++. template @@ -249,8 +255,8 @@ static_assert(array_size(nsm_gte16) == array_size(nsm_lte16)); /// Handles 16-bit codepoints. template int8_t CodepointWidth16(D d, uint16_t input) { - assert(input > 0xFF); - assert(input <= 0xFFFF); + GHOSTTY_SIMD_ASSERT(input > 0xFF); + GHOSTTY_SIMD_ASSERT(input <= 0xFFFF); const size_t N = hn::Lanes(d); const hn::Vec input_vec = Set(d, input); @@ -287,7 +293,7 @@ int8_t CodepointWidth16(D d, uint16_t input) { return 2; } } - assert(i >= 7); // We should have checked all the ranges. + GHOSTTY_SIMD_ASSERT(i >= 7); // We should have checked all the ranges. } { @@ -353,7 +359,7 @@ int8_t CodepointWidth16(D d, uint16_t input) { /// Handles codepoints larger than 16-bit. template int8_t CodepointWidth32(D d, T input) { - assert(input > 0xFFFF); + GHOSTTY_SIMD_ASSERT(input > 0xFFFF); const size_t N = hn::Lanes(d); const hn::Vec input_vec = Set(d, input); @@ -379,7 +385,7 @@ int8_t CodepointWidth32(D d, T input) { return 2; } } - assert(i >= 2); // We should have checked all the ranges. + GHOSTTY_SIMD_ASSERT(i >= 2); // We should have checked all the ranges. } { diff --git a/src/simd/vt.cpp b/src/simd/vt.cpp index 1179c3773..5bf4147d5 100644 --- a/src/simd/vt.cpp +++ b/src/simd/vt.cpp @@ -5,8 +5,6 @@ #include #include -#include -#include #include #include