mitchell's touchups

- benchmark: avoid buffers to avoid a memcpy
- build: keep frame pointers on macOS. There was some debug changes from
  Zig 0.15 and this helps. Also, Apple actually requires/expects x29 to
  always be a frame pointer.
- build/macos: force libSystem symbols instead of compiler-rt
- global: add InitOpts.tool so that ghostty-gen/bench can parse their
  own actions in `+action`
- quirks: provide our own vectorized memset. see the comment for more
  details why.
- synthetic: fix UB by accessing global.io before it was initialized
- terminal/hash_map: force inline for unique repr types. Zig 0.15
  inlined and 0.16 doesn't, measured a huge slowdown in hyperlink
  benchmarks.
- terminal: add explicit `@Vector` usage for storing a run of identical cells
  as well as for scanning printable cells. This auto-vectorized in Zig
  0.15 but not in Zig 0.16. This produces the same assembly.
- unicode: properties and LUT need power-of-two backing integer to avoid
  bad LLVM codegen
This commit is contained in:
Mitchell Hashimoto
2026-07-20 20:33:23 -07:00
parent e8525c0fd9
commit f2a7652aba
35 changed files with 936 additions and 151 deletions

2
.gitignore vendored
View File

@@ -14,7 +14,7 @@ zig-pkg/
build-cmake/
CMakeCache.txt
CMakeFiles/
/build.zig.zon.bak
build.zig.zon.bak
/result*
/.nixos-test-history
example/*.wasm

View File

@@ -11,62 +11,62 @@
// Aro/translate-c itself.
.translate_c = .{
.lazy = true,
.url = "https://codeberg.org/vancluever/translate-c/archive/a916baf225a3cfc93a982225462db612604ae290.tar.gz",
.url = "https://deps.files.ghostty.org/translate_c-a916baf225a3cfc93a982225462db612604ae290.tar.gz",
.hash = "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB",
},
// Zig libs
.libxev = .{
// mitchellh/libxev
.url = "https://github.com/mitchellh/libxev/archive/9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz",
.url = "https://deps.files.ghostty.org/libxev-9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz",
.hash = "libxev-0.0.0-86vtcwIRFADbH4hk-EjROXxlrKIRPQdA41XiTSytYO-F",
.lazy = true,
},
.vaxis = .{
// rockorager/libvaxis
.url = "https://github.com/rockorager/libvaxis/archive/1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz",
.url = "https://deps.files.ghostty.org/vaxis-1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz",
.hash = "vaxis-0.6.0-BWNV_CrbCQCscGpzsAlR402rYQ_tV3aAl081c2iRRkka",
.lazy = true,
},
.z2d = .{
// vancluever/z2d
.url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.11.0.tar.gz",
.url = "https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz",
.hash = "z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR",
.lazy = true,
},
.zig_objc = .{
// mitchellh/zig-objc
.url = "https://github.com/mitchellh/zig-objc/archive/c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz",
.url = "https://deps.files.ghostty.org/zig_objc-c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz",
.hash = "zig_objc-0.0.0-Ir_Sp9gsAQCPAJc0oF5xoWePHWP6Y6tCphDeyNUThJoi",
.lazy = true,
},
.zig_js = .{
// mitchellh/zig-js
.url = "https://github.com/mitchellh/zig-js/archive/3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz",
.url = "https://deps.files.ghostty.org/zig_js-3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz",
.hash = "zig_js-0.0.0-rjCAV7-GAADvMTBL7lPMuvDk7xgS9PCMIZWiOUXLZSlj",
.lazy = true,
},
.uucode = .{
// jacobsandlund/uucode
.url = "https://github.com/jacobsandlund/uucode/archive/2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz",
.url = "https://deps.files.ghostty.org/uucode-2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz",
.hash = "uucode-0.2.0-ZZjBPlK5VADj7fdoq7G8LIHzD5o6FSkcBXXrRWr4jnrA",
},
.zig_wayland = .{
// codeberg ifreund/zig-wayland
.url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.6.0.tar.gz",
.url = "https://deps.files.ghostty.org/wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX.tar.gz",
.hash = "wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX",
.lazy = true,
},
.zf = .{
// natecraddock/zf
.url = "https://github.com/natecraddock/zf/archive/c35c421f84895193246db06c40683c1a30e616ef.tar.gz",
.url = "https://deps.files.ghostty.org/zf-c35c421f84895193246db06c40683c1a30e616ef.tar.gz",
.hash = "zf-0.11.0-OIRy8X-RAAAwaRXHMYpj2uvBnuGTZWEE_3V7acqHQNtW",
.lazy = true,
},
.gobject = .{
// https://github.com/ghostty-org/zig-gobject based on zig_gobject
// Temporary until we generate them at build time automatically.
.url = "https://github.com/ghostty-org/zig-gobject/releases/download/0.8.0-2026-04-23-26-1/ghostty-gobject-0.8.0-2026-04-23-26-1.tar.zst",
.url = "https://deps.files.ghostty.org/gobject-2026-04-23-26-1.tar.zst",
.hash = "gobject-0.3.1-Skun7E1KnwBGMX5nslHYG1yWHaSevywxQO8oM7tTOgIp",
.lazy = true,
},
@@ -99,7 +99,7 @@
.lazy = true,
},
.wayland_protocols = .{
.url = "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz",
.url = "https://deps.files.ghostty.org/N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA.tar.gz",
.hash = "N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA",
.lazy = true,
},

32
build.zig.zon.json generated
View File

@@ -36,7 +36,7 @@
},
"gobject-0.3.1-Skun7E1KnwBGMX5nslHYG1yWHaSevywxQO8oM7tTOgIp": {
"name": "gobject",
"url": "https://github.com/ghostty-org/zig-gobject/releases/download/0.8.0-2026-04-23-26-1/ghostty-gobject-0.8.0-2026-04-23-26-1.tar.zst",
"url": "https://deps.files.ghostty.org/gobject-2026-04-23-26-1.tar.zst",
"hash": "sha256-cZGf9a1+sDeORKWYIdzQ/5KLT7l/9pgPc2cIbqfBc4o="
},
"N-V-__8AALiNBAA-_0gprYr92CjrMj1I5bqNu0TSJOnjFNSr": {
@@ -56,7 +56,7 @@
},
"N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI": {
"name": "imgui",
"url": "https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz",
"url": "https://deps.files.ghostty.org/N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI.tar.gz",
"hash": "sha256-yBbCDox18+Fa6Gc1DnmSVQLRpqhZOLsac7iSfl8x+cs="
},
"N-V-__8AALZGBAAS5NLVH-c8eC-6VtCdcH-9nUvVfUSkWS__": {
@@ -76,7 +76,7 @@
},
"libxev-0.0.0-86vtcwIRFADbH4hk-EjROXxlrKIRPQdA41XiTSytYO-F": {
"name": "libxev",
"url": "https://github.com/mitchellh/libxev/archive/9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz",
"url": "https://deps.files.ghostty.org/libxev-9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz",
"hash": "sha256-fOU1oxIxfoEgoLuWz7fVX6M+zmqpo7gqZObWiH/aDE0="
},
"N-V-__8AAG3RoQEyRC2Vw7Qoro5SYBf62IHn3HjqtNVY6aWK": {
@@ -116,12 +116,12 @@
},
"translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB": {
"name": "translate_c",
"url": "https://codeberg.org/vancluever/translate-c/archive/a916baf225a3cfc93a982225462db612604ae290.tar.gz",
"url": "https://deps.files.ghostty.org/translate_c-a916baf225a3cfc93a982225462db612604ae290.tar.gz",
"hash": "sha256-i7TSSiLU9YGtLtEjgVqy2jSnKLDTeKHX+EFuXEMXJ+U="
},
"uucode-0.2.0-ZZjBPlK5VADj7fdoq7G8LIHzD5o6FSkcBXXrRWr4jnrA": {
"name": "uucode",
"url": "https://github.com/jacobsandlund/uucode/archive/2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz",
"url": "https://deps.files.ghostty.org/uucode-2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz",
"hash": "sha256-R5RXW5tWIaDq5JOF2+oWd5YOYOyns6WH7f687WE+b20="
},
"vaxis-0.6.0-BWNV_MjFCQCs9UDHiRkrgw_ayeiPkzOe4xVbaAqXkUWW": {
@@ -131,7 +131,7 @@
},
"vaxis-0.6.0-BWNV_CrbCQCscGpzsAlR402rYQ_tV3aAl081c2iRRkka": {
"name": "vaxis",
"url": "https://github.com/rockorager/libvaxis/archive/1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz",
"url": "https://deps.files.ghostty.org/vaxis-1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz",
"hash": "sha256-z3J3w+oYapfAZZR+MvWXwg+th1hePDA1TptnKFO4r+M="
},
"N-V-__8AAKrHGAAs2shYq8UkE6bGcR1QJtLTyOE_lcosMn6t": {
@@ -139,16 +139,16 @@
"url": "https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz",
"hash": "sha256-6kGR1o5DdnflHzqs3ieCmBAUTpMdOXoyfcYDXiw5xQ0="
},
"N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA": {
"name": "wayland_protocols",
"url": "https://deps.files.ghostty.org/N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA.tar.gz",
"hash": "sha256-3S3xSrX0EDgleq7cxLX7msDuAY8/D5SvkJcCjmDTMiM="
},
"N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S": {
"name": "wayland_protocols",
"url": "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz",
"hash": "sha256-XO3K3egbdeYPI+XoO13SuOtO+5+Peb16NH0UiusFMPg="
},
"N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA": {
"name": "wayland_protocols",
"url": "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz",
"hash": "sha256-3S3xSrX0EDgleq7cxLX7msDuAY8/D5SvkJcCjmDTMiM="
},
"N-V-__8AAAzZywE3s51XfsLbP9eyEw57ae9swYB9aGB6fCMs": {
"name": "wuffs",
"url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz",
@@ -156,27 +156,27 @@
},
"z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR": {
"name": "z2d",
"url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.11.0.tar.gz",
"url": "https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz",
"hash": "sha256-/Yje2P+ro7IrPodQ26YKk7RsPSYj1Cqx6bdrmykXEKA="
},
"zf-0.11.0-OIRy8X-RAAAwaRXHMYpj2uvBnuGTZWEE_3V7acqHQNtW": {
"name": "zf",
"url": "https://github.com/natecraddock/zf/archive/c35c421f84895193246db06c40683c1a30e616ef.tar.gz",
"url": "https://deps.files.ghostty.org/zf-c35c421f84895193246db06c40683c1a30e616ef.tar.gz",
"hash": "sha256-LHurWwqK8jhP8AZd/CXrMJNeVsKftRCTN31/EgEgKaA="
},
"zig_js-0.0.0-rjCAV7-GAADvMTBL7lPMuvDk7xgS9PCMIZWiOUXLZSlj": {
"name": "zig_js",
"url": "https://github.com/mitchellh/zig-js/archive/3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz",
"url": "https://deps.files.ghostty.org/zig_js-3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz",
"hash": "sha256-Q1643wb/MCX0QkPa9QEY9xeQbqpC2IOrjER0ZyL3mH0="
},
"zig_objc-0.0.0-Ir_Sp9gsAQCPAJc0oF5xoWePHWP6Y6tCphDeyNUThJoi": {
"name": "zig_objc",
"url": "https://github.com/mitchellh/zig-objc/archive/c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz",
"url": "https://deps.files.ghostty.org/zig_objc-c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz",
"hash": "sha256-t8cTTJm0crcoGAyOqXO0wB2w7Yit80J2Orwr6prd/G0="
},
"wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX": {
"name": "zig_wayland",
"url": "https://codeberg.org/ifreund/zig-wayland/archive/v0.6.0.tar.gz",
"url": "https://deps.files.ghostty.org/wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX.tar.gz",
"hash": "sha256-3m/ITNhZUJ/5uD/Tqm+0uZSktGoYgWF5oldOqOCUkIE="
},
"zigimg-0.1.0-8_eo2oyaFwBZwJpmqPkCfVXWBrHcqbYwmrp1I6bTD3lI": {

40
build.zig.zon.nix generated
View File

@@ -164,7 +164,7 @@ in
name = "gobject-0.3.1-Skun7E1KnwBGMX5nslHYG1yWHaSevywxQO8oM7tTOgIp";
path = fetchZigArtifact {
name = "gobject";
url = "https://github.com/ghostty-org/zig-gobject/releases/download/0.8.0-2026-04-23-26-1/ghostty-gobject-0.8.0-2026-04-23-26-1.tar.zst";
url = "https://deps.files.ghostty.org/gobject-2026-04-23-26-1.tar.zst";
hash = "sha256-cZGf9a1+sDeORKWYIdzQ/5KLT7l/9pgPc2cIbqfBc4o=";
unpack = true;
};
@@ -200,7 +200,7 @@ in
name = "N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI";
path = fetchZigArtifact {
name = "imgui";
url = "https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz";
url = "https://deps.files.ghostty.org/N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI.tar.gz";
hash = "sha256-yBbCDox18+Fa6Gc1DnmSVQLRpqhZOLsac7iSfl8x+cs=";
unpack = false;
};
@@ -236,7 +236,7 @@ in
name = "libxev-0.0.0-86vtcwIRFADbH4hk-EjROXxlrKIRPQdA41XiTSytYO-F";
path = fetchZigArtifact {
name = "libxev";
url = "https://github.com/mitchellh/libxev/archive/9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz";
url = "https://deps.files.ghostty.org/libxev-9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz";
hash = "sha256-fOU1oxIxfoEgoLuWz7fVX6M+zmqpo7gqZObWiH/aDE0=";
unpack = true;
};
@@ -308,7 +308,7 @@ in
name = "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB";
path = fetchZigArtifact {
name = "translate_c";
url = "https://codeberg.org/vancluever/translate-c/archive/a916baf225a3cfc93a982225462db612604ae290.tar.gz";
url = "https://deps.files.ghostty.org/translate_c-a916baf225a3cfc93a982225462db612604ae290.tar.gz";
hash = "sha256-i7TSSiLU9YGtLtEjgVqy2jSnKLDTeKHX+EFuXEMXJ+U=";
unpack = true;
};
@@ -317,7 +317,7 @@ in
name = "uucode-0.2.0-ZZjBPlK5VADj7fdoq7G8LIHzD5o6FSkcBXXrRWr4jnrA";
path = fetchZigArtifact {
name = "uucode";
url = "https://github.com/jacobsandlund/uucode/archive/2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz";
url = "https://deps.files.ghostty.org/uucode-2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz";
hash = "sha256-R5RXW5tWIaDq5JOF2+oWd5YOYOyns6WH7f687WE+b20=";
unpack = true;
};
@@ -335,7 +335,7 @@ in
name = "vaxis-0.6.0-BWNV_CrbCQCscGpzsAlR402rYQ_tV3aAl081c2iRRkka";
path = fetchZigArtifact {
name = "vaxis";
url = "https://github.com/rockorager/libvaxis/archive/1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz";
url = "https://deps.files.ghostty.org/vaxis-1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz";
hash = "sha256-z3J3w+oYapfAZZR+MvWXwg+th1hePDA1TptnKFO4r+M=";
unpack = true;
};
@@ -349,6 +349,15 @@ in
unpack = false;
};
}
{
name = "N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA";
path = fetchZigArtifact {
name = "wayland_protocols";
url = "https://deps.files.ghostty.org/N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA.tar.gz";
hash = "sha256-3S3xSrX0EDgleq7cxLX7msDuAY8/D5SvkJcCjmDTMiM=";
unpack = false;
};
}
{
name = "N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S";
path = fetchZigArtifact {
@@ -358,15 +367,6 @@ in
unpack = false;
};
}
{
name = "N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA";
path = fetchZigArtifact {
name = "wayland_protocols";
url = "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz";
hash = "sha256-3S3xSrX0EDgleq7cxLX7msDuAY8/D5SvkJcCjmDTMiM=";
unpack = false;
};
}
{
name = "N-V-__8AAAzZywE3s51XfsLbP9eyEw57ae9swYB9aGB6fCMs";
path = fetchZigArtifact {
@@ -380,7 +380,7 @@ in
name = "z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR";
path = fetchZigArtifact {
name = "z2d";
url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.11.0.tar.gz";
url = "https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz";
hash = "sha256-/Yje2P+ro7IrPodQ26YKk7RsPSYj1Cqx6bdrmykXEKA=";
unpack = true;
};
@@ -389,7 +389,7 @@ in
name = "zf-0.11.0-OIRy8X-RAAAwaRXHMYpj2uvBnuGTZWEE_3V7acqHQNtW";
path = fetchZigArtifact {
name = "zf";
url = "https://github.com/natecraddock/zf/archive/c35c421f84895193246db06c40683c1a30e616ef.tar.gz";
url = "https://deps.files.ghostty.org/zf-c35c421f84895193246db06c40683c1a30e616ef.tar.gz";
hash = "sha256-LHurWwqK8jhP8AZd/CXrMJNeVsKftRCTN31/EgEgKaA=";
unpack = true;
};
@@ -398,7 +398,7 @@ in
name = "zig_js-0.0.0-rjCAV7-GAADvMTBL7lPMuvDk7xgS9PCMIZWiOUXLZSlj";
path = fetchZigArtifact {
name = "zig_js";
url = "https://github.com/mitchellh/zig-js/archive/3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz";
url = "https://deps.files.ghostty.org/zig_js-3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz";
hash = "sha256-Q1643wb/MCX0QkPa9QEY9xeQbqpC2IOrjER0ZyL3mH0=";
unpack = true;
};
@@ -407,7 +407,7 @@ in
name = "zig_objc-0.0.0-Ir_Sp9gsAQCPAJc0oF5xoWePHWP6Y6tCphDeyNUThJoi";
path = fetchZigArtifact {
name = "zig_objc";
url = "https://github.com/mitchellh/zig-objc/archive/c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz";
url = "https://deps.files.ghostty.org/zig_objc-c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz";
hash = "sha256-t8cTTJm0crcoGAyOqXO0wB2w7Yit80J2Orwr6prd/G0=";
unpack = true;
};
@@ -416,7 +416,7 @@ in
name = "wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX";
path = fetchZigArtifact {
name = "zig_wayland";
url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.6.0.tar.gz";
url = "https://deps.files.ghostty.org/wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX.tar.gz";
hash = "sha256-3m/ITNhZUJ/5uD/Tqm+0uZSktGoYgWF5oldOqOCUkIE=";
unpack = true;
};

24
build.zig.zon.txt generated
View File

@@ -1,9 +1,9 @@
git+https://github.com/rockorager/libvaxis.git#c1e1f23be38951c425cdf31af455ba23ef178940
git+https://github.com/zigimg/zigimg#d695acd97c02e57bb151e8f659d1280f5cd6ca70
https://codeberg.org/ifreund/zig-wayland/archive/v0.6.0.tar.gz
https://codeberg.org/vancluever/translate-c/archive/a916baf225a3cfc93a982225462db612604ae290.tar.gz
https://deps.files.ghostty.org/DearBindings_v0.17_ImGui_v1.92.5-docking.tar.gz
https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz
https://deps.files.ghostty.org/N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI.tar.gz
https://deps.files.ghostty.org/N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA.tar.gz
https://deps.files.ghostty.org/NerdFontsSymbolsOnly-3.4.0.tar.gz
https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz
https://deps.files.ghostty.org/fontconfig-2.14.2.tar.gz
@@ -11,28 +11,28 @@ https://deps.files.ghostty.org/freetype-1220b81f6ecfb3fd222f76cf9106fecfa6554ab0
https://deps.files.ghostty.org/gettext-0.24.tar.gz
https://deps.files.ghostty.org/ghostty-themes-release-20260713-155359-c3968b3.tgz
https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz
https://deps.files.ghostty.org/gobject-2026-04-23-26-1.tar.zst
https://deps.files.ghostty.org/gtk4-layer-shell-1.1.0.tar.gz
https://deps.files.ghostty.org/harfbuzz-11.0.0.tar.xz
https://deps.files.ghostty.org/highway-66486a10623fa0d72fe91260f96c892e41aceb06.tar.gz
https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz
https://deps.files.ghostty.org/libxev-9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz
https://deps.files.ghostty.org/libxml2-2.11.5.tar.gz
https://deps.files.ghostty.org/oniguruma-1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb.tar.gz
https://deps.files.ghostty.org/pixels-12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806.tar.gz
https://deps.files.ghostty.org/plasma_wayland_protocols-12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566.tar.gz
https://deps.files.ghostty.org/sentry-1220446be831adcca918167647c06c7b825849fa3fba5f22da394667974537a9c77e.tar.gz
https://deps.files.ghostty.org/spirv_cross-1220fb3b5586e8be67bc3feb34cbe749cf42a60d628d2953632c2f8141302748c8da.tar.gz
https://deps.files.ghostty.org/translate_c-a916baf225a3cfc93a982225462db612604ae290.tar.gz
https://deps.files.ghostty.org/uucode-2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz
https://deps.files.ghostty.org/vaxis-1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz
https://deps.files.ghostty.org/wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX.tar.gz
https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz
https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz
https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz
https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz
https://deps.files.ghostty.org/zf-c35c421f84895193246db06c40683c1a30e616ef.tar.gz
https://deps.files.ghostty.org/zig_js-3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz
https://deps.files.ghostty.org/zig_objc-c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
https://github.com/ghostty-org/zig-gobject/releases/download/0.8.0-2026-04-23-26-1/ghostty-gobject-0.8.0-2026-04-23-26-1.tar.zst
https://github.com/jacobsandlund/uucode/archive/2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz
https://github.com/mitchellh/libxev/archive/9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz
https://github.com/mitchellh/zig-js/archive/3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz
https://github.com/mitchellh/zig-objc/archive/c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz
https://github.com/natecraddock/zf/archive/c35c421f84895193246db06c40683c1a30e616ef.tar.gz
https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz
https://github.com/rockorager/libvaxis/archive/1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz
https://github.com/vancluever/arocc/archive/ecbc5c799574e0da2758a961b12efa586007f03c.tar.gz
https://github.com/vancluever/z2d/archive/refs/tags/v0.11.0.tar.gz
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz

View File

@@ -43,7 +43,7 @@
},
{
"type": "archive",
"url": "https://github.com/ghostty-org/zig-gobject/releases/download/0.8.0-2026-04-23-26-1/ghostty-gobject-0.8.0-2026-04-23-26-1.tar.zst",
"url": "https://deps.files.ghostty.org/gobject-2026-04-23-26-1.tar.zst",
"dest": "vendor/p/gobject-0.3.1-Skun7E1KnwBGMX5nslHYG1yWHaSevywxQO8oM7tTOgIp",
"sha256": "642e324b3644c00464a2161e4af497f73cbbe8ce02a4c0edb92aacb365748047"
},
@@ -67,7 +67,7 @@
},
{
"type": "archive",
"url": "https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz",
"url": "https://deps.files.ghostty.org/N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI.tar.gz",
"dest": "vendor/p/N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI",
"sha256": "c816c20e8c75f3e15ae867350e79925502d1a6a85938bb1a73b8927e5f31f9cb"
},
@@ -91,7 +91,7 @@
},
{
"type": "archive",
"url": "https://github.com/mitchellh/libxev/archive/9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz",
"url": "https://deps.files.ghostty.org/libxev-9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz",
"dest": "vendor/p/libxev-0.0.0-86vtcwIRFADbH4hk-EjROXxlrKIRPQdA41XiTSytYO-F",
"sha256": "e2b9b224cd78101156abb8f46479a51f6cce66c4815c632856e20a1440dd02b7"
},
@@ -139,13 +139,13 @@
},
{
"type": "archive",
"url": "https://codeberg.org/vancluever/translate-c/archive/a916baf225a3cfc93a982225462db612604ae290.tar.gz",
"url": "https://deps.files.ghostty.org/translate_c-a916baf225a3cfc93a982225462db612604ae290.tar.gz",
"dest": "vendor/p/translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB",
"sha256": "4fd2a2b21dbc49c22bea6854a59a7568dc77183760531ace3cf8da1267696afd"
},
{
"type": "archive",
"url": "https://github.com/jacobsandlund/uucode/archive/2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz",
"url": "https://deps.files.ghostty.org/uucode-2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz",
"dest": "vendor/p/uucode-0.2.0-ZZjBPlK5VADj7fdoq7G8LIHzD5o6FSkcBXXrRWr4jnrA",
"sha256": "7e76fc7fab1e7ac728c52b35bbb3e5b8c639841abfc7fe1a4bcb13050594bc9e"
},
@@ -157,7 +157,7 @@
},
{
"type": "archive",
"url": "https://github.com/rockorager/libvaxis/archive/1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz",
"url": "https://deps.files.ghostty.org/vaxis-1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz",
"dest": "vendor/p/vaxis-0.6.0-BWNV_CrbCQCscGpzsAlR402rYQ_tV3aAl081c2iRRkka",
"sha256": "3ea050e905887c46bca1a2c18e41d6edf574c1aa6ebfbc5cf3c52846275e4fa8"
},
@@ -169,15 +169,15 @@
},
{
"type": "archive",
"url": "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz",
"dest": "vendor/p/N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S",
"sha256": "5cedcadde81b75e60f23e5e83b5dd2b8eb4efb9f8f79bd7a347d148aeb0530f8"
"url": "https://deps.files.ghostty.org/N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA.tar.gz",
"dest": "vendor/p/N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA",
"sha256": "dd2df14ab5f41038257aaedcc4b5fb9ac0ee018f3f0f94af9097028e60d33223"
},
{
"type": "archive",
"url": "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz",
"dest": "vendor/p/N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA",
"sha256": "dd2df14ab5f41038257aaedcc4b5fb9ac0ee018f3f0f94af9097028e60d33223"
"url": "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz",
"dest": "vendor/p/N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S",
"sha256": "5cedcadde81b75e60f23e5e83b5dd2b8eb4efb9f8f79bd7a347d148aeb0530f8"
},
{
"type": "archive",
@@ -187,31 +187,31 @@
},
{
"type": "archive",
"url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.11.0.tar.gz",
"url": "https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz",
"dest": "vendor/p/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR",
"sha256": "d48b117307f767ca379bf31bd171880e66dcafb89eca4a8f923222619992fcb4"
},
{
"type": "archive",
"url": "https://github.com/natecraddock/zf/archive/c35c421f84895193246db06c40683c1a30e616ef.tar.gz",
"url": "https://deps.files.ghostty.org/zf-c35c421f84895193246db06c40683c1a30e616ef.tar.gz",
"dest": "vendor/p/zf-0.11.0-OIRy8X-RAAAwaRXHMYpj2uvBnuGTZWEE_3V7acqHQNtW",
"sha256": "ca4bc109fd9d97cedae2f5bdb879cc78c346d43c14a34e3a2abd2ee9208c8547"
},
{
"type": "archive",
"url": "https://github.com/mitchellh/zig-js/archive/3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz",
"url": "https://deps.files.ghostty.org/zig_js-3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz",
"dest": "vendor/p/zig_js-0.0.0-rjCAV7-GAADvMTBL7lPMuvDk7xgS9PCMIZWiOUXLZSlj",
"sha256": "a2ff488a244301f4e59469a5232aad6869b01acc50f7d7ed2647c029a57d4b3b"
},
{
"type": "archive",
"url": "https://github.com/mitchellh/zig-objc/archive/c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz",
"url": "https://deps.files.ghostty.org/zig_objc-c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz",
"dest": "vendor/p/zig_objc-0.0.0-Ir_Sp9gsAQCPAJc0oF5xoWePHWP6Y6tCphDeyNUThJoi",
"sha256": "831eaec554bb23ef20229f1e705d8f53e8a29c829840bb8d0f5edb05c76d6df0"
},
{
"type": "archive",
"url": "https://codeberg.org/ifreund/zig-wayland/archive/v0.6.0.tar.gz",
"url": "https://deps.files.ghostty.org/wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX.tar.gz",
"dest": "vendor/p/wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX",
"sha256": "759a632e36a948e0e412d2d74a43a69d2f34e65d40289a16af7aa3725852ef25"
},

View File

@@ -2,23 +2,34 @@
# This script downloads external dependencies from build.zig.zon.json that
# are not already mirrored at deps.files.ghostty.org, saves them to a local
# directory, and updates build.zig.zon to point to the new mirror URLs.
# directory, and updates the build.zig.zon files (the root one as well as
# every pkg/*/build.zig.zon) to point to the new mirror URLs.
#
# The downloaded files are unmodified so their checksums and content hashes
# will match the originals.
#
# After running this script, the files in the output directory can be uploaded
# to blob storage, and build.zig.zon will already be updated with the new URLs.
# to blob storage, and the build.zig.zon files will already be updated with
# the new URLs.
def main [
--output: string = "tmp-mirror", # Output directory for the mirrored files
--prefix: string = "https://deps.files.ghostty.org/", # Final URL prefix to ignore
--dry-run, # Print what would be downloaded without downloading
] {
let script_dir = ($env.CURRENT_FILE | path dirname)
let input_file = ($script_dir | path join ".." ".." "build.zig.zon.json")
let zon_file = ($script_dir | path join ".." ".." "build.zig.zon")
let root_dir = ($script_dir | path join ".." ".." | path expand)
let input_file = ($root_dir | path join "build.zig.zon.json")
let output_dir = $output
# All build.zig.zon files that may reference external URLs: the root
# one plus every package under pkg/. build.zig.zon.json is generated
# from the full dependency tree so it covers all of these.
let zon_files = (
[($root_dir | path join "build.zig.zon")]
| append (glob ($root_dir | path join "pkg" "*" "build.zig.zon"))
| sort
)
# Ensure the output directory exists
mkdir $output_dir
@@ -75,22 +86,34 @@ def main [
if $dry_run {
print "Dry run complete - no files were downloaded\n"
print $"Would update ($url_replacements | length) URLs in build.zig.zon"
} else {
print "All dependencies downloaded successfully\n"
print $"Updating ($zon_file)..."
# Backup the old file
let backup_file = $"($zon_file).bak"
cp $zon_file $backup_file
print $"Backed up to ($backup_file)"
mut zon_content = (open $zon_file)
for replacement in $url_replacements {
$zon_content = ($zon_content | str replace $replacement.old $replacement.new)
}
$zon_content | save -f $zon_file
}
print $"Updated ($url_replacements | length) URLs in build.zig.zon"
# Apply the URL replacements to every build.zig.zon that references them.
for zon_file in $zon_files {
mut zon_content = (open --raw $zon_file)
mut count = 0
for replacement in $url_replacements {
if ($zon_content | str contains $replacement.old) {
$zon_content = ($zon_content | str replace --all $replacement.old $replacement.new)
$count = $count + 1
}
}
if $count == 0 {
continue
}
let relative_path = ($zon_file | path relative-to $root_dir)
if $dry_run {
print $"Would update ($count) URLs in ($relative_path)"
} else {
# Backup the old file
let backup_file = $"($zon_file).bak"
cp $zon_file $backup_file
$zon_content | save -f $zon_file
print $"Updated ($count) URLs in ($relative_path), backup at ($relative_path).bak"
}
}
}

View File

@@ -15,7 +15,7 @@
.imgui = .{
// https://github.com/ocornut/imgui
.url = "https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz",
.url = "https://deps.files.ghostty.org/N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI.tar.gz",
.hash = "N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI",
.lazy = true,
},

View File

@@ -15,7 +15,7 @@
.lazy = true,
},
.translate_c = .{
.url = "https://codeberg.org/vancluever/translate-c/archive/a916baf225a3cfc93a982225462db612604ae290.tar.gz",
.url = "https://deps.files.ghostty.org/translate_c-a916baf225a3cfc93a982225462db612604ae290.tar.gz",
.hash = "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB",
.lazy = true,
},

