mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-06-12 06:38:20 +00:00
build: binary patch to add growable tables
This commit is contained in:
@@ -147,80 +147,10 @@
|
||||
effectsLog.push({ label, data });
|
||||
}
|
||||
|
||||
// Patch the WASM binary to make the function table growable by
|
||||
// removing its max limit. The build already exports the table
|
||||
// (via --export-table), but Zig's linker doesn't support
|
||||
// --growable-table, so the table has a fixed max that prevents
|
||||
// Table.grow() from JS. This patches the table section (id=4)
|
||||
// to use flag=0 (no max), allowing JS to add callback entries.
|
||||
function patchTableGrowable(buffer) {
|
||||
const bytes = new Uint8Array(buffer);
|
||||
|
||||
function readLEB128(arr, offset) {
|
||||
let result = 0, shift = 0, bytesRead = 0;
|
||||
while (true) {
|
||||
const byte = arr[offset + bytesRead];
|
||||
result |= (byte & 0x7f) << shift;
|
||||
bytesRead++;
|
||||
if ((byte & 0x80) === 0) break;
|
||||
shift += 7;
|
||||
}
|
||||
return { value: result, bytesRead };
|
||||
}
|
||||
|
||||
function encodeLEB128(value) {
|
||||
const out = [];
|
||||
do {
|
||||
let b = value & 0x7f;
|
||||
value >>>= 7;
|
||||
if (value !== 0) b |= 0x80;
|
||||
out.push(b);
|
||||
} while (value !== 0);
|
||||
return out;
|
||||
}
|
||||
|
||||
let pos = 8; // skip magic + version
|
||||
while (pos < bytes.length) {
|
||||
const sectionId = bytes[pos];
|
||||
const { value: sectionSize, bytesRead: sizeLen } = readLEB128(bytes, pos + 1);
|
||||
const sectionStart = pos + 1 + sizeLen;
|
||||
if (sectionId === 4) {
|
||||
const { value: tableCount, bytesRead: countLen } = readLEB128(bytes, sectionStart);
|
||||
let tpos = sectionStart + countLen;
|
||||
const elemType = bytes[tpos++]; // 0x70 = funcref
|
||||
const flags = bytes[tpos];
|
||||
if (!(flags & 1)) return buffer; // already no max
|
||||
// Has max — rebuild section without it
|
||||
const { value: min, bytesRead: minLen } = readLEB128(bytes, tpos + 1);
|
||||
const { bytesRead: maxLen } = readLEB128(bytes, tpos + 1 + minLen);
|
||||
const afterLimits = tpos + 1 + minLen + maxLen;
|
||||
const newPayload = new Uint8Array([
|
||||
...encodeLEB128(tableCount),
|
||||
elemType,
|
||||
0x00, // flags: no max
|
||||
...encodeLEB128(min),
|
||||
...bytes.slice(afterLimits, sectionStart + sectionSize),
|
||||
]);
|
||||
const newSize = encodeLEB128(newPayload.length);
|
||||
const afterSection = sectionStart + sectionSize;
|
||||
const result = new Uint8Array(pos + 1 + newSize.length + newPayload.length + (bytes.length - afterSection));
|
||||
result.set(bytes.subarray(0, pos));
|
||||
let w = pos;
|
||||
result[w++] = 4; // section id
|
||||
for (const b of newSize) result[w++] = b;
|
||||
for (const b of newPayload) result[w++] = b;
|
||||
result.set(bytes.subarray(afterSection), w);
|
||||
return result.buffer;
|
||||
}
|
||||
pos = sectionStart + sectionSize;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
async function loadWasm() {
|
||||
try {
|
||||
const response = await fetch('../../zig-out/bin/ghostty-vt.wasm');
|
||||
const wasmBytes = patchTableGrowable(await response.arrayBuffer());
|
||||
const wasmBytes = await response.arrayBuffer();
|
||||
|
||||
const wasmModule = await WebAssembly.instantiate(wasmBytes, {
|
||||
env: {
|
||||
|
||||
@@ -9,8 +9,8 @@ const GhosttyZig = @import("GhosttyZig.zig");
|
||||
/// The step that generates the file.
|
||||
step: *std.Build.Step,
|
||||
|
||||
/// The artifact result
|
||||
artifact: *std.Build.Step.InstallArtifact,
|
||||
/// The install step for the library output.
|
||||
artifact: *std.Build.Step,
|
||||
|
||||
/// The kind of library
|
||||
kind: Kind,
|
||||
@@ -51,11 +51,32 @@ pub fn initWasm(
|
||||
// There is no entrypoint for this wasm module.
|
||||
exe.entry = .disabled;
|
||||
|
||||
// Zig's WASM linker doesn't support --growable-table, so the table
|
||||
// has a fixed max equal to its initial size. Post-process with wabt
|
||||
// tools (wasm2wat → sed → wat2wasm) to remove the max limit, making
|
||||
// the table growable from JS via Table.grow().
|
||||
const wasm2wat = b.addSystemCommand(&.{"wasm2wat"});
|
||||
wasm2wat.addFileArg(exe.getEmittedBin());
|
||||
|
||||
const awk = b.addSystemCommand(&.{
|
||||
"awk",
|
||||
// Remove the table max from "(table (;0;) MIN MAX funcref)"
|
||||
// so that it becomes "(table (;0;) MIN funcref)", making the
|
||||
// table growable from JS.
|
||||
"/\\(table \\(;[0-9]+;\\) [0-9]+ [0-9]+ funcref\\)/ { sub(/ [0-9]+ funcref\\)/, \" funcref)\") } 1",
|
||||
});
|
||||
awk.addFileArg(wasm2wat.captureStdOut());
|
||||
|
||||
const wat2wasm = b.addSystemCommand(&.{ "wat2wasm", "--enable-all" });
|
||||
wat2wasm.addFileArg(awk.captureStdOut());
|
||||
wat2wasm.addArgs(&.{"-o"});
|
||||
const output = wat2wasm.addOutputFileArg("ghostty-vt.wasm");
|
||||
|
||||
return .{
|
||||
.step = &exe.step,
|
||||
.artifact = b.addInstallArtifact(exe, .{}),
|
||||
.step = &wat2wasm.step,
|
||||
.artifact = &b.addInstallFileWithDir(output, .bin, "ghostty-vt.wasm").step,
|
||||
.kind = .wasm,
|
||||
.output = exe.getEmittedBin(),
|
||||
.output = output,
|
||||
.dsym = null,
|
||||
.pkg_config = null,
|
||||
};
|
||||
@@ -168,7 +189,7 @@ fn initLib(
|
||||
|
||||
return .{
|
||||
.step = &lib.step,
|
||||
.artifact = b.addInstallArtifact(lib, .{}),
|
||||
.artifact = &b.addInstallArtifact(lib, .{}).step,
|
||||
.kind = kind,
|
||||
.output = lib.getEmittedBin(),
|
||||
.dsym = dsymutil,
|
||||
@@ -181,7 +202,7 @@ pub fn install(
|
||||
step: *std.Build.Step,
|
||||
) void {
|
||||
const b = step.owner;
|
||||
step.dependOn(&self.artifact.step);
|
||||
step.dependOn(self.artifact);
|
||||
if (self.pkg_config) |pkg_config| {
|
||||
step.dependOn(&b.addInstallFileWithDir(
|
||||
pkg_config,
|
||||
|
||||
Reference in New Issue
Block a user