From 7bdea1befa9622b1410f980ce9fc4acc6116e6bc Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Sat, 19 Apr 2025 16:19:46 +0800 Subject: [PATCH] Support binding JS strings to [:0]const u8 Some APIs need a null-terminated string. Currently, they have to ask for a `[]const u8` and then convert it to a `[:0]const u8`. This is 2 allocations: 1 for jsruntime to get the `[]const u8` from v8, and then one to get the [:0]. By supporting `[:0]const u8` directly, this is now a single allocation. --- src/browser/dom/implementation.zig | 41 +++++----------------------- src/runtime/js.zig | 18 +++++++++++- src/runtime/test_primitive_types.zig | 12 ++++++++ 3 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/browser/dom/implementation.zig b/src/browser/dom/implementation.zig index b97b37e5..03874b82 100644 --- a/src/browser/dom/implementation.zig +++ b/src/browser/dom/implementation.zig @@ -31,45 +31,20 @@ pub const DOMImplementation = struct { pub fn _createDocumentType( _: *DOMImplementation, - qname: []const u8, - publicId: []const u8, - systemId: []const u8, - state: *SessionState, + qname: [:0]const u8, + publicId: [:0]const u8, + systemId: [:0]const u8, ) !*parser.DocumentType { - const allocator = state.arena; - const cqname = try allocator.dupeZ(u8, qname); - defer allocator.free(cqname); - - const cpublicId = try allocator.dupeZ(u8, publicId); - defer allocator.free(cpublicId); - - const csystemId = try allocator.dupeZ(u8, systemId); - defer allocator.free(csystemId); - - return try parser.domImplementationCreateDocumentType(cqname, cpublicId, csystemId); + return try parser.domImplementationCreateDocumentType(qname, publicId, systemId); } pub fn _createDocument( _: *DOMImplementation, - namespace: ?[]const u8, - qname: ?[]const u8, + namespace: ?[:0]const u8, + qname: ?[:0]const u8, doctype: ?*parser.DocumentType, - state: *SessionState, ) !*parser.Document { - const allocator = state.arena; - var cnamespace: ?[:0]const u8 = null; - if (namespace) |ns| { - cnamespace = try allocator.dupeZ(u8, ns); - } - defer if (cnamespace) |v| allocator.free(v); - - var cqname: ?[:0]const u8 = null; - if (qname) |qn| { - cqname = try allocator.dupeZ(u8, qn); - } - defer if (cqname) |v| allocator.free(v); - - return try parser.domImplementationCreateDocument(cnamespace, cqname, doctype); + return try parser.domImplementationCreateDocument(namespace, qname, doctype); } pub fn _createHTMLDocument(_: *DOMImplementation, title: ?[]const u8) !*parser.DocumentHTML { @@ -79,8 +54,6 @@ pub const DOMImplementation = struct { pub fn _hasFeature(_: *DOMImplementation) bool { return true; } - - pub fn deinit(_: *DOMImplementation, _: std.mem.Allocator) void {} }; // Tests diff --git a/src/runtime/js.zig b/src/runtime/js.zig index 4a267e75..15de61c4 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -1862,7 +1862,13 @@ fn Caller(comptime E: type) type { }, .slice => { if (ptr.child == u8) { - return valueToString(self.call_allocator, js_value, self.isolate, self.context); + if (ptr.sentinel()) |s| { + if (comptime s == 0) { + return valueToStringZ(self.call_allocator, js_value, self.isolate, self.context); + } + } else { + return valueToString(self.call_allocator, js_value, self.isolate, self.context); + } } // TODO: TypedArray @@ -2241,6 +2247,16 @@ fn valueToString(allocator: Allocator, value: v8.Value, isolate: v8.Isolate, con return buf; } +fn valueToStringZ(allocator: Allocator, value: v8.Value, isolate: v8.Isolate, context: v8.Context) ![:0]u8 { + const str = try value.toString(context); + const len = str.lenUtf8(isolate); + const buf = try allocator.alloc(u8, len + 1); + const n = str.writeUtf8(isolate, buf[0..len]); + std.debug.assert(n == len); + buf[len] = 0; + return buf[0..len :0]; +} + const NoopInspector = struct { pub fn onInspectorResponse(_: *anyopaque, _: u32, _: []const u8) void {} pub fn onInspectorEvent(_: *anyopaque, _: []const u8) void {} diff --git a/src/runtime/test_primitive_types.zig b/src/runtime/test_primitive_types.zig index 022a8c91..a2b64f93 100644 --- a/src/runtime/test_primitive_types.zig +++ b/src/runtime/test_primitive_types.zig @@ -96,6 +96,14 @@ const Primitives = struct { pub fn _checkOptionalReturnString(_: *const Primitives) ?[]const u8 { return "ok"; } + + pub fn _echoString(_: *const Primitives, a: []const u8) []const u8 { + return a; + } + + pub fn _echoStringZ(_: *const Primitives, a: [:0]const u8) []const u8 { + return a; + } }; const testing = @import("testing.zig"); @@ -172,5 +180,9 @@ test "JS: primitive types" { .{ "p.checkOptionalReturn() === true;", "true" }, .{ "p.checkOptionalReturnNull() === null;", "true" }, .{ "p.checkOptionalReturnString() === 'ok';", "true" }, + + // strings + .{ "p.echoString('over 9000!');", "over 9000!" }, + .{ "p.echoStringZ('Teg');", "Teg" }, }, .{}); }