@@ -45,6 +45,12 @@ MappedCodeCacheFile::~MappedCodeCacheFile() {
4545 if (CacheManager) {
4646 CacheManager->UnregisterMappedCodeBuffer (*this );
4747 }
48+
49+ #ifndef _WIN32
50+ if (CodeBuffer.empty ()) {
51+ FEXCore::Allocator::munmap (CodeBuffer.data (), CodeBuffer.size_bytes ());
52+ }
53+ #endif
4854}
4955
5056void AbstractCodeCache::RegisterMappedCodeBuffer (MappedCodeCacheFile& Code) {
@@ -346,7 +352,7 @@ bool CodeCache::SaveData(Core::InternalThreadState& Thread, int fd, const Execut
346352
347353 // Dump the host code (relocated for position-independent serialization)
348354 std::span CodeBufferData (reinterpret_cast <std::byte*>(CodeBuffer->Ptr ), reinterpret_cast <std::byte*>(CodeBuffer->Ptr ) + CTX .LatestOffset );
349- if (!ApplyCodeRelocations (SerializedBaseAddress, CodeBufferData, Relocations, true )) {
355+ if (!ApplyCodeRelocations (SerializedBaseAddress, CodeBufferData, Relocations, 0 , true )) {
350356 LOGMAN_THROW_A_FMT (false , " Failed to apply code relocations" );
351357 return false ;
352358 }
@@ -428,7 +434,7 @@ void CodeCache::Validate(const ExecutableFileSectionInfo& Section, fextl::set<ui
428434 NewRelocations.erase (std::remove_if (NewRelocations.begin (), NewRelocations.end (), [](const CPU ::Relocation& Reloc) {
429435 return Reloc.Header .Type != CPU ::RelocationTypes::RELOC_NAMED_SYMBOL_LITERAL && Reloc.Header .Type != CPU ::RelocationTypes::RELOC_NAMED_THUNK_MOVE ;
430436 }));
431- (void )ApplyCodeRelocations (Section.FileStartVA , CodeBufferRangeRef, NewRelocations, false );
437+ (void )ApplyCodeRelocations (Section.FileStartVA , CodeBufferRangeRef, NewRelocations, 0 , false );
432438
433439 if (ValidationCTX->LatestOffset <= CodeBufferRangeRef.size ()) {
434440 // Reference compilation produced fewer bytes than our cache, so validation is going to fail.
@@ -490,11 +496,13 @@ void CodeCache::Validate(const ExecutableFileSectionInfo& Section, fextl::set<ui
490496}
491497
492498bool CodeCache::ApplyCodeRelocations (uint64_t GuestEntry, std::span<std::byte> Code,
493- std::span<const FEXCore::CPU ::Relocation> EntryRelocations, bool ForStorage) {
499+ std::span<const FEXCore::CPU ::Relocation> EntryRelocations, uint32_t RelocationOffset, bool ForStorage) {
494500 CPU ::Arm64Emitter Emitter (&CTX , Code.data (), Code.size_bytes ());
495501 for (size_t j = 0 ; j < EntryRelocations.size (); ++j) {
496502 const FEXCore::CPU ::Relocation& Reloc = EntryRelocations[j];
497- Emitter.SetCursorOffset (Reloc.Header .Offset );
503+ LOGMAN_THROW_A_FMT (Reloc.Header .Offset >= RelocationOffset, " Invalid relocation offset" );
504+ LOGMAN_THROW_A_FMT (Reloc.Header .Offset - RelocationOffset < Code.size_bytes (), " Invalid relocation offset" );
505+ Emitter.SetCursorOffset (Reloc.Header .Offset - RelocationOffset);
498506
499507 switch (Reloc.Header .Type ) {
500508 case FEXCore::CPU ::RelocationTypes::RELOC_NAMED_SYMBOL_LITERAL : {
@@ -582,8 +590,20 @@ CodeCache::LoadCache(std::span<std::byte> CacheFile, const ExecutableFileInfo& F
582590 Cursor = reinterpret_cast <std::byte*>(AlignUp (reinterpret_cast <uintptr_t >(Cursor), Utils::FEX_PAGE_SIZE ));
583591 auto CodeDataInFile = std::span {Cursor, header.CodeBufferSize };
584592
585- // Make code data inaccessible until finalized
586- Allocator::VirtualProtect (CodeDataInFile.data (), header.CodeBufferSize , Allocator::ProtectOptions::None);
593+ #ifndef _WIN32
594+ // Allocate target memory for post-relocation code. This is PROT_NONE until
595+ // the first execution, so that contents can be lazily populated in a
596+ // frontend-provided segfault handler.
597+ void * CodeBufferAllocation = Allocator::mmap (nullptr , header.CodeBufferSize , PROT_NONE , MAP_PRIVATE | MAP_ANONYMOUS , -1 , 0 );
598+ if (CodeBufferAllocation == MAP_FAILED ) {
599+ LogMan::Msg::EFmt (" Failed to reserve target memory for code cache" );
600+ return nullptr ;
601+ }
602+ auto CodeBuffer = std::span {static_cast <std::byte*>(CodeBufferAllocation), header.CodeBufferSize };
603+ #else
604+ // TODO: Implement lazy mapping on Windows
605+ auto CodeBuffer = CodeDataInFile;
606+ #endif
587607
588608 // Group relocations by page
589609 size_t NumPages = header.CodeBufferSize / Utils::FEX_PAGE_SIZE ;
@@ -600,7 +620,7 @@ CodeCache::LoadCache(std::span<std::byte> CacheFile, const ExecutableFileInfo& F
600620
601621 auto Storage = FEXCore::Allocator::aligned_alloc (alignof (MappedCodeCacheFile), sizeof (MappedCodeCacheFile));
602622 return fextl::unique_ptr<MappedCodeCacheFile>(
603- new (Storage) MappedCodeCacheFile {this , CacheFile, CodeDataInFile, BlockListStart, header.NumBlocks , header.NumCodePages ,
623+ new (Storage) MappedCodeCacheFile {this , CacheFile, CodeDataInFile, CodeBuffer, BlockListStart, header.NumBlocks , header.NumCodePages ,
604624 std::move (PageRelocationRanges), fextl::vector<bool >(NumPages), FileStartVA});
605625}
606626
@@ -674,7 +694,7 @@ bool CodeCache::EnableLoadedSection(Core::InternalThreadState* Thread, MappedCod
674694 }
675695
676696 // Guest code pages
677- auto * Cursor = Code.CodeBuffer .data () + Code.CodeBuffer .size_bytes ();
697+ auto * Cursor = Code.CodeBufferInFile .data () + Code.CodeBufferInFile .size_bytes ();
678698 fextl::vector<uint64_t > Entrypoints;
679699 for (uint32_t i = 0 ; i < Code.NumCodePages ; ++i) {
680700 uint64_t CodePage;
@@ -699,7 +719,12 @@ bool CodeCache::EnableLoadedSection(Core::InternalThreadState* Thread, MappedCod
699719 }
700720 }
701721
722+ #ifndef _WIN32
702723 if (!EnableLazyCodeCaching || EnableCodeCacheValidation) {
724+ #else
725+ // TODO: Implement lazy mapping on Windows
726+ if (true ) {
727+ #endif
703728 auto Range = SelectCodeRangeToFinalize (Code, 0 , Code.CodeBuffer .size_bytes () / Utils::FEX_PAGE_SIZE );
704729 FinalizeCodePages (Code, Range);
705730 }
@@ -792,29 +817,67 @@ void CodeCache::FinalizeCodePages(MappedCodeCacheFile& Code, std::span<std::byte
792817 const size_t StartOffset = CodeRange.data () - Code.CodeBuffer .data ();
793818 const auto StartPage = StartOffset / Utils::FEX_PAGE_SIZE ;
794819 const auto EndPage = StartPage + CodeRange.size_bytes () / Utils::FEX_PAGE_SIZE ;
820+ const size_t Size = CodeRange.size_bytes ();
795821
796822 // None of the selected pages should be loaded at all; otherwise, SelectCodeRangeToFinalize returned inconsistent ranges
797823 LOGMAN_THROW_A_FMT (std::find (Code.LoadedPages .begin () + StartPage, Code.LoadedPages .begin () + EndPage, true ) == Code.LoadedPages .begin () + EndPage,
798824 " Inconsistent page load state" );
799825
800826 FEXCORE_PROFILE_SCOPED (" FinalizeCodePages" );
801827
802- // Map writeable to allow applying relocations
803- // TODO: Are there any remaining race conditions here?
804- Allocator::VirtualProtect (CodeRange.data (), CodeRange.size_bytes (), Allocator::ProtectOptions::Write);
828+ #ifndef _WIN32
829+ // Atomicity is critical when making the finalized code data visible.
830+ // We ensure this by remapping a temporary buffer onto the PROT_NONE
831+ // placeholder page in CodeBuffer. Some constraints to keep in mind are:
832+ // 1. Pages can't be write-only (readability is implicitly added), so
833+ // we can't change CodeBuffer from PROT_NONE to PROT_WRITE even for just
834+ // a short duration
835+ // 2. Naive mremap from CodeBufferInFile to CodeBuffer would leave a gap in
836+ // the former, which would make cleanup overly complicated
837+ //
838+ // Due to (1), we can't apply relocations in place (CodeBufferInFile); at
839+ // least a secondary buffer is needed for execution (CodeBuffer).
840+ // Due to (2), a third buffer is temporarily allocated here and freed on
841+ // completion. The final code data is computed here and then the memory
842+ // is remapped onto CodeBuffer.
843+ auto * Staging = reinterpret_cast <std::byte*>(Allocator::VirtualAlloc (nullptr , Size, true ));
844+ if (!Staging) {
845+ ERROR_AND_DIE_FMT (" Failed to allocate {} bytes of staging memory for code-cache finalization" , Size);
846+ }
847+
848+ // Copy code from the cache file to the staging buffer
849+ memcpy (Staging, Code.CodeBufferInFile .data () + StartOffset, Size);
805850
806851 // Apply relocations
852+ auto StagingSpan = std::span {Staging, Size};
807853 for (size_t i = StartPage; i < EndPage; ++i) {
808854 auto PageRelocations = SpanPageRelocations (Code, i);
809- (void )ApplyCodeRelocations (Code.GuestBase , Code. CodeBuffer , PageRelocations, false );
855+ (void )ApplyCodeRelocations (Code.GuestBase , StagingSpan , PageRelocations, static_cast < uint32_t >(StartOffset) , false );
810856 Code.LoadedPages [i] = true ;
811857 }
812858
813- ARMEmitter::Emitter::ClearICache (CodeRange.data (), CodeRange.size_bytes ());
814- if (!Allocator::VirtualProtect (CodeRange.data (), CodeRange.size_bytes (),
815- Allocator::ProtectOptions::Read | Allocator::ProtectOptions::Write | Allocator::ProtectOptions::Exec)) {
816- ERROR_AND_DIE_FMT (" VirtualProtect failed" );
859+ // Atomically make the finalized code data visible by remapping the staging
860+ // buffer onto the requested CodeBuffer window. MREMAP_DONTUNMAP is used to
861+ // leave the old VA range reserved so that we can cleanly deallocate it
862+ // through Allocator.
863+ void * RemapResult = ::mremap (Staging, Size, Size, MREMAP_FIXED | MREMAP_MAYMOVE | MREMAP_DONTUNMAP , CodeRange.data ());
864+ if (RemapResult == MAP_FAILED ) {
865+ ERROR_AND_DIE_FMT (" {}: mremap failed: {}" , __FUNCTION__, errno);
817866 }
867+ Allocator::VirtualFree (Staging, Size);
868+
869+ // Release resident file pages that will no longer be needed. The VA range is left allocated to allow cleanup with a single VirtualFree.
870+ Allocator::VirtualDontNeed (Code.CodeBufferInFile .data () + StartOffset, Size);
871+ #else
872+ // TODO: Implement lazy mapping on Windows
873+ for (size_t i = StartPage; i < EndPage; ++i) {
874+ auto PageRelocations = SpanPageRelocations (Code, i);
875+ (void )ApplyCodeRelocations (Code.GuestBase , Code.CodeBuffer , PageRelocations, 0 , false );
876+ Code.LoadedPages [i] = true ;
877+ }
878+ #endif
879+
880+ ARMEmitter::Emitter::ClearICache (CodeRange.data (), Size);
818881}
819882
820883} // namespace FEXCore::Context
0 commit comments