Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d17c8e3
Add track
ctreffs Aug 24, 2026
b8710ba
build: add compiler stats profiling script and Makefile rule
ctreffs Aug 24, 2026
4b8523f
perf: remove unnecessary Foundation imports in math functions
ctreffs Aug 24, 2026
a334a29
perf: annotate explicit literal types in constants and identities
ctreffs Aug 24, 2026
64820ef
perf: simplify type checking for classification, distance, and axis f…
ctreffs Aug 24, 2026
28bcb7d
perf: un-nest matrix multiplication expressions and annotate inlinabl…
ctreffs Aug 24, 2026
f7cc317
docs: add compiler optimization report with step-by-step benchmarks
ctreffs Aug 24, 2026
ee12995
docs: update compiler optimization report with multi-pass benchmark data
ctreffs Aug 24, 2026
bc01f0d
build: add benchmark-commits script and Makefile rule
ctreffs Aug 24, 2026
c167be3
perf: specialize remap extension for Float and Double
ctreffs Aug 24, 2026
5625079
perf: annotate trigonometric functions with inlinable
ctreffs Aug 24, 2026
016fc65
perf: explicitly qualify helper calls in matrix and quaternion operators
ctreffs Aug 24, 2026
ecff3bf
docs: extend compiler report with all 13 benchmarked commits
ctreffs Aug 24, 2026
e0542e4
perf: disambiguate tan and double types in Matrix4x4+Projections.swift
ctreffs Aug 24, 2026
ddd5756
perf: annotate max and min overloads with inlinable
ctreffs Aug 24, 2026
4da60b4
perf: qualify C math call in distance function
ctreffs Aug 24, 2026
16a9d82
perf: declare constants as computed properties with explicit type names
ctreffs Aug 24, 2026
fa7c3f6
perf: simplify clamp and floating-point infinity classifications
ctreffs Aug 24, 2026
ce90bdd
docs: update compiler report with latest performance and instruction …
ctreffs Aug 24, 2026
f9a9ed3
docs: add scalar min/max cross-platform analysis
ctreffs Aug 24, 2026
6a4acac
build: untrack report and analysis docs from git while ignoring locally
ctreffs Aug 24, 2026
466842b
fix: correct subtract call in quaternion operators
ctreffs Aug 24, 2026
5918aa5
Update gitignore
ctreffs Aug 24, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
.gemini/
*.DS_Store
build*/
_diagnostics/
# conductor/
gha-creds-*.json
xcuserdata/
Expand Down
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ DOCS_VERSION_PATH ?= main
HOSTING_BASE_PATH ?= $(REPO_NAME)/$(DOCS_VERSION_PATH)

# Targets
.PHONY: setup lint lint-fix test test-coverage clean pre-commit docs docs-preview docs-generate docs-coverage
.PHONY: setup lint lint-fix test test-coverage clean pre-commit docs docs-preview docs-generate docs-coverage profile-stats benchmark-commits

setup:
@echo "Detected Package Swift Version: $(PACKAGE_SWIFT_VERSION)"
Expand Down Expand Up @@ -75,3 +75,12 @@ clean:
swift package clean
rm -rf .build
rm -rf .swiftpm

profile-stats:
chmod +x Scripts/profile-compiler-stats.sh
./Scripts/profile-compiler-stats.sh

benchmark-commits:
chmod +x Scripts/profile-compiler-stats.sh
chmod +x Scripts/benchmark-commits.py
python3 Scripts/benchmark-commits.py
150 changes: 150 additions & 0 deletions Scripts/benchmark-commits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/usr/bin/env python3
import subprocess
import json
import glob
import os
import sys
import argparse

def main():
parser = argparse.ArgumentParser(description="Benchmark compiler stats across git commits.")
parser.add_argument("--base", default="origin/master", help="Base commit or branch to start benchmark from")
parser.add_argument("--runs", type=int, default=2, help="Number of benchmark runs per commit")
parser.add_argument("--output", default="_diagnostics/commit_stats_multi.json", help="Path to output JSON result file")
args = parser.parse_args()

# Determine root directory of git repository
repo_root = subprocess.check_output(["git", "rev-parse", "--show-toplevel"], text=True).strip()
os.chdir(repo_root)

profile_script = os.path.join(repo_root, "Scripts", "profile-compiler-stats.sh")
if not os.path.exists(profile_script):
print(f"Error: Profile script not found at {profile_script}")
sys.exit(1)

# Get current branch
current_branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"], text=True).strip()

# Get commits from base to HEAD
try:
log_output = subprocess.check_output(
["git", "log", f"{args.base}..HEAD", "--oneline", "--reverse"],
text=True
).strip()
except subprocess.CalledProcessError as e:
print(f"Error fetching git commit log: {e}")
sys.exit(1)

commits = [(args.base, f"Baseline ({args.base})")]
if log_output:
for line in log_output.splitlines():
parts = line.split(" ", 1)
sha = parts[0]
msg = parts[1] if len(parts) > 1 else ""
commits.append((sha, msg))

