Skip to content

Commit

Permalink
speed up hot-path utf8 processing
Browse files Browse the repository at this point in the history
  • Loading branch information
sliemeobn committed Oct 14, 2024
1 parent 4ab9270 commit 54b0d84
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 34 deletions.
4 changes: 4 additions & 0 deletions Sources/Elementary/Core/AttributeStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ public struct _MergedAttributes: Sequence, Sendable {
Iterator(storage)
}

public var isEmpty: Bool {
storage.isEmpty
}

public struct Iterator: IteratorProtocol {
enum State {
case empty
Expand Down
2 changes: 1 addition & 1 deletion Sources/Elementary/Core/Html+Elements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ extension HTMLComment: Sendable {}
extension HTMLRaw: Sendable {}

extension HTMLTagDefinition {
@usableFromInline
@usableFromInline @inline(__always)
static var renderingType: _HTMLRenderToken.RenderingType {
_rendersInline ? .inline : .block
}
Expand Down
98 changes: 68 additions & 30 deletions Sources/Elementary/Rendering/RenderingUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,61 +23,99 @@ extension _RenderingContext {
// I do not know why this function does not work in embedded, but currently it crashes the compiler
#if !hasFeature(Embedded)
extension [UInt8] {
@inline(__always)
mutating func appendToken(_ token: consuming _HTMLRenderToken) {
// avoid strings and append each component directly
switch token {
case let .startTag(tagName, attributes: attributes, isUnpaired: _, type: _):
append(60) // <
append(contentsOf: tagName.utf8)
for attribute in attributes {
append(32) // space
append(contentsOf: attribute.name.utf8)
if let value = attribute.value {
append(contentsOf: [61, 34]) // ="
appendEscapedAttributeValue(value)
append(34) // "
appendString(tagName)
if !attributes.isEmpty {
for attribute in attributes {
append(32) // space
appendString(attribute.name)
if let value = attribute.value {
append(contentsOf: [61, 34]) // ="
appendEscapedAttributeValue(value)
append(34) // "
}
}
}
append(62) // >
case let .endTag(tagName, _):
append(contentsOf: [60, 47]) // </
append(contentsOf: tagName.utf8)
appendString(tagName)
append(62) // >
case let .text(text):
appendEscapedText(text)
case let .raw(raw):
append(contentsOf: raw.utf8)
appendString(raw)
case let .comment(comment):
append(contentsOf: "<!--".utf8)
appendString("<!--")
appendEscapedText(comment)
append(contentsOf: "-->".utf8)
appendString("-->")
}
}

@inline(__always)
mutating func appendString(_ string: consuming String) {
string.withUTF8 { utf8 in
append(contentsOf: utf8)
}
}

@inline(__always)
mutating func appendEscapedAttributeValue(_ value: consuming String) {
for byte in value.utf8 {
switch byte {
case 38: // &
append(contentsOf: "&amp;".utf8)
case 34: // "
append(contentsOf: "&quot;".utf8)
default:
append(byte)
value.withUTF8 { utf8 in
var start = utf8.startIndex

for current in utf8.indices {
switch utf8[current] {
case 38: // &
append(contentsOf: utf8[start ..< current])
appendString("&amp;")
start = utf8.index(after: current)
case 34: // "
append(contentsOf: utf8[start ..< current])
appendString("&quot;")
start = utf8.index(after: current)
default:
()
}
}

if start < utf8.endIndex {
append(contentsOf: utf8[start ..< utf8.endIndex])
}
}
}

@inline(__always)
mutating func appendEscapedText(_ value: consuming String) {
for byte in value.utf8 {
switch byte {
case 38: // &
append(contentsOf: "&amp;".utf8)
case 60: // <
append(contentsOf: "&lt;".utf8)
case 62: // >
append(contentsOf: "&gt;".utf8)
default:
append(byte)
value.withUTF8 { utf8 in
var start = utf8.startIndex

for current in utf8.indices {
switch utf8[current] {
case 38: // &
append(contentsOf: utf8[start ..< current])
appendString("&amp;")
start = utf8.index(after: current)
case 60: // <
append(contentsOf: utf8[start ..< current])
appendString("&lt;")
start = utf8.index(after: current)
case 62: // >
append(contentsOf: utf8[start ..< current])
appendString("&gt;")
start = utf8.index(after: current)
default:
()
}
}

if start < utf8.endIndex {
append(contentsOf: utf8[start ..< utf8.endIndex])
}
}
}
Expand Down
3 changes: 0 additions & 3 deletions Tests/ElementaryTests/Utilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,3 @@ func HTMLAssertEqualAsyncOnly(_ html: some HTML, _ expected: String, file: Stati
func HTMLFormattedAssertEqual(_ html: some HTML, _ expected: String, file: StaticString = #filePath, line: UInt = #line) {
XCTAssertEqual(expected, html.renderFormatted(), file: file, line: line)
}

@inline(never)
func blackHole<T>(_: T) {}

0 comments on commit 54b0d84

Please sign in to comment.