diff --git a/src/build/Config.zig b/src/build/Config.zig index e075ed7ed..97a98b752 100644 --- a/src/build/Config.zig +++ b/src/build/Config.zig @@ -460,6 +460,7 @@ pub fn addOptions(self: *const Config, step: *std.Build.Step.Options) !void { step.addOption(bool, "x11", self.x11); step.addOption(bool, "wayland", self.wayland); step.addOption(bool, "sentry", self.sentry); + step.addOption(bool, "simd", self.simd); step.addOption(bool, "i18n", self.i18n); step.addOption(ApprtRuntime, "app_runtime", self.app_runtime); step.addOption(FontBackend, "font_backend", self.font_backend); diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index 0cf0ef5c1..cf84b3e0c 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -112,6 +112,7 @@ pub fn add( const vt_options = @import("../terminal/build_options.zig"); vt_options.addOptions(b, step.root_module, .{ .artifact = .ghostty, + .simd = self.config.simd, .slow_runtime_safety = switch (optimize) { .Debug => true, .ReleaseSafe, diff --git a/src/simd/base64.zig b/src/simd/base64.zig index 778fbfe3e..c733cf103 100644 --- a/src/simd/base64.zig +++ b/src/simd/base64.zig @@ -1,4 +1,63 @@ const std = @import("std"); +const options = @import("build_options"); +const assert = std.debug.assert; +const log = std.log.scoped(.simd_base64); + +// Used for the non-SIMD implementation +const Base64Decoder = std.base64.standard_no_pad.Decoder; + +pub fn maxLen(input: []const u8) usize { + if (comptime options.simd) return ghostty_simd_base64_max_length( + input.ptr, + input.len, + ); + + return maxLenScalar(input); +} + +fn maxLenScalar(input: []const u8) usize { + return Base64Decoder.calcSizeForSlice(scalarInput(input)) catch |err| { + log.warn("failed to calculate base64 size for payload: {}", .{err}); + return 0; + }; +} + +pub fn decode(input: []const u8, output: []u8) error{Base64Invalid}![]const u8 { + if (comptime options.simd) { + const res = ghostty_simd_base64_decode( + input.ptr, + input.len, + output.ptr, + ); + if (res < 0) return error.Base64Invalid; + return output[0..@intCast(res)]; + } + + return decodeScalar(input, output); +} + +fn decodeScalar( + input_raw: []const u8, + output: []u8, +) error{Base64Invalid}![]const u8 { + const input = scalarInput(input_raw); + const size = maxLenScalar(input); + if (size == 0) return ""; + assert(output.len >= size); + Base64Decoder.decode( + output, + scalarInput(input), + ) catch return error.Base64Invalid; + return output[0..size]; +} + +/// For non-SIMD enabled builds, we trim the padding from the end of the +/// base64 input in order to get identical output with the SIMD version. +fn scalarInput(input: []const u8) []const u8 { + var i: usize = 0; + while (input[input.len - i - 1] == '=') i += 1; + return input[0 .. input.len - i]; +} // base64.cpp extern "c" fn ghostty_simd_base64_max_length( @@ -11,16 +70,6 @@ extern "c" fn ghostty_simd_base64_decode( output: [*]u8, ) isize; -pub fn maxLen(input: []const u8) usize { - return ghostty_simd_base64_max_length(input.ptr, input.len); -} - -pub fn decode(input: []const u8, output: []u8) error{Base64Invalid}![]const u8 { - const res = ghostty_simd_base64_decode(input.ptr, input.len, output.ptr); - if (res < 0) return error.Base64Invalid; - return output[0..@intCast(res)]; -} - test "base64 maxLen" { const testing = std.testing; const len = maxLen("aGVsbG8gd29ybGQ="); diff --git a/src/simd/codepoint_width.zig b/src/simd/codepoint_width.zig index aab4bdd95..e097dbd61 100644 --- a/src/simd/codepoint_width.zig +++ b/src/simd/codepoint_width.zig @@ -1,11 +1,12 @@ const std = @import("std"); +const options = @import("build_options"); // vt.cpp extern "c" fn ghostty_simd_codepoint_width(u32) i8; pub fn codepointWidth(cp: u32) i8 { - //return @import("ziglyph").display_width.codePointWidth(@intCast(cp), .half); - return ghostty_simd_codepoint_width(cp); + if (comptime options.simd) return ghostty_simd_codepoint_width(cp); + return @import("ziglyph").display_width.codePointWidth(@intCast(cp), .half); } test "codepointWidth basic" { diff --git a/src/simd/index_of.zig b/src/simd/index_of.zig index b39605996..cea549b95 100644 --- a/src/simd/index_of.zig +++ b/src/simd/index_of.zig @@ -1,5 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); +const options = @import("build_options"); extern "c" fn ghostty_simd_index_of( needle: u8, @@ -8,8 +9,16 @@ extern "c" fn ghostty_simd_index_of( ) usize; pub fn indexOf(input: []const u8, needle: u8) ?usize { - const result = ghostty_simd_index_of(needle, input.ptr, input.len); - return if (result == input.len) null else result; + if (comptime options.simd) { + const result = ghostty_simd_index_of(needle, input.ptr, input.len); + return if (result == input.len) null else result; + } + + return indexOfScalar(input, needle); +} + +fn indexOfScalar(input: []const u8, needle: u8) ?usize { + return std.mem.indexOfScalar(u8, input, needle); } test "indexOf" { diff --git a/src/simd/main.zig b/src/simd/main.zig index bfcd68c0a..aabdd21d1 100644 --- a/src/simd/main.zig +++ b/src/simd/main.zig @@ -1,3 +1,6 @@ +//! SIMD-optimized routines. If `build_options.simd` is false, then the API +//! still works but we fall back to pure Zig scalar implementations. + const std = @import("std"); const codepoint_width = @import("codepoint_width.zig"); diff --git a/src/simd/vt.zig b/src/simd/vt.zig index dc1c0a511..2a19c52c7 100644 --- a/src/simd/vt.zig +++ b/src/simd/vt.zig @@ -1,4 +1,7 @@ const std = @import("std"); +const options = @import("build_options"); +const assert = std.debug.assert; +const indexOf = @import("index_of.zig").indexOf; // vt.cpp extern "c" fn ghostty_simd_decode_utf8_until_control_seq( @@ -17,15 +20,43 @@ pub fn utf8DecodeUntilControlSeq( input: []const u8, output: []u32, ) DecodeResult { - var decoded: usize = 0; - const consumed = ghostty_simd_decode_utf8_until_control_seq( - input.ptr, - input.len, - output.ptr, - &decoded, - ); + assert(output.len >= input.len); - return .{ .consumed = consumed, .decoded = decoded }; + if (comptime options.simd) { + var decoded: usize = 0; + const consumed = ghostty_simd_decode_utf8_until_control_seq( + input.ptr, + input.len, + output.ptr, + &decoded, + ); + + return .{ .consumed = consumed, .decoded = decoded }; + } + + return utf8DecodeUntilControlSeqScalar(input, output); +} + +fn utf8DecodeUntilControlSeqScalar( + input: []const u8, + output: []u32, +) DecodeResult { + // Find our escape + const idx = indexOf(input, 0x1B) orelse input.len; + + // Copy up to the escape + const view = std.unicode.Utf8View.init(input[0..idx]) catch unreachable; + var it = view.iterator(); + var i: usize = 0; + while (it.nextCodepoint()) |cp| { + output[i] = @intCast(cp); + i += 1; + } + + return .{ + .consumed = idx, + .decoded = i, + }; } test "decode no escape" {