Collect normal-mode profiles from XCUITest target apps - #3037
karim-alweheshy wants to merge 2 commits into
Conversation
f4f4b97 to
a71f243
Compare
|
CI follow-up: a no-source-change retry is fully green across Bazel 8.0.1, Bazel 8.x, Bazel 9.x, last-green Bazel, buildifier, and documentation tests. The prior failure had the same unrelated simulator and coverage targets time out in both Bazel 8 lanes; this PR’s new profiling integration target did not fail, and an earlier same-patch lane also passed. I therefore treated the failure as shared simulator infrastructure noise and did not add a source workaround. |
04101b7 to
d4cdf16
Compare
|
@aaronsky @keith, could one of you review this XCUITest runner change when convenient? It preserves the existing test-process profile collection and adds normal-mode collection from the launched target app, with focused integration coverage. The no-source-change CI retry is green across every supported Bazel lane. |
🤖 AI Disclaimer
I used Codex to help investigate, implement, and test this change. I reviewed
the resulting behavior against LLVM's profile format and exercised the
integration test locally.
Why this work
XCUITests can drive realistic app journeys such as launch, navigation, data
loading, scrolling, and user interaction. Those journeys are useful inputs for
instrumentation PGO only if the profile comes from the target app process that
executes the production code.
The current
ios_xctestrun_runnercan collect coverage from the XCTest runner,but it cannot put
LLVM_PROFILE_FILEin the XCUITest target app's environmentor retrieve that app's raw profiles. Xcode's
ClangProfileDataDirectoryPathpath also selects compiler-rt continuous mode.That mode preserves execution counters, but its writer omits LLVM value profile
data such as indirect-call targets. A counter-complete profile can therefore
still be insufficient for indirect-call promotion and profile-guided
devirtualization.
What this enables
This change lets a Bazel XCUITest turn a real target-app journey into fresh,
normal-mode LLVM profile data:
.profrawfiles for inspection or later merging;Coverage.profdataartifact suitable as input to a laterinstrumentation-PGO build;
sizes, that continuous-mode coverage does not write; and
invalid one, instead of silently accepting stale or incomplete data.
How a PGO build uses it
A subsequent optimized Swift build can consume the merged profile with
-profile-use=<profdata>; Clang's corresponding IR-PGO path uses-fprofile-use=<path>. LLVM then uses two complementary classes of information:call sites. They guide inlining, branch and block layout, hot/cold splitting,
function placement, and optimization-versus-code-size decisions.
behind a function pointer, closure, callback, or other surviving indirect
dispatch.
PGOIndirectCallPromotioncan guard for the common target and turnits hot path into a direct call, enabling further inlining, constant
propagation, specialization, and devirtualization.
PGOMemOPSizeOptspecialize operationssuch as
memcpy,memmove,memset,memcmp, andbcmpfor their hottestobserved sizes so they can become more efficient inline sequences.
optional profiling mode is enabled, allowing more efficient virtual-call
promotion.
Continuous mode retains the execution counters, so it can still support basic
counter-guided optimization. The missing value records remove the additional
call-target and operation-size specialization opportunities above.
This PR does not invoke the profile-use compilation, guarantee that every
record results in a transformation, or change the release build by itself. It
provides the reliable target-app profile collection stage; a later optimized
build must consume
Coverage.profdata, and LLVM decides which transformationsare profitable. Objective-C message sends are not generally equivalent to LLVM
indirect-call sites and are not implied by this claim.
How collection works
Passing:
makes the runner:
host app and a safe per-process filename.
LLVM_PROFILE_FILEin the target app throughUITargetAppEnvironmentVariables, rather than setting it only on the XCTestrunner.
ClangProfileDataDirectoryPathand its paired coverage metadata fromthe generated xctestrun file so Xcode does not select continuous mode.
xcodebuildstarts.explicitly flush it at a chosen journey boundary with
__llvm_profile_write_file().matching files newer than the marker, and copy them to
target-app-profrawin Bazel's undeclared outputs.llvm-profdataintoCoverage.profdata, failing the test if no fresh profile exists or mergingfails.
The filename must be directly under
%t, contain%pas its only percentsubstitution, and contain no glob characters, backslashes, or line breaks.
%pprevents separate target-app processes from overwriting one another.An explicit flush must run inside the target app because that process owns the
profile counters and value records. A call or swizzle in the XCTest runner
cannot flush another process. An explicit call is not required when
compiler-rt's normal-exit writer already produces a fresh profile, but it is
useful when the journey boundary must be deterministic or the app may later be
force-terminated or crash. The rule documentation includes Objective-C and
Swift examples.
Related to #338.
Verification
Tested locally with Bazel 9.2.0 and Xcode 26.0 (17A321):
bazel build //apple/testing/default_runner:ios_default_runner//test:ios_xctestrun_runner_ui_test, covering:&filenames and rejection of unsupported backslashes;failures, pre-action failure, and TERM interruption;
simulator cleanup; and
cleanup exit 148 while preserving the XCResult and fresh profile outputs.
bazel test //doc:check_iosshellcheck test/ios_xctestrun_runner_ui_test.shgit diff --checkand Buildifier checksThe broader runner unit suite is locally limited by the repository fixture's
iPhone Xsselection being incompatible with the installed iOS 26 runtime.The focused simulator executions above ran on
iPhone 16/iOS 26.0.The committed integration case launches an IR-instrumented target app twice,
explicitly flushes in the target process, collects two fresh raw profiles,
merges them, and verifies with
llvm-profdata show --ic-targetsthat the mergedprofile contains an indirect-call target record.