results = []

try:
for commit_sha, commit_msg in commits:
print(f"\n==================================================")
print(f"Checking out {commit_sha}: {commit_msg}")
print(f"==================================================")

subprocess.run(["git", "checkout", "-f", commit_sha], check=True)

runs = []
for run_idx in range(1, args.runs + 1):
print(f"--- Run {run_idx}/{args.runs} for {commit_sha} ---")

res = subprocess.run([profile_script], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)

stats_dir = "_diagnostics/stats"
json_files = glob.glob(os.path.join(stats_dir, "stats-*.json"))

total_instructions = 0
total_source_lines = 0
total_typechecking_wall = 0.0
total_silgen_wall = 0.0
total_frontend_wall = 0.0

frontend_stats = []

for filepath in json_files:
try:
with open(filepath, 'r') as f:
data = json.load(f)

instructions = data.get("Frontend.NumInstructionsExecuted", 0)
lines = data.get("AST.NumSourceLines", 0)

tc_wall = data.get("time.swift.perform-whole-module-type-checking.wall", 0.0)
if tc_wall == 0.0:
tc_wall = data.get("time.swift.perform-sema.wall", 0.0)
if tc_wall == 0.0:
tc_wall = data.get("time.swift.Type checking and Semantic analysis.wall", 0.0)

sil_wall = data.get("time.swift.SILGen.wall", 0.0)

fe_wall = 0.0
for k, v in data.items():
if k.startswith("time.swift-frontend.") and k.endswith(".wall"):
fe_wall = v
break

total_instructions += instructions
total_source_lines += lines
total_typechecking_wall += tc_wall
total_silgen_wall += sil_wall
total_frontend_wall += fe_wall

filename = os.path.basename(filepath)
parts = filename.split("swift-frontend-FirebladeMath-")
swift_file = parts[1].split("-arm64")[0] if len(parts) > 1 else filename

if instructions > 0:
frontend_stats.append({
"file": swift_file,
"instructions": instructions,
"lines": lines,
"tc_wall": tc_wall,
"fe_wall": fe_wall
})
except Exception as e:
print(f"Error parsing {filepath}: {e}")

frontend_stats.sort(key=lambda x: x["instructions"], reverse=True)

runs.append({
"run_index": run_idx,
"total_instructions": total_instructions,
"total_source_lines": total_source_lines,
"typechecking_wall": total_typechecking_wall,
"frontend_wall": total_frontend_wall,
"silgen_wall": total_silgen_wall,
"top_hotspots": frontend_stats[:5]
})

avg_instructions = sum(r["total_instructions"] for r in runs) / len(runs) if runs else 0
avg_frontend_wall = sum(r["frontend_wall"] for r in runs) / len(runs) if runs else 0

results.append({
"commit_sha": commit_sha,
"commit_msg": commit_msg,
"runs": runs,
"avg_instructions": avg_instructions,
"avg_frontend_wall": avg_frontend_wall
})

finally:
# Always restore original branch
subprocess.run(["git", "checkout", "-f", current_branch], check=True)

os.makedirs(os.path.dirname(os.path.abspath(args.output)), exist_ok=True)
with open(args.output, 'w') as f:
json.dump(results, f, indent=2)

print(f"\nBenchmarking complete. Saved results to {args.output}")

if __name__ == "__main__":
main()
101 changes: 101 additions & 0 deletions Scripts/profile-compiler-stats.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env bash
set -euo pipefail

STATS_DIR="_diagnostics/stats"

echo "=== Cleaning build directory and previous diagnostics ==="
rm -rf "${STATS_DIR}" .build
mkdir -p "${STATS_DIR}"

echo "=== Building project with compiler statistics enabled ==="
swift build --disable-sandbox -Xswiftc -stats-output-dir -Xswiftc "${STATS_DIR}"

echo "=== Analyzing compiler stats ==="
python3 - "${STATS_DIR}" << 'EOF'
import json
import glob
import os
import sys

stats_dir = sys.argv[1]
json_files = glob.glob(os.path.join(stats_dir, "stats-*.json"))

if not json_files:
print(f"No stat JSON files found in {stats_dir}")
sys.exit(1)

total_instructions = 0
total_source_lines = 0
total_typechecking_wall = 0.0
total_silgen_wall = 0.0
total_frontend_wall = 0.0

frontend_stats = []

for filepath in json_files:
try:
with open(filepath, 'r') as f:
data = json.load(f)

instructions = data.get("Frontend.NumInstructionsExecuted", 0)
lines = data.get("AST.NumSourceLines", 0)

# Sema / Type checking time
tc_wall = data.get("time.swift.perform-whole-module-type-checking.wall", 0.0)
if tc_wall == 0.0:
tc_wall = data.get("time.swift.perform-sema.wall", 0.0)
if tc_wall == 0.0:
tc_wall = data.get("time.swift.Type checking and Semantic analysis.wall", 0.0)

