Skip to content
Merged
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th
or map entry may instead advance because of ref, null, or type envelopes; name those derived
facts `fieldReadAlwaysAdvances`, `elementReadAlwaysAdvances`, or `entryReadAlwaysAdvances` rather
than conflating them with `readData`.
- For remote TypeDef/TypeMeta reads, the checked metadata cache is the only owner of remote "already validated" state. Cache hit means the header was previously parsed, body/hash-validated, policy-checked, and published by that cache, so the hot path must skip the body and use cached metadata without extra validation, hashing, limit checks, exact-local checks, allocation, or policy work. A known expected local TypeDef/TypeMeta header/hash match is a local-schema hit, not a remote cache miss: it may skip the body and use the local TypeInfo/TypeMeta without schema-version counting or cache publish. Cache miss is the only path that parses and validates non-local metadata, enforces limits, performs exact-local byte comparison when needed, and publishes remote metadata to the cache. Do not add nullable accepted-header fields, sentinel headers, per-TypeInfo markers, pending metadata state, parallel header-low/header-high slots, or parallel acceptance state for this decision. If a runtime needs a metadata hit hint, cache the concrete checked metadata owner object, such as the TypeInfo, TypeDef, or TypeMeta used by that runtime, and compare its validated header identity directly.
- For remote TypeDef/TypeMeta reads, the checked metadata cache is the only owner of remote "already validated" state. Cache hit means the header was previously parsed, body/hash-validated, policy-checked, and published by that cache, so the hot path must skip the body and use cached metadata without extra validation, hashing, limit checks, exact-local checks, allocation, or policy work. The protocol-defined 52-bit TypeDef/TypeMeta header hash is the unique schema identity, so a known expected local header/hash match is a local-schema hit and must not recompare field arrays or metadata bodies. It may skip the body and use the local TypeInfo/TypeMeta without schema-version counting or cache publish. Cache miss is the only path that parses and validates non-local metadata, enforces limits, and publishes remote metadata to the cache. Do not add nullable accepted-header fields, sentinel headers, per-TypeInfo markers, pending metadata state, parallel header-low/header-high slots, or parallel acceptance state for this decision. If a runtime needs a metadata hit hint, cache the concrete checked metadata owner object, such as the TypeInfo, TypeDef, or TypeMeta used by that runtime, and compare its validated header identity directly.
- When a user corrects a non-obvious invariant, encode it in the nearest source comment before continuing, and also update `AGENTS.md`, `.agents/**`, docs, or specs when the rule is reusable beyond one file. Do not rely only on chat history, task notes, commit messages, or benchmark logs for corrections that protect security, protocol behavior, ownership, naming, or hot-path performance.
- Reject semantic hacks. Do not bypass broken semantics by deleting cases, simplifying callers, adding coercion hooks, or using workaround fallbacks; fix the underlying bug and prove it with focused tests.
- Protect hot paths. Avoid per-call allocations, callback objects, result tuples or records, unnecessary runtime branches, and wrapper-class substitutions in hot codec/runtime paths; prefer conditional imports and allocation-free concrete implementations where they fit the language.
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/swift/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,6 @@ protoc \
```

- The benchmark intentionally includes plain-model conversion for protobuf to mirror real-world usage.
- `swift-benchmark` and `swift-external-benchmark` are separate products. Building the ordinary
product does not compile external-type models or serializer specializations.
- `swift-benchmark` and `swift-external-benchmark` are separate executables. Building the ordinary
benchmark does not compile external-type models or serializer specializations.
- Results vary across machines and runtime environments.
11 changes: 5 additions & 6 deletions benchmarks/swift/benchmark_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,13 +265,12 @@ def write_report(
"Apache Fory, Protocol Buffers, and JSON in Swift."
)
lines.append("")
lines.append("## Benchmark Products")
lines.append("## Benchmark Scope")
lines.append("")
lines.append(
"The ordinary/xlang cases are built by `swift-benchmark`. External-type "
"and carrier comparisons are built by the separate "
"`swift-external-benchmark` product, so building the ordinary product "
"does not compile those models or serializer specializations."
"The results below cover the ordinary/xlang cases run by `swift-benchmark`. "
"External-type and carrier comparisons use the separate "
"`swift-external-benchmark` executable and are included only when requested."
)
lines.append("")
lines.append("## Throughput Plot")
Expand Down Expand Up @@ -344,7 +343,7 @@ def write_report(
lines.append("## External-Type Serialization")
lines.append("")
lines.append(
"These cases are built in the isolated `swift-external-benchmark` product."
"These cases run in the isolated `swift-external-benchmark` executable."
)
lines.append("")
lines.append("| Case | Operation | TPS | ns/op | Bytes |")
Expand Down
35 changes: 16 additions & 19 deletions docs/benchmarks/object-serialization/xlang/swift/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@

This benchmark compares serialization and deserialization throughput for Apache Fory, Protocol Buffers, and JSON in Swift.

## Benchmark Executables
## Benchmark Scope

The ordinary/xlang cases are built by `swift-benchmark`. External-type and
carrier comparisons are built by the separate `swift-external-benchmark`
executable, so building the ordinary executable does not compile those models or
serializer specializations.
The results below cover the ordinary/xlang cases run by `swift-benchmark`. External-type and carrier comparisons use the separate `swift-external-benchmark` executable and are included only when requested.

## Throughput Plot

Expand All @@ -17,9 +14,9 @@ serializer specializations.

| Key | Value |
| --------------------- | ----------------------------- |
| Timestamp | 2026-05-08T09:05:32Z |
| Timestamp | 2026-08-04T09:52:12Z |
| OS | Version 15.7.2 (Build 24G325) |
| Host | macbook-pro.local |
| Host | MacBook-Pro.local |
| CPU Cores (Logical) | 12 |
| Memory (GB) | 48.00 |
| Duration per case (s) | 3 |
Expand All @@ -28,18 +25,18 @@ serializer specializations.

| Datatype | Operation | Fory TPS | Protobuf TPS | JSON TPS | Fastest |
| ----------------- | ----------- | ---------: | -----------: | -------: | ------------ |
| NumericStruct | Serialize | 9,435,623 | 6,175,939 | 408,960 | fory (1.53x) |
| NumericStruct | Deserialize | 11,037,225 | 6,842,676 | 328,302 | fory (1.61x) |
| Sample | Serialize | 3,596,835 | 1,257,100 | 79,781 | fory (2.86x) |
| Sample | Deserialize | 982,255 | 733,588 | 41,274 | fory (1.34x) |
| MediaContent | Serialize | 1,561,376 | 609,896 | 98,677 | fory (2.56x) |
| MediaContent | Deserialize | 523,836 | 395,202 | 70,528 | fory (1.33x) |
| NumericStructList | Serialize | 2,910,846 | 918,363 | 82,965 | fory (3.17x) |
| NumericStructList | Deserialize | 2,436,636 | 701,656 | 69,353 | fory (3.47x) |
| SampleList | Serialize | 694,557 | 202,040 | 16,679 | fory (3.44x) |
| SampleList | Deserialize | 187,109 | 131,947 | 8,236 | fory (1.42x) |
| MediaContentList | Serialize | 348,238 | 98,007 | 18,698 | fory (3.55x) |
| MediaContentList | Deserialize | 104,990 | 74,422 | 16,298 | fory (1.41x) |
| NumericStruct | Serialize | 14,759,606 | 7,572,190 | 481,278 | fory (1.95x) |
| NumericStruct | Deserialize | 18,155,795 | 7,317,942 | 377,802 | fory (2.48x) |
| Sample | Serialize | 5,633,231 | 1,380,130 | 102,372 | fory (4.08x) |
| Sample | Deserialize | 1,509,740 | 916,626 | 52,897 | fory (1.65x) |
| MediaContent | Serialize | 2,987,821 | 784,863 | 111,200 | fory (3.81x) |
| MediaContent | Deserialize | 1,072,345 | 549,726 | 97,799 | fory (1.95x) |
| NumericStructList | Serialize | 5,515,769 | 1,093,924 | 93,635 | fory (5.04x) |
| NumericStructList | Deserialize | 2,598,609 | 746,527 | 76,161 | fory (3.48x) |
| SampleList | Serialize | 1,201,868 | 220,772 | 20,772 | fory (5.44x) |
| SampleList | Deserialize | 286,949 | 161,192 | 10,777 | fory (1.78x) |
| MediaContentList | Serialize | 657,276 | 118,004 | 22,744 | fory (5.57x) |
| MediaContentList | Deserialize | 215,726 | 99,445 | 19,521 | fory (2.17x) |

## Serialized Size (bytes)

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
56 changes: 35 additions & 21 deletions swift/Sources/Fory/ByteBuffer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@
import Foundation

public final class ByteBuffer {
// Buffer state access is synchronous and never re-entered while borrowed. Overlapping access is
// invalid because unchecked exclusivity removes Swift's runtime enforcement on these hot paths.
@usableFromInline
@exclusivity(unchecked)
internal var storage: [UInt8]

@usableFromInline
@exclusivity(unchecked)
internal var cursor: Int

@exclusivity(unchecked)
private var dataBridge = Data()

@inlinable
Expand Down Expand Up @@ -180,26 +185,6 @@ public final class ByteBuffer {
return dataBridge
}

@usableFromInline
@inline(__always)
internal func materializeData(
byteCount: Int,
_ body: (UnsafeMutablePointer<UInt8>) -> Void
) -> Data {
if dataBridge.count != byteCount {
dataBridge.count = byteCount
}
if byteCount > 0 {
dataBridge.withUnsafeMutableBytes { destination in
guard let base = destination.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return
}
body(base)
}
}
return dataBridge
}

@inlinable
public func getCursor() -> Int {
cursor
Expand Down Expand Up @@ -837,7 +822,36 @@ public final class ByteBuffer {
guard let base = buffer.baseAddress else {
return nil
}
let utf8Bytes = UnsafeBufferPointer(start: base.advanced(by: start), count: count)
let utf8Base = base.advanced(by: start)
let utf8Bytes = UnsafeBufferPointer(start: utf8Base, count: count)
var index = 0
var isASCII = true
// An ASCII byte has a clear high bit, so this mask checks eight bytes at once
// regardless of native byte order. The bounded loop makes the unaligned load safe.
while index <= count - MemoryLayout<UInt64>.size {
let word = UnsafeRawPointer(utf8Base.advanced(by: index))
.loadUnaligned(as: UInt64.self)
if word & 0x8080_8080_8080_8080 != 0 {
isASCII = false
break
}
index += MemoryLayout<UInt64>.size
}
if isASCII {
while index < count {
if utf8Bytes[index] >= 0x80 {
isASCII = false
break
}
index += 1
}
}
if isASCII {
return String(decoding: utf8Bytes, as: UTF8.self)
}
if #available(macOS 15.0, iOS 18.0, *) {
return String(validating: utf8Bytes, as: UTF8.self)
}
return String(bytes: utf8Bytes, encoding: .utf8)
}
guard let decoded else {
Expand Down
Loading
Loading