Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ docs/**/*.mdx linguist-detectable=true linguist-language=MDX

.agents/** linguist-detectable=false linguist-generated
.claude/** linguist-detectable=false linguist-generated

*.js
Comment on lines +26 to +27
Comment thread
tukuyomil032 marked this conversation as resolved.
1 change: 1 addition & 0 deletions apps/native-macos/.swiftformat
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
--importgrouping testable-last
--exclude .build
--disable swiftTestingTestCaseNames
--disable trailingCommas
8 changes: 8 additions & 0 deletions apps/native-macos/Scripts/entitlements/spike-allow-jit.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
6 changes: 6 additions & 0 deletions apps/native-macos/Scripts/entitlements/spike-baseline.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
</dict>
</plist>
56 changes: 56 additions & 0 deletions apps/native-macos/Scripts/hardened-runtime-spike.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PACKAGE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
BUILD_DIR="$PACKAGE_DIR/.build/release"
APP_BUNDLE="$PACKAGE_DIR/.build/spike/HardenedRuntimeSpike.app"
ENTITLEMENTS_DIR="$SCRIPT_DIR/entitlements"

echo "==> Building release binary"
swift build -c release --package-path "$PACKAGE_DIR"

echo "==> Assembling minimal .app bundle"
rm -rf "$APP_BUNDLE"
mkdir -p "$APP_BUNDLE/Contents/MacOS"
cp "$BUILD_DIR/App" "$APP_BUNDLE/Contents/MacOS/App"

cat > "$APP_BUNDLE/Contents/Info.plist" <<'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>dev.mc-vector.native.spike.hardened-runtime</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
</dict>
</plist>
PLIST

declare -a RESULTS=()

for plist in "$ENTITLEMENTS_DIR"/spike-*.plist; do
name="$(basename "$plist" .plist)"
echo "==> Signing with entitlements: $name"
codesign --force --options runtime -s - --entitlements "$plist" "$APP_BUNDLE"

echo "==> Running (MCV_SPIKE=hardened-runtime-java) with $name"
set +e
OUTPUT="$(MCV_SPIKE=hardened-runtime-java "$APP_BUNDLE/Contents/MacOS/App" 2>&1)"
STATUS=$?
set -e

echo "--- $name (exit=$STATUS) ---"
echo "$OUTPUT"
echo "---------------------------"
RESULTS+=("$name: exit=$STATUS")
done

echo ""
echo "==> Summary"
for line in "${RESULTS[@]}"; do
echo "$line"
done
45 changes: 43 additions & 2 deletions apps/native-macos/Sources/App/Main.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,49 @@
import Core
import Foundation

@main
struct Main {
static func main() {
print("MC-Vector Native starting…")
static func main() async {
guard let spike = ProcessInfo.processInfo.environment["MCV_SPIKE"] else {
print("MC-Vector Native starting…")
return
}

switch spike {
case "hardened-runtime-java":
await self.runHardenedRuntimeJavaSpike()
case "panel-nspanel":
PanelSpikeRunner.runNSPanelBridge()
case "panel-window":
PanelSpikeRunner.runSwiftUIWindowLevel()
case "log-stream":
PanelSpikeRunner.runLogStreamSpike()
default:
print("Unknown MCV_SPIKE value: \(spike)")
}
}

static func runHardenedRuntimeJavaSpike() async {
let javaCandidates = [
"/opt/homebrew/opt/openjdk/bin/java",
"/usr/bin/java"
]
guard let javaPath = javaCandidates.first(where: { FileManager.default.isExecutableFile(atPath: $0) }) else {
print("java executable not found in known locations")
return
}

let harness = JavaLaunchHarness()
do {
let result = try await harness.launch(
executableURL: URL(fileURLWithPath: javaPath),
arguments: ["-version"],
)
print("exitCode=\(result.terminationStatus)")
print("stdout=\(result.standardOutput)")
print("stderr=\(result.standardError)")
} catch {
print("launch failed: \(error)")
}
}
}
22 changes: 22 additions & 0 deletions apps/native-macos/Sources/App/Spikes/PanelSpikeRunner.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import AppKit
import Core

@MainActor
enum PanelSpikeRunner {
static func runNSPanelBridge() {
let app = NSApplication.shared
app.setActivationPolicy(.accessory)
let panel = NonactivatingGlassPanel()
panel.center()
panel.makeKeyAndOrderFront(nil)
app.run()
}

static func runSwiftUIWindowLevel() {
SwiftUIWindowLevelSpike.main()
}

static func runLogStreamSpike() {
LogStreamSpikeApp.main()
}
}
62 changes: 62 additions & 0 deletions apps/native-macos/Sources/Core/Spikes/JavaLaunchHarness.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import Foundation

public struct JavaLaunchResult: Sendable, Equatable {
public let terminationStatus: Int32
public let standardOutput: String
public let standardError: String

public init(terminationStatus: Int32, standardOutput: String, standardError: String) {
self.terminationStatus = terminationStatus
self.standardOutput = standardOutput
self.standardError = standardError
}
}

public enum JavaLaunchError: Error, Sendable {
case executableNotFound(URL)
}

public actor JavaLaunchHarness {
public init() {}

public func launch(executableURL: URL, arguments: [String]) async throws -> JavaLaunchResult {
guard FileManager.default.isExecutableFile(atPath: executableURL.path) else {
throw JavaLaunchError.executableNotFound(executableURL)
}

let process = Process()
process.executableURL = executableURL
process.arguments = arguments

let stdoutPipe = Pipe()
let stderrPipe = Pipe()
process.standardOutput = stdoutPipe
process.standardError = stderrPipe

try process.run()

// Read both pipes concurrently: draining stdout fully before starting
// on stderr (or vice versa) deadlocks once a child fills the OS pipe
// buffer on the pipe being read second while the other is still full.
async let stdoutData = stdoutPipe.fileHandleForReading.readToEndCompat()
async let stderrData = stderrPipe.fileHandleForReading.readToEndCompat()
let (stdoutBytes, stderrBytes) = try await (stdoutData, stderrData)

process.waitUntilExit()
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return JavaLaunchResult(
terminationStatus: process.terminationStatus,
standardOutput: String(bytes: stdoutBytes, encoding: .utf8) ?? "",
standardError: String(bytes: stderrBytes, encoding: .utf8) ?? "",
)
}
}

private extension FileHandle {
func readToEndCompat() throws -> Data {
if let data = try self.readToEnd() {
return data
}
return Data()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Foundation

public struct LogLine: Sendable, Identifiable, Equatable {
public let id: Int
public let timestamp: ContinuousClock.Instant
public let text: String

public init(id: Int, timestamp: ContinuousClock.Instant, text: String) {
self.id = id
self.timestamp = timestamp
self.text = text
}
}

public actor DummyLogGenerator {
public init() {}

public func stream(linesPerSecond: Int) -> AsyncStream<LogLine> {
let interval = Duration.seconds(1) / Double(max(linesPerSecond, 1))

return AsyncStream { continuation in
let task = Task {
var index = 0
let clock = ContinuousClock()
while !Task.isCancelled {
index += 1
continuation.yield(
LogLine(id: index, timestamp: clock.now, text: "[spike] log line #\(index)"),
)
try? await Task.sleep(for: interval)
}
continuation.finish()
}

continuation.onTermination = { _ in
task.cancel()
}
}
}
}
35 changes: 35 additions & 0 deletions apps/native-macos/Sources/Core/Spikes/LogSpike/LogBatcher.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Foundation

public struct LogBatcher: Sendable {
private let interval: Duration

public init(interval: Duration) {
self.interval = interval
}

/// Groups `lines` into batches such that every line whose timestamp falls
/// within the same `interval`-sized window (measured from the first
/// line's timestamp) lands in the same batch, in arrival order.
public func batch(_ lines: [LogLine]) -> [[LogLine]] {
guard let first = lines.first else { return [] }

var batches: [[LogLine]] = []
var currentBatch: [LogLine] = []
var windowStart = first.timestamp

for line in lines {
if line.timestamp - windowStart >= self.interval, !currentBatch.isEmpty {
batches.append(currentBatch)
currentBatch = []
windowStart = line.timestamp
}
currentBatch.append(line)
}
Comment on lines +20 to +27

if !currentBatch.isEmpty {
batches.append(currentBatch)
}

return batches
}
}
22 changes: 22 additions & 0 deletions apps/native-macos/Sources/Core/Spikes/LogSpike/LogLineBuffer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/// A fixed-capacity buffer that trims with hysteresis: it only shifts the
/// backing array once the overshoot allowance is exceeded, rather than on
/// every single append. Trimming to exactly `retainedLineCount` on every
/// append would make `Array.removeFirst(_:)` the dominant cost at
/// high ingest rates, masking the rendering cost this spike exists to measure.
public struct LogLineBuffer {
public private(set) var lines: [LogLine] = []
private let retainedLineCount: Int
private let trimOvershoot: Int

public init(retainedLineCount: Int, trimOvershoot: Int) {
self.retainedLineCount = retainedLineCount
self.trimOvershoot = trimOvershoot
}

public mutating func append(_ line: LogLine) {
self.lines.append(line)
if self.lines.count > self.retainedLineCount + self.trimOvershoot {
self.lines.removeFirst(self.lines.count - self.retainedLineCount)
}
}
}
Loading
Loading