sil_wall = data.get("time.swift.SILGen.wall", 0.0)

# Find frontend wall key
fe_wall = 0.0
for k, v in data.items():
if k.startswith("time.swift-frontend.") and k.endswith(".wall"):
fe_wall = v
break

total_instructions += instructions
total_source_lines += lines
total_typechecking_wall += tc_wall
total_silgen_wall += sil_wall
total_frontend_wall += fe_wall

filename = os.path.basename(filepath)
# Find swift file name from JSON filename if present
parts = filename.split("swift-frontend-FirebladeMath-")
swift_file = parts[1].split("-arm64")[0] if len(parts) > 1 else filename

if instructions > 0:
frontend_stats.append({
"file": swift_file,
"instructions": instructions,
"lines": lines,
"tc_wall": tc_wall,
"fe_wall": fe_wall
})
except Exception as e:
print(f"Error reading {filepath}: {e}")

# Sort by instructions descending
frontend_stats.sort(key=lambda x: x["instructions"], reverse=True)

print("\n--------------------------------------------------------------------------------")
print(f"{'Swift File':<40} | {'Instructions':<15} | {'Lines':<8} | {'Sema Wall (s)':<12}")
print("--------------------------------------------------------------------------------")
for item in frontend_stats[:10]:
print(f"{item['file']:<40} | {item['instructions']:>15,} | {item['lines']:>8,} | {item['tc_wall']:>12.3f}")

print("--------------------------------------------------------------------------------")
print("Compiler Statistics Summary:")
print("--------------------------------------------------")
print(f"Total Instructions Executed : {total_instructions:,}")
print(f"Total AST Source Lines : {total_source_lines:,}")
if total_source_lines > 0:
print(f"Instructions / Source Line : {total_instructions / total_source_lines:,.1f}")
print(f"Type-Checking Wall Time : {total_typechecking_wall:.3f} s")
print(f"Total Frontend Wall Time : {total_frontend_wall:.3f} s")
print(f"SILGen Wall Time : {total_silgen_wall:.3f} s")
print("--------------------------------------------------\n")
EOF
12 changes: 6 additions & 6 deletions Sources/FirebladeMath/Constants.swift
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
/// Factor to convert degrees to radians (Double).
public let kDegreeToRadians64 = Double.pi / 180.0
@inlinable public var kDegreeToRadians64: Double { Double.pi / 180.0 }
/// Factor to convert degrees to radians (Float).
public let kDegreeToRadians32 = Float(kDegreeToRadians64)
@inlinable public var kDegreeToRadians32: Float { Float(Double.pi / 180.0) }

/// Factor to convert radians to degrees (Double).
public let kRadiansToDegree64: Double = 180.0 / Double.pi
@inlinable public var kRadiansToDegree64: Double { 180.0 / Double.pi }
/// Factor to convert radians to degrees (Float).
public let kRadiansToDegree32 = Float(kRadiansToDegree64)
@inlinable public var kRadiansToDegree32: Float { Float(180.0 / Double.pi) }

/// Extension to add constants to Float.
extension Float {
/// Half of Pi (π/2).
public static let halfPi = Float(Double.halfPi)
@inlinable public static var halfPi: Float { Float.pi * 0.5 }
}

/// Extension to add constants to Double.
extension Double {
/// Half of Pi (π/2).
public static let halfPi: Double = .pi * 0.5
@inlinable public static var halfPi: Double { Double.pi * 0.5 }
}
12 changes: 9 additions & 3 deletions Sources/FirebladeMath/Functions/abs.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import Foundation

#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#else
import Foundation
#endif

/// Computes the absolute value of a floating point value x.
///
/// - Parameter x: floating point value
/// - Returns: If successful, returns the absolute value of x (|x|). The value returned is exact and does not depend on any rounding modes.
public func abs(_ x: Float) -> Float {
fabsf(x)
#if canImport(Darwin)
return Darwin.fabsf(x)
#elseif canImport(Glibc)
return Glibc.fabsf(x)
#else
return Foundation.fabsf(x)
#endif
}

/// Computes the absolute value of a floating point value x.
Expand Down
4 changes: 2 additions & 2 deletions Sources/FirebladeMath/Functions/acos.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Foundation

#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#else
import Foundation
#endif

/// Computes the principal value of the arc cosine of x.
Expand Down
4 changes: 2 additions & 2 deletions Sources/FirebladeMath/Functions/acosh.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Foundation

#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#else
import Foundation
#endif

/// Computes the inverse hyperbolic cosine of x.
Expand Down
4 changes: 2 additions & 2 deletions Sources/FirebladeMath/Functions/asin.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Foundation

#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#else
import Foundation
#endif

/// Computes the principal values of the arc sine of x.
Expand Down
4 changes: 2 additions & 2 deletions Sources/FirebladeMath/Functions/asinh.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Foundation

#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#else
import Foundation
#endif

/// Computes the inverse hyperbolic sine of x.
Expand Down
Loading
Loading