JIT: hot-reload non-leaf files via single-module incremental split#194
Open
obj-p wants to merge 3 commits into
Open
JIT: hot-reload non-leaf files via single-module incremental split#194obj-p wants to merge 3 commits into
obj-p wants to merge 3 commits into
Conversation
bitmapImageRepForCachingDisplay inherits the host window's backingScaleFactor, so on a Retina (2x) display the snapshot came out 800x1200 instead of the logical 400x600, making output depend on which machine the daemon ran on. Build the NSBitmapImageRep manually at the point bounds so capture is 1x and reproducible across machines. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The structural split compiled the bulk into a prebuilt stable module that the hot file imported. That is one-directional, so a non-leaf hot file (one the bulk references, e.g. a previewed view used by another preview) failed with "cannot find <type> in scope" when the bulk compiled without it. Detect the non-leaf case (the stable-module compile fails) and compile the whole target module incrementally instead: the editable overlay plus the other sources under one module name, where the Swift driver recompiles only the hot file and reuses the bulk objects. References resolve in both directions. Because the overlay then uses the target's own module name, its @observable DesignTimeStore would re-register across generations, so non-leaf builds respawn the agent each structural edit. hotReloadStructural now exercises this on the JIT daemon. Its sync marker moves from "Compiled:" (only the non-JIT recompile logs it) to "Reloaded", which both daemon kinds log on a successful structural edit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
lookupInitialized read session->initialized outside the lock with no re-check, so two threads could both pass the guard and double-initialize the JITDylib (and the bool itself was a data race). Hold the lock across the check and the init so the flag is only touched under the lock. PreviewsAnonymousMapper::prepare and ::initialize decremented the upper_bound iterator with no bounds check, which is undefined behavior if the reservation map is empty or the address precedes all reservations. Guard r == begin(): prepare returns nullptr, initialize reports an Expected error. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
main(a5b58cc, the #190 merge) shipped the JIT split-compile path with onlythe leaf case handled. The split prebuilds a stable module from the target's
other sources and
@testable imports it from the edited file. When the editedfile is non-leaf (another file in the target references it, e.g.
ToDoView.swiftis used byToDoProviderPreview.swift), the stable compileexcludes the hot file but the bulk still references it, so it fails with
cannot find 'ToDoView' in scopeand the hot reload times out.Fix
Detect the non-leaf case (the stable-module compile throws) and compile the
whole target module incrementally instead: the edited file as an overlay
plus the other sources under one module name, where the Swift driver recompiles
only the hot file and reuses the bulk objects. References resolve both
directions, so the hot file lives in exactly one module.
Because the overlay then uses the target's own module name, its
@ObservableDesignTimeStorewould re-register across generations, so non-leaf buildsrespawn the JIT agent each structural edit (
requiresFreshAgent).Design notes / known tradeoffs
still serves the common case (editing the preview-bearing leaf file).
(
bulkIsNonLeaf). A real error in another bulk file would pin the session tothe slower whole-module path. Acceptable: it degrades speed, not correctness,
and the error still surfaces. Follow-up candidate.
Verification
hotReloadStructural(editsToDoView.swift, the non-leaf file) passes in~24s against the JIT daemon, where
maintimes out at 90s.PreviewsJITLinkTestssuite: 50/50 passed serially (--no-parallel).Commits
Snapshot: pin window capture to deterministic 1x(independent, also strandedoff
main)JIT: hot-reload non-leaf files via single-module incremental split(the fix)JIT C++: fix lookupInitialized lock and anon-mapper bounds(JIT-linkhardening)