View File

@@ -5,7 +5,7 @@
.paths = .{""},
.dependencies = .{
.translate_c = .{
.url = "https://codeberg.org/vancluever/translate-c/archive/a916baf225a3cfc93a982225462db612604ae290.tar.gz",
.url = "https://deps.files.ghostty.org/translate_c-a916baf225a3cfc93a982225462db612604ae290.tar.gz",
.hash = "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB",
.lazy = true,
},

View File

@@ -6,7 +6,7 @@
.dependencies = .{
.apple_sdk = .{ .path = "../apple-sdk" },
.translate_c = .{
.url = "https://codeberg.org/vancluever/translate-c/archive/a916baf225a3cfc93a982225462db612604ae290.tar.gz",
.url = "https://deps.files.ghostty.org/translate_c-a916baf225a3cfc93a982225462db612604ae290.tar.gz",
.hash = "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB",
.lazy = true,
},

View File

@@ -5,7 +5,7 @@
.dependencies = .{
.translate_c = .{
.lazy = true,
.url = "https://codeberg.org/vancluever/translate-c/archive/a916baf225a3cfc93a982225462db612604ae290.tar.gz",
.url = "https://deps.files.ghostty.org/translate_c-a916baf225a3cfc93a982225462db612604ae290.tar.gz",
.hash = "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB",
},

View File

@@ -113,8 +113,10 @@ fn step(ptr: *anyopaque) Benchmark.Error!void {
// aren't currently IO bound.
const f = self.data_f orelse return;
var read_buf: [64 * 1024]u8 align(std.atomic.cache_line) = undefined;
var f_reader = f.reader(global.io(), &read_buf);
// Unbuffered: readSliceShort below reads directly into `buf`,
// avoiding a per-chunk memcpy through an intermediate reader
// buffer that would pollute the measurement.
var f_reader = f.reader(global.io(), &.{});
const r = &f_reader.interface;
// This buffer size matches the read buffer size used by the

View File

@@ -46,7 +46,7 @@ pub const Action = enum {
/// An entrypoint for the benchmark CLI.
pub fn main(init: std.process.Init) !void {
try global.init(.{ .main = init });
try global.init(.{ .tool = init });
const alloc = std.heap.c_allocator;
const action_ = try cli.action.detectArgs(Action, alloc, init.minimal.args);
const action = action_ orelse return error.NoAction;

View File

@@ -669,6 +669,17 @@ pub fn fromOptions() Config {
};
}
/// Whether release artifacts should omit frame pointers.
///
/// Stripped release builds omit them in general, but we always keep frame
/// pointers on Apple platforms: the Apple arm64 ABI expects x29 to
/// be a valid frame pointer and Apple's profiling and crash reporting
/// tools rely on it for backtraces.
pub fn omitFramePointer(self: *const Config) bool {
if (self.target.result.os.tag.isDarwin()) return false;
return self.strip;
}
/// Returns the minimum OS version for the given OS tag. This shouldn't
/// be used generally, it should only be used for Darwin-based OS currently.
pub fn osVersionMin(tag: std.Target.Os.Tag) ?std.Target.Query.OsVersion {

View File

@@ -18,7 +18,7 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty
.target = cfg.target,
.optimize = cfg.optimize,
.strip = cfg.strip,
.omit_frame_pointer = cfg.strip,
.omit_frame_pointer = cfg.omitFramePointer(),
.unwind_tables = if (cfg.strip) .none else .sync,
}),
// Crashes on x86_64 self-hosted on 0.15.1

View File

@@ -1,9 +1,11 @@
const GhosttyLib = @This();
const std = @import("std");
const builtin = @import("builtin");
const RunStep = std.Build.Step.Run;
const CombineArchivesStep = @import("CombineArchivesStep.zig");
const Config = @import("Config.zig");
const LibsystemOverrideStep = @import("LibsystemOverrideStep.zig");
const SharedDeps = @import("SharedDeps.zig");
const LipoStep = @import("LipoStep.zig");
@@ -27,7 +29,7 @@ pub fn initStatic(
.target = deps.config.target,
.optimize = deps.config.optimize,
.strip = deps.config.strip,
.omit_frame_pointer = deps.config.strip,
.omit_frame_pointer = deps.config.omitFramePointer(),
.unwind_tables = if (deps.config.strip) .none else .sync,
.link_libc = true,
}),
@@ -57,9 +59,19 @@ pub fn initStatic(
const combined = CombineArchivesStep.create(b, deps.config.target, "ghostty-internal", lib_list.items);
combined.step.dependOn(&lib.step);
// On Darwin, prefer libSystem's libc/libm over the bundled
// compiler-rt for consumers of this archive. See
// libsystem_override.sh for details. This is a no-op elsewhere.
const override = LibsystemOverrideStep.create(
b,
deps.config.target,
combined.output,
"libghostty-internal.a",
);
return .{
.step = combined.step,
.output = combined.output,
.step = override.step orelse combined.step,
.output = override.output,
// Static libraries cannot have dSYMs because they aren't linked.
.dsym = null,
@@ -88,7 +100,7 @@ pub fn initShared(
.target = deps.config.target,
.optimize = deps.config.optimize,
.strip = deps.config.strip,
.omit_frame_pointer = deps.config.strip,
.omit_frame_pointer = deps.config.omitFramePointer(),
.unwind_tables = if (deps.config.strip) .none else .sync,
}),

View File

@@ -5,6 +5,7 @@ const builtin = @import("builtin");
const assert = std.debug.assert;
const RunStep = std.Build.Step.Run;
const CombineArchivesStep = @import("CombineArchivesStep.zig");
const LibsystemOverrideStep = @import("LibsystemOverrideStep.zig");
const Config = @import("Config.zig");
const GhosttyZig = @import("GhosttyZig.zig");
const LipoStep = @import("LipoStep.zig");
@@ -295,22 +296,44 @@ fn initLib(
const combined = CombineArchivesStep.create(b, target, "ghostty-vt", sources.items);
combined.step.dependOn(&lib.step);
// On Darwin, prefer libSystem's libc/libm over the bundled
// compiler-rt for consumers of this archive. See GhosttyLib
// and libsystem_override.sh for details.
const override = LibsystemOverrideStep.create(
b,
target,
combined.output,
"libghostty-vt-fat.a",
);
return .{
.step = combined.step,
.step = override.step orelse combined.step,
.artifact = &b.addInstallArtifact(lib, .{}).step,
.kind = kind,
.output = combined.output,
.output = override.output,
.dsym = dsymutil,
.pkg_config = if (pcs) |v| v.shared else null,
.pkg_config_static = if (pcs) |v| v.static else null,
};
}
// Same libSystem preference for the plain (no vendored SIMD)
// static archive; a no-op off Darwin.
const override: LibsystemOverrideStep.Result = if (kind == .static) LibsystemOverrideStep.create(
b,
target,
lib.getEmittedBin(),
"libghostty-vt-static.a",
) else .{
.step = null,
.output = lib.getEmittedBin(),
};
return .{
.step = &lib.step,
.step = override.step orelse &lib.step,
.artifact = &b.addInstallArtifact(lib, .{}).step,
.kind = kind,
.output = lib.getEmittedBin(),
.output = override.output,
.dsym = dsymutil,
.pkg_config = if (pcs) |v| v.shared else null,
.pkg_config_static = if (pcs) |v| v.static else null,

View File

@@ -0,0 +1,51 @@
//! Wraps a Darwin static archive with the libsystem_override.sh
//! post-processing step so that consumers linking the archive bind
//! well-known libc/libm symbols (memcpy, memmove, memset, cos, sin,
//! ...) to Apple's libSystem instead of the bundled Zig compiler-rt.
//! See src/build/libsystem_override.sh for the full rationale.
//!
//! This applies to every static archive we produce, public
//! (e.g. libghostty-vt) and app-internal alike: the compiler-rt
//! bundle must stay because the few symbols libSystem does not
//! provide (f128 conversions, *q math, sincos*, ___zig_probe_stack)
//! have live references, e.g. from the bundled ubsan runtime and
//! x86_64 stack probing in Debug builds.
//!
//! Note shared libraries can't use either approach: Zig 0.16 links
//! compiler-rt as an eagerly-included object, so its definitions are
//! already bound into the dylib at link time and cannot be repaired
//! post-hoc. That needs an upstream Zig fix.
const std = @import("std");
const builtin = @import("builtin");
pub const Result = struct {
/// The step performing the override, or null if the override
/// doesn't apply to this target/host (in which case `output` is
/// the unmodified input).
step: ?*std.Build.Step,
output: std.Build.LazyPath,
};
/// Post-process the given static archive on Darwin. Requires a Darwin
/// host for the Apple toolchain (nmedit); in all other configurations
/// this is a no-op and the input is returned unmodified, which is
/// functional (the bundled compiler-rt symbols are used), just slower.
pub fn create(
b: *std.Build,
target: std.Build.ResolvedTarget,
input: std.Build.LazyPath,
out_name: []const u8,
) Result {
if (!target.result.os.tag.isDarwin() or
comptime !builtin.os.tag.isDarwin())
{
return .{ .step = null, .output = input };
}
const run = b.addSystemCommand(&.{"/bin/sh"});
run.setName("libsystem override");
run.addFileArg(b.path("src/build/libsystem_override.sh"));
run.addFileArg(input);
const output = run.addOutputFileArg(out_name);
return .{ .step = &run.step, .output = output };
}

123
src/build/libsystem_override.sh Executable file
View File

@@ -0,0 +1,123 @@
#!/bin/sh
#
# Darwin-only: rewrite a static archive so that consumers linking it bind
# libc/libm symbols to Apple's libSystem instead of the bundled Zig
# compiler-rt. This uses the Apple toolchain (xcrun, nmedit) and libSystem
# semantics, so it must run on a Darwin host for a Darwin target.
#
# Background: our static library bundles Zig's compiler-rt, which defines
# strong global implementations of libc/libm functions (memcpy, memmove,
# memset, cos, sin, ...). When a consumer (e.g. the macOS app via the
# XCFramework) links this archive, ld64 resolves those symbols from the
# archive instead of libSystem, silently replacing Apple's highly
# optimized implementations (_platform_memmove and friends, vectorized
# libm) with compiler-rt's generic ones. We measured Zig 0.16 (LLVM 21)
# compiler-rt memmove costing several percent of PTY throughput on
# scroll-region workloads versus libSystem's.
#
# The mechanism is to localize (make non-external) the libSystem-provided
# symbols defined by the compiler_rt.o archive member. This keeps
# compiler_rt.o functional for the intrinsics that libSystem does NOT
# provide (e.g. the f128 conversions ___extenddftf2/___extendxftf2, *q
# math, sincos*) while letting every other archive member bind the
# well-known libc/libm symbols to libSystem at final link.
#
# usage: libsystem_override.sh <input.a> <output.a>
set -eu
in="$1"
out="$2"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
cp -f "$in" "$out"
chmod u+w "$out"
# The symbols to prefer from libSystem. Everything listed here is a
# stable macOS export (verified against libSystem's link surface).
# Notably NOT listed (not exported by libSystem): the *q (f128)
# variants, sincos/sincosf/sincosl, and all ___-prefixed compiler
# intrinsics except the fortify _chk wrappers below.
cat >"$tmp/localize.txt" <<'EOF'
_bcmp
_memcmp
_memcpy
_memmove
_memset
_strlen
___memcpy_chk
___memmove_chk
___memset_chk
___strcat_chk
___strcpy_chk
_ceil
_ceilf
_ceill
_cos
_cosf
_cosl
_exp
_exp2
_exp2f
_exp2l
_expf
_expl
_fabs
_fabsf
_fabsl
_floor
_floorf
_floorl
_fma
_fmaf
_fmal
_fmax
_fmaxf
_fmaxl
_fmin
_fminf
_fminl
_fmod
_fmodf
_fmodl
_log
_log10
_log10f
_log10l
_log2
_log2f
_log2l
_logf
_logl
_round
_roundf
_roundl
_sin
_sinf
_sinl
_sqrt
_sqrtf
_sqrtl
_tan
_tanf
_tanl
_trunc
_truncf
_truncl
EOF
cd "$tmp"
xcrun ar x "$out" compiler_rt.o
chmod 644 compiler_rt.o
# nmedit takes a keep-list (-s): all current global definitions except
# the ones we want to localize.
xcrun nm -g compiler_rt.o | awk '$2 ~ /^[A-TV-Z]$/ {print $3}' | sort -u >all.txt
sort -u localize.txt >loc.txt
comm -23 all.txt loc.txt >keep.txt
xcrun nmedit -s keep.txt compiler_rt.o
xcrun ar r "$out" compiler_rt.o
xcrun ranlib "$out" 2>/dev/null || true

View File

@@ -204,7 +204,7 @@ pub const Path = union(enum) {
defer dir.close(global.io());
const abs = abs: {
const dir_len = dir.realPathFile(global.io(), ".", &buf) catch |err| {
const abs_len = dir.realPathFile(global.io(), path, &buf) catch |err| {
if (err == error.FileNotFound) {
// The file doesn't exist. Try to resolve the relative path
// another way.
@@ -229,7 +229,7 @@ pub const Path = union(enum) {
return;
};
break :abs buf[0..dir_len];
break :abs buf[0..abs_len];
};
log.debug(

View File

@@ -35,6 +35,14 @@ var state: ?GlobalState = undefined;
pub const InitOpts = union(enum) {
main: std.process.Init,
/// Same as `main` but for auxiliary tool binaries (e.g. ghostty-bench
/// and ghostty-gen) that have their own CLI action namespace. This
/// skips detection of ghostty app CLI actions, since tool actions
/// (e.g. `+terminal-stream`) are not valid app actions and would
/// otherwise cause init to fail with InvalidAction.
tool: std.process.Init,
c: struct {
argc: usize,
argv: [*][*:0]u8,
@@ -52,11 +60,11 @@ pub fn init(opts: InitOpts) !void {
.gpa = null,
.alloc = undefined,
.environ = switch (opts) {
.main => |m| m.minimal.environ,
.main, .tool => |m| m.minimal.environ,
.c => |c| c.environ,
},
.args = switch (opts) {
.main => |m| m.minimal.args,
.main, .tool => |m| m.minimal.args,
// TODO: Using the C API from Windows is unsupported at this time.
//
// When do we plan on supporting Windows, it's recommended to
@@ -116,11 +124,16 @@ pub fn init(opts: InitOpts) !void {
errdefer freeTmpDir(self.alloc, self.tmp_dir_path.?);
// We first try to parse any action that we may be executing.
self.action = try cli.action.detectArgs(
cli.ghostty.Action,
self.alloc,
self.args,
);
// Tool binaries (ghostty-bench, ghostty-gen) have their own action
// namespace and detect their own actions, so we skip detection here.
self.action = switch (opts) {
.main, .c => try cli.action.detectArgs(
cli.ghostty.Action,
self.alloc,
self.args,
),
.tool => null,
};
// If we have an action executing, we disable logging by default
// since we write to stderr we don't want logs messing up our

View File

@@ -145,6 +145,12 @@ comptime {
// If we're building the C library (vs. the Zig module) then
// we want to reference the C API so that it gets exported.
if (@import("root") == lib) {
// Force-reference our memset override so its export is
// emitted. This must stay inside the root guard so that
// downstream Zig module consumers don't get the override
// injected into their binaries. See quirks_memset.zig.
_ = @import("quirks_memset.zig");
const c = terminal.c_api;
@export(&c.key_event_new, .{ .name = "ghostty_key_event_new" });
@export(&c.key_event_free, .{ .name = "ghostty_key_event_free" });

View File

@@ -21,6 +21,12 @@ pub const std_options: std.Options = if (@hasDecl(entrypoint, "std_options"))
else
.{};
comptime {
// Force-reference our memset override so its export is emitted.
// See quirks_memset.zig for details on why this exists.
_ = @import("quirks_memset.zig");
}
test {
// Zig 0.16.0 has made test logging more strict. Now, *anything* that gets
// printed to stderr results in a "failed command" message, even if the
@@ -28,4 +34,5 @@ test {
// log spam in general), we bump the default testing log level to error.
std.testing.log_level = std.log.Level.err;
_ = entrypoint;
_ = @import("quirks_memset.zig");
}

View File

@@ -1,3 +1,9 @@
const benchmark = @import("benchmark/main.zig");
comptime {
// Force-reference our memset override so its export is emitted.
// See quirks_memset.zig for details on why this exists.
_ = @import("quirks_memset.zig");
}
pub const main = benchmark.cli.main;

View File

@@ -44,6 +44,10 @@ comptime {
// Our benchmark API. We probably want to gate this on a build
// config in the future but for now we always just export it.
_ = @import("benchmark/main.zig").CApi;
// Force-reference our memset override so its export is emitted.
// See quirks_memset.zig for details on why this exists.
_ = @import("quirks_memset.zig");
}
/// ghostty_info_s

View File

@@ -1,3 +1,9 @@
const synthetic = @import("synthetic/main.zig");
comptime {
// Force-reference our memset override so its export is emitted.
// See quirks_memset.zig for details on why this exists.
_ = @import("quirks_memset.zig");
}
pub const main = synthetic.cli.main;

322
src/quirks_memset.zig Normal file
View File

@@ -0,0 +1,322 @@
//! Provides a fast `memset` symbol that overrides the slow scalar
//! implementation provided by Zig 0.16's compiler_rt.
//!
//! Consider deleting when upstream ships an optimized compiler_rt
//! memset (tracked by ziglang/zig#32091, maybe fixed at
//! ziglang/zig#35754). To verify it is safe to delete, check that
//! the disassembly of `memset` in a ReleaseFast binary is vectorized
//! (or at least not a byte-at-a-time loop).
//!
//! ## Background
//!
//! Zig 0.16.0 globally disabled LLVM loop auto-vectorization to work
//! around an LLVM 21 miscompilation (llvm/llvm-project#186922, see
//! the 0.16.0 release notes). compiler_rt's memset is a naive
//! byte-at-a-time loop that relied entirely on auto-vectorization,
//! so it now compiles to a scalar loop roughly 24x slower than what
//! Zig 0.15 produced.
//!
//! In benchmarks this made `ghostty-bench +terminal-stream` 2.8x slower on
//! ASCII input (memset was 63% of all executed instructions!).
//!
//! Because compiler_rt's symbol is weak, exporting a strong `memset`
//! from our own code transparently overrides it everywhere. The
//! implementation below is manually vectorized with `@Vector`, which
//! does not depend on the disabled loop vectorizer.
//!
//! Other mem functions deliberately not overridden:
//!
//! - memcpy/memmove: compiler_rt's implementations are manually
//! vectorized upstream ("memcpyFast") and remain fast.
//! - memcmp/bcmp/strlen: also scalar in 0.16, but they never showed
//! up in our profiles.
//!
//! This file has no effect unless it is referenced from an artifact
//! root (e.g. `comptime { _ = @import("quirks_memset.zig"); }`).
//! It must NOT be imported from shared code, otherwise downstream
//! consumers of our Zig modules would get this export injected into
//! their binaries.
//!
//! ## References
//!
//! I referenced the musl asm + c memset implementation (MIT licensed).
//! The primary thing I took away from that is the dc zva trick for
//! aarch64. The remainder is fairly obvious memset work.
const std = @import("std");
const builtin = @import("builtin");
comptime {
// Strong linkage when we control the final link (executables,
// shared libraries), weak otherwise.
const linkage: std.builtin.GlobalLinkage = switch (builtin.output_mode) {
.Exe => .strong,
.Lib => switch (builtin.link_mode) {
.dynamic => .strong,
.static => .weak,
},
.Obj => .weak,
};
// Whether the override is emitted:
//
// 1. On targets without SIMD we disable, since compiler_rt's
// scalar operations are going to be just as good.
// 2. The C object format target uses strong linkage which
// conflits with ours and errors.
// 3. Weak COFF builds fatally error because MSVC's linker
// errors when two identical linked symbols exist. MSVC has
// CRT which links so we don't need this there anyways.
const enabled =
std.simd.suggestVectorLength(u8) != null and
builtin.object_format != .c and
!(linkage == .weak and builtin.object_format == .coff);
if (enabled) @export(&memset, .{
.name = "memset",
.linkage = linkage,
// Hidden so that shared library builds (libghostty,
// libghostty-vt) resolve this internally without exporting
// it to their host applications.
.visibility = .hidden,
});
}
/// Bytes stored per loop iteration. Twice the native vector size so
/// each iteration issues two vector stores (e.g. `stp q0, q0` on
/// aarch64). Capped at 128 both because the small path below relies
/// on len < 128 once the loop is skipped, and because wider single
/// iterations stop paying off anyway (e.g. on scalable-vector
/// targets that suggest very large lengths).
const vec_bytes = @min(128, 2 * (std.simd.suggestVectorLength(u8) orelse 8));
/// Whether the `dc zva` fast path for large zero fills is available.
/// `dc zva` zeroes a whole cacheline per instruction without moving data
/// through the store pipeline. Based on musl.
const zva_enabled = builtin.cpu.arch == .aarch64 and
builtin.os.tag != .freestanding;
/// Only use `dc zva` at or above this many bytes. Below this our
/// plain vector loop measures faster. Empirically mesaured.
const zva_threshold = 16384;
/// Matches compiler_rt's memset signature (lib/compiler_rt.zig).
fn memset(dest: ?[*]u8, c: u8, len: usize) callconv(.c) ?[*]u8 {
@setRuntimeSafety(false);
if (len == 0) return dest;
const d = dest.?;
// Large path: full-width vector stores.
if (len >= vec_bytes) {
// Very large zero fills: zero whole cachelines with `dc zva`
// instead. Only implemented for the (universal in practice)
// 64-byte block size; anything else falls through to the
// vector loop.
if (comptime zva_enabled) {
if (len >= zva_threshold and c == 0) zva: {
if (zvaSize() != 64) break :zva;
const splat64: @Vector(64, u8) = @splat(0);
// Head: one unaligned store covering every byte up
// to the first 64-aligned address (and usually a bit
// beyond it; the overlap is fine).
const addr = @intFromPtr(d);
const aligned = std.mem.alignForward(usize, addr, 64);
d[0..64].* = splat64;
// Zero whole aligned cachelines while at least one
// full line remains in range. `dc zva` requires the
// WHOLE line to be inside the buffer: it always
// zeroes all 64 bytes.
var p: [*]u8 = @ptrFromInt(aligned);
const end_addr = addr + len;
while (@intFromPtr(p) + 64 <= end_addr) : (p += 64) {
asm volatile ("dc zva, %[ptr]"
:
: [ptr] "r" (p),
: .{ .memory = true });
}
// Tail: overlapping unaligned store anchored to the
// end of the buffer, covering whatever the line loop
// could not. len >= zva_threshold >= 64 so this
// cannot underflow.
d[len - 64 ..][0..64].* = splat64;
return dest;
}
}
const splat: @Vector(vec_bytes, u8) = @splat(c);
// Fill [0, N) where N is len rounded down to a multiple of
// vec_bytes.
var i: usize = 0;
while (i + vec_bytes <= len) : (i += vec_bytes) {
d[i..][0..vec_bytes].* = splat;
// This empty asm statement prevents LLVM's
// LoopIdiomRecognize pass from replacing this loop with
// a call to memset, which would be infinite recursion
// since we ARE memset. compiler_rt is protected from
// this by being built with -fno-builtin; Ghostty is not.
asm volatile ("" ::: .{ .memory = true });
}
// Fill the remaining [N, len) tail, if any, with one more
// full-width store anchored to the END of the buffer. It
// overlaps up to vec_bytes-1 bytes that the loop already
// wrote, which is fine (same value), and cannot underflow
// because len >= vec_bytes in this path.
if (i != len) d[len - vec_bytes ..][0..vec_bytes].* = splat;
return dest;
}
// Small path (len < vec_bytes): pairs of narrower stores, one
// anchored to the start of the buffer and one to the end.
//
// The invariant for each branch below: a store of width w at
// [0..w] plus a store at [len-w..len] covers every byte exactly
// when w <= len <= 2*w. Smaller lens are handled by a later
// branch; larger lens by an earlier one.
comptime std.debug.assert(vec_bytes <= 128);
if (comptime vec_bytes > 64) {
if (len >= 64) {
// 64 <= len < vec_bytes <= 128 (asserted above): one
// pair of 64-byte stores covers len <= 128. Only emitted
// when vec_bytes > 64 (e.g. AVX-512), otherwise the
// vector loop above already handled these lengths.
const splat64: @Vector(64, u8) = @splat(c);
d[0..64].* = splat64;
d[len - 64 ..][0..64].* = splat64;
return dest;
}
}
if (len >= 16) {
// 16 <= len < @min(vec_bytes, 64), so at most 63. One pair
// of 16-byte stores covers len <= 32. For 32 < len < 64, a
// second pair extends the covered prefix to [0..32] and the
// covered suffix to [len-32..len], which meet or overlap in
// the middle.
const splat16: @Vector(16, u8) = @splat(c);
d[0..16].* = splat16;
d[len - 16 ..][0..16].* = splat16;
if (len > 32) {
d[16..32].* = splat16;
d[len - 32 ..][0..16].* = splat16;
}
return dest;
}
if (len >= 8) {
// 8 <= len <= 15: one 8-byte pair (covers len <= 16).
const splat8: @Vector(8, u8) = @splat(c);
d[0..8].* = splat8;
d[len - 8 ..][0..8].* = splat8;
return dest;
}
if (len >= 4) {
// 4 <= len <= 7: one 4-byte pair (covers len <= 8).
const splat4: @Vector(4, u8) = @splat(c);
d[0..4].* = splat4;
d[len - 4 ..][0..4].* = splat4;
return dest;
}
// 1 <= len <= 3: too short for the pair trick; write bytes one
// at a time. The asm statement again keeps LoopIdiomRecognize
// from turning this into a recursive memset call; LLVM will do
// that even for a loop this short because the trip count is not
// statically known.
var i: usize = 0;
while (i < len) : (i += 1) {
d[i] = c;
asm volatile ("" ::: .{ .memory = true });
}
return dest;
}
/// Cached `dc zva` block size: 0 = not yet queried, 1 = unavailable
/// or prohibited, otherwise the block size in bytes. Racing threads
/// store the same value so the memory ordering is irrelevant.
var zva_size: std.atomic.Value(usize) = .init(0);
fn zvaSize() usize {
const cached = zva_size.load(.monotonic);
if (cached != 0) return cached;
const dczid = asm ("mrs %[out], dczid_el0"
: [out] "=r" (-> u64),
);
// Bit 4 (DZP) prohibits DC ZVA; bits 0-3 are log2 of the block
// size in 4-byte words.
const size: usize = if (dczid & 0x10 != 0)
1
else
@as(usize, 4) << @intCast(dczid & 0xF);
zva_size.store(size, .monotonic);
return size;
}
test memset {
const testing = std.testing;
// A buffer larger than anything the small/vector paths special
// case, with room for offset (alignment) variations. Guard bytes
// around the fill region must remain untouched.
var buf: [4 * vec_bytes + 33]u8 = undefined;
for (0..vec_bytes + 1) |offset| {
for (0..buf.len - offset) |len| {
for ([_]u8{ 0x00, 0x5C, 0xFF }) |c| {
@memset(&buf, 0xAA);
const region = buf[offset..][0..len];
const ret = memset(region.ptr, c, len);
try testing.expectEqual(region.ptr, ret.?);
for (buf[0..offset]) |b| try testing.expectEqual(0xAA, b);
for (region) |b| try testing.expectEqual(c, b);
for (buf[offset + len ..]) |b| try testing.expectEqual(0xAA, b);
}
}
}
// Zero length must not touch memory and must tolerate null.
try testing.expectEqual(null, memset(null, 0x11, 0));
var one: [1]u8 = .{0xAA};
_ = memset(&one, 0x11, 0);
try testing.expectEqual(0xAA, one[0]);
}
test "memset large zero fills (dc zva path)" {
const testing = std.testing;
const alloc = testing.allocator;
// Sizes at and around the ZVA threshold, with a guard byte on
// each side and every offset within a cacheline so the head and
// tail alignment handling is fully exercised.
const buf = try alloc.alloc(u8, 2 * zva_threshold + 66);
defer alloc.free(buf);
for ([_]usize{
zva_threshold - 1,
zva_threshold,
zva_threshold + 63,
2 * zva_threshold,
}) |len| {
for (0..65) |offset| {
@memset(buf, 0xAA);
const region = buf[1 + offset ..][0..len];
_ = memset(region.ptr, 0x00, len);
for (buf[0 .. 1 + offset]) |b| try testing.expectEqual(0xAA, b);
for (region) |b| try testing.expectEqual(0x00, b);
for (buf[1 + offset + len ..]) |b| try testing.expectEqual(0xAA, b);
}
}
// Nonzero fills of ZVA-eligible sizes must not take the ZVA path
// (it can only write zeroes).
@memset(buf, 0xAA);
_ = memset(buf.ptr + 1, 0x5C, zva_threshold);
try testing.expectEqual(0xAA, buf[0]);
for (buf[1 .. 1 + zva_threshold]) |b| try testing.expectEqual(0x5C, b);
try testing.expectEqual(0xAA, buf[1 + zva_threshold]);
}

View File

@@ -9,6 +9,18 @@ pub const index_of = @import("index_of.zig");
pub const vt = @import("vt.zig");
pub const codepointWidth = codepoint_width.codepointWidth;
/// The number of vector lanes to use for manually vectorized hot
/// loops operating on elements of type T, or null if the target has
/// no usable SIMD support and a plain scalar loop should be used
/// instead (e.g. wasm32 without simd128).
///
/// This is twice the native vector width so that each iteration works
/// two vector registers, giving better instruction-level parallelism.
pub fn lanes(comptime T: type) ?comptime_int {
const native = std.simd.suggestVectorLength(T) orelse return null;
return native * 2;
}
test {
@import("std").testing.refAllDecls(@This());
}

View File

@@ -1,7 +1,6 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const cli = @import("../cli.zig");
const global = @import("../global.zig");
/// The available actions for the CLI. This is the list of available
/// synthetic generators. View docs for each individual one in the
@@ -93,7 +92,7 @@ fn mainActionImpl(
// Our output always goes to stdout.
var buffer: [2048]u8 = undefined;
var stdout_writer = std.Io.File.stdout().writer(global.io(), &buffer);
var stdout_writer = std.Io.File.stdout().writer(io, &buffer);
const writer = &stdout_writer.interface;
// Create our implementation

View File

@@ -10,6 +10,7 @@ const assert = @import("../quirks.zig").inlineAssert;
const tripwire = @import("../tripwire.zig");
const testing = std.testing;
const Allocator = std.mem.Allocator;
const simd = @import("../simd/main.zig");
const unicode = @import("../unicode/main.zig");
const uucode = @import("uucode");
@@ -630,6 +631,85 @@ inline fn printSliceEligible(cp: u32, comptime width: PrintSliceWidth) bool {
});
}
/// Store a run of narrow codepoint cells built from a bit template:
/// for each `idx` in `[from, to)`, `cells[idx]` is assigned the bits
/// `template_bits | (cps[idx] << cp_shift)`.
///
/// The template is a complete Cell (content tag, style, wide state,
/// etc. already baked in by the caller) whose codepoint content bits
/// are zero. Since Cell is a packed struct(u64), OR-ing a codepoint
/// into the content field's bit position yields a finished cell as a
/// single integer, keeping the loop pure data movement: no per-cell
/// field assignments and no branches.
///
/// This loop is manually vectorized: Zig 0.16 (LLVM 21) no longer
/// auto-vectorizes the scalar form the way Zig 0.15 (LLVM 20) did.
inline fn printSliceStoreRun(
cells: [*]Cell,
cps: [*]const u32,
from: usize,
to: usize,
template_bits: u64,
) void {
// The bit position of the `content` field within the packed
// Cell. A codepoint occupies the low bits of `content`, so
// shifting a codepoint left by this amount places it exactly
// where `.content = .{ .codepoint = .{ .data = cp } }` would.
const cp_shift = @bitOffsetOf(Cell, "content");
// Since codepoints are OR'd into the content field rather than
// assigned, any nonzero content bits in the template would
// corrupt the stored codepoints.
const content_mask: u64 = comptime mask: {
const bits = @bitSizeOf(@FieldType(Cell, "content"));
break :mask ((1 << bits) - 1) << cp_shift;
};
assert(template_bits & content_mask == 0);
var idx = from;
// Vectorized bulk of the run.
if (simd.lanes(u64)) |lanes| {
// u64 due to backing integer of Cell
const V = @Vector(lanes, u64);
// The template and shift amount are loop-invariant, so
// broadcast them to every lane once up front.
const template: V = @splat(template_bits);
const shift: @Vector(
lanes,
std.math.Log2Int(u64),
) = @splat(cp_shift);
while (idx + lanes <= to) : (idx += lanes) {
// Load `lanes` decoded codepoints...
const narrow: @Vector(lanes, u32) = cps[idx..][0..lanes].*;
// ...widen each u32 lane to the u64 cell size...
const wide: V = @intCast(narrow);
// ...shift each codepoint into the content field's bit
// position and merge with the template, producing
// `lanes` finished cells (coerced from vector to array
// so they can be bitcast for the store below)...
const bits: [lanes]u64 = template | (wide << shift);
// ...and store them contiguously. Cell is a packed
// struct(u64) so an array of u64 bit patterns has
// identical layout to an array of cells.
cells[idx..][0..lanes].* = @bitCast(bits);
}
}
// Scalar tail: the final `< lanes` cells of the run, or the
// entire run on targets without SIMD. Note `idx` carries over
// from the vector loop above. This is the same computation as
// the vector body, one cell at a time.
while (idx < to) : (idx += 1) {
cells[idx] = @bitCast(template_bits | (@as(u64, cps[idx]) << cp_shift));
}
}
/// The row-filling portion of the printSlice fast path, specialized by
/// width class. The first codepoint must already be validated by the
/// caller (printSliceFast).
@@ -671,19 +751,20 @@ fn printSliceFill(
// Anything else (including eligible unicode) proceeds via
// the scalar loop below.
if (comptime width == .narrow) {
const lanes = 8;
const V = @Vector(lanes, u32);
const lo: V = @splat(0x10);
const hi: V = @splat(0xFF);
while (idx + lanes <= cps.len) {
const v: V = cps[idx..][0..lanes].*;
const in_range = (v >= lo) & (v <= hi);
if (!@reduce(.And, in_range)) {
const bits: std.meta.Int(.unsigned, lanes) = @bitCast(in_range);
idx += @ctz(~bits);
break;
if (simd.lanes(u32)) |lanes| {
const V = @Vector(lanes, u32);
const lo: V = @splat(0x10);
const hi: V = @splat(0xFF);
while (idx + lanes <= cps.len) {
const v: V = cps[idx..][0..lanes].*;
const in_range = (v >= lo) & (v <= hi);
if (!@reduce(.And, in_range)) {
const bits: std.meta.Int(.unsigned, lanes) = @bitCast(in_range);
idx += @ctz(~bits);
break;
}
idx += lanes;
}
idx += lanes;
}
}
@@ -817,12 +898,38 @@ fn printSliceFill(
// We can only write whole (wide, spacer) pairs.
const pair_end = k + (simple - k) / 2 * 2;
var idx = k;
// Manually vectorized for the same reason as
// printSliceStoreRun: build a vector of wide cells,
// interleave with spacer tails, and store (wide,
// spacer) pairs several at a time.
if (simd.lanes(u64)) |lanes| {
const pair_lanes = lanes / 2;
const Vp = @Vector(pair_lanes, u64);
const wide_v: Vp = @splat(wide_bits);
const spacer_v: Vp = @splat(spacer_bits);
const shift_v: @Vector(
pair_lanes,
std.math.Log2Int(u64),
) = @splat(cp_shift);
while (idx + 2 * pair_lanes <= pair_end) : (idx += 2 * pair_lanes) {
const narrow: @Vector(pair_lanes, u32) =
cps[printed + idx / 2 ..][0..pair_lanes].*;
const wides: Vp = wide_v | (@as(Vp, @intCast(narrow)) << shift_v);
const inter: [2 * pair_lanes]u64 = std.simd.interlace(.{
wides,
spacer_v,
});
cells[idx..][0 .. 2 * pair_lanes].* = @bitCast(inter);
}
}
while (idx < pair_end) : (idx += 2) {
cells[idx] = @bitCast(
wide_bits | (@as(u64, cps[printed + idx / 2]) << cp_shift),
);
cells[idx + 1] = @bitCast(spacer_bits);
}
// If the simple run ended mid-pair we stop at the pair
// boundary and handle the offending cell below.
k = pair_end;
@@ -832,11 +939,13 @@ fn printSliceFill(
simple = pair_end;
}
} else {
for (k..simple) |idx| {
cells[idx] = @bitCast(
template_bits | (@as(u64, cps[printed + idx]) << cp_shift),
);
}
printSliceStoreRun(
cells,
cps.ptr + printed,
k,
simple,
template_bits,
);
k = simple;
}
if (k >= cell_count) break;
@@ -888,11 +997,13 @@ fn printSliceFill(
page.styles.useMultiple(page.memory, style_id, @intCast(n));
}
for (k..m) |idx| {
cells[idx] = @bitCast(
template_bits | (@as(u64, cps[printed + idx]) << cp_shift),
);
}
printSliceStoreRun(
cells,
cps.ptr + printed,
k,
m,
template_bits,
);
k = m;
continue :fill;
}

View File

@@ -70,8 +70,24 @@ fn AutoHashMapUnmanaged(
fn AutoContext(comptime K: type) type {
return struct {
pub const hash = std.hash_map.getAutoHashFn(K, @This());
pub const eql = std.hash_map.getAutoEqlFn(K, @This());
pub fn hash(_: @This(), key: K) u64 {
if (comptime std.meta.hasUniqueRepresentation(K)) {
// LLVM 21 (Zig 0.16) failed to inline this which resulted
// in a measurable almost 2x slowdown on our hyperlink map
// benchmark. So, force it.
return @call(
.always_inline,
std.hash.Wyhash.hash,
.{ 0, std.mem.asBytes(&key) },
);
}
var hasher = std.hash.Wyhash.init(0);
std.hash.autoHash(&hasher, key);
return hasher.final();
}
};
}

View File

@@ -579,8 +579,30 @@ pub fn Stream(comptime H: type) type {
continue;
}
// Find the end of the printable run. This is an
// early-exit search loop that LLVM won't
// auto-vectorize, and printable runs dominate real
// input, so scan several codepoints at a time
// manually (same idiom as the printSliceFill run
// scan).
var end = i + 1;
while (end < cps.len and cps[end] > 0xF) end += 1;
scan: {
if (simd.lanes(u32)) |lanes| {
const V = @Vector(lanes, u32);
const threshold: V = @splat(0xF);
while (end + lanes <= cps.len) {
const v: V = cps[end..][0..lanes].*;
const gt = v > threshold;
if (!@reduce(.And, gt)) {
const bits: std.meta.Int(.unsigned, lanes) = @bitCast(gt);
end += @ctz(~bits);
break :scan;
}
end += lanes;
}
}
while (end < cps.len and cps[end] > 0xF) end += 1;
}
self.handler.vt(.print_slice, .{ .cps = cps[i..end] });
i = end;
}

View File

@@ -141,8 +141,8 @@ inline fn invalidCodepoint(cp: anytype) bool {
/// This is all the structures and data for the precomputed lookup table
/// for all possible permutations of state and grapheme break properties.
/// Precomputation requires 2^13 keys of 4 bit values so the whole table is
/// 8KB.
/// Precomputation requires 2^13 keys of byte-sized values so the whole
/// table is 8KB.
const Precompute = struct {
const Key = packed struct(u13) {
state: uucode.grapheme.BreakState,
@@ -154,9 +154,15 @@ const Precompute = struct {
}
};
const Value = packed struct(u4) {
const Value = packed struct(u8) {
result: bool,
state: uucode.grapheme.BreakState,
/// Explicit padding to give the struct a power-of-two backing
/// integer, for the same reason as `Properties._padding` (avoids
/// exotic-width LLVM integer accesses that defeat register
/// promotion). @sizeOf was already 1, so the table stays 8KB.
_padding: u4 = 0,
};
const data = precompute: {

View File

@@ -7,7 +7,7 @@
const std = @import("std");
const uucode = @import("uucode");
pub const Properties = packed struct {
pub const Properties = packed struct(u16) {
/// Codepoint width. We clamp to [0, 2] since Ghostty handles control
/// characters and we max out at 2 for wide characters (i.e. 3-em dash
/// becomes a 2-em dash).
@@ -23,6 +23,16 @@ pub const Properties = packed struct {
/// Emoji VS compatibility
emoji_vs_base: bool = false,
/// Explicit padding to give the struct a power-of-two backing integer.
/// This is a performance workaround: with a non-power-of-two backing
/// integer (u9), Zig 0.16 lowers loads/stores of this struct through
/// mismatched exotic-width LLVM integer types (store i9 / load i16),
/// which blocks LLVM from promoting temporaries to registers and forces
/// stack round-trips in hot paths like `graphemeBreak`. See
/// https://github.com/ziglang/zig/issues/21891 for the upstream issue.
/// The size of the lookup table is unchanged (@sizeOf was already 2).
_padding: u7 = 0,
// Needed for lut.Generator
pub fn eql(a: Properties, b: Properties) bool {
return a.width == b.width and