@@ -411,6 +411,9 @@ et::task<> MasterServer::load_workspace() {
411411
412412 auto report = scan_dependency_graph (cdb, path_pool, dependency_graph);
413413
414+ // Build reverse include map so headers can find their host source files.
415+ dependency_graph.build_reverse_map ();
416+
414417 auto unresolved = report.includes_found - report.includes_resolved ;
415418 double accuracy =
416419 report.includes_found > 0
@@ -562,20 +565,191 @@ et::task<> MasterServer::load_workspace() {
562565 LOG_INFO (" CompileGraph initialized with {} module(s)" , path_to_module.size ());
563566}
564567
568+ std::optional<HeaderFileContext>
569+ MasterServer::resolve_header_context (std::uint32_t header_path_id) {
570+ // Find source files that transitively include this header.
571+ auto hosts = dependency_graph.find_host_sources (header_path_id);
572+ if (hosts.empty ()) {
573+ LOG_DEBUG (" resolve_header_context: no host sources for path_id={}" , header_path_id);
574+ return std::nullopt ;
575+ }
576+
577+ // Pick the first available host that has a CDB entry.
578+ std::uint32_t host_path_id = 0 ;
579+ std::vector<std::uint32_t > chain;
580+ for (auto candidate: hosts) {
581+ auto candidate_path = path_pool.resolve (candidate);
582+ auto results = cdb.lookup (candidate_path, {.suppress_logging = true });
583+ if (results.empty ())
584+ continue ;
585+ auto c = dependency_graph.find_include_chain (candidate, header_path_id);
586+ if (c.empty ())
587+ continue ;
588+ host_path_id = candidate;
589+ chain = std::move (c);
590+ break ;
591+ }
592+
593+ if (chain.empty ()) {
594+ LOG_DEBUG (" resolve_header_context: no usable host with include chain for path_id={}" ,
595+ header_path_id);
596+ return std::nullopt ;
597+ }
598+
599+ // Build preamble text: for each file in the chain except the last (target),
600+ // append all content up to (but not including) the line that includes the
601+ // next file in the chain.
602+ std::string preamble;
603+ for (std::size_t i = 0 ; i + 1 < chain.size (); ++i) {
604+ auto cur_id = chain[i];
605+ auto next_id = chain[i + 1 ];
606+
607+ auto cur_path = path_pool.resolve (cur_id);
608+ auto next_path = path_pool.resolve (next_id);
609+ auto next_filename = llvm::sys::path::filename (next_path);
610+
611+ // Prefer in-memory document text over disk content.
612+ std::string content;
613+ if (auto doc_it = documents.find (cur_id); doc_it != documents.end ()) {
614+ content = doc_it->second .text ;
615+ } else {
616+ auto buf = llvm::MemoryBuffer::getFile (cur_path);
617+ if (!buf) {
618+ LOG_WARN (" resolve_header_context: cannot read {}" , cur_path);
619+ return std::nullopt ;
620+ }
621+ content = (*buf)->getBuffer ().str ();
622+ }
623+
624+ // Scan line by line for the #include that brings in next_filename.
625+ llvm::StringRef content_ref (content);
626+ std::size_t line_start = 0 ;
627+ std::size_t include_line_start = std::string::npos;
628+ while (line_start <= content_ref.size ()) {
629+ auto newline_pos = content_ref.find (' \n ' , line_start);
630+ auto line_end =
631+ (newline_pos == llvm::StringRef::npos) ? content_ref.size () : newline_pos;
632+ auto line = content_ref.slice (line_start, line_end).trim ();
633+
634+ if (line.starts_with (" #include" ) || line.starts_with (" # include" )) {
635+ // Check if this line references the next file in the chain.
636+ if (line.contains (next_filename)) {
637+ include_line_start = line_start;
638+ break ;
639+ }
640+ }
641+
642+ line_start =
643+ (newline_pos == llvm::StringRef::npos) ? content_ref.size () + 1 : newline_pos + 1 ;
644+ }
645+
646+ // Emit a #line marker then all content before the include line.
647+ preamble += std::format (" #line 1 \" {}\"\n " , cur_path.str ());
648+ if (include_line_start != std::string::npos) {
649+ preamble += content_ref.substr (0 , include_line_start).str ();
650+ } else {
651+ // No matching include line found — emit the whole file to be safe.
652+ LOG_DEBUG (" resolve_header_context: include line for {} not found in {}, emitting full" ,
653+ next_filename,
654+ cur_path);
655+ preamble += content;
656+ }
657+ }
658+
659+ // Hash the preamble and write to cache directory.
660+ auto preamble_hash = llvm::xxh3_64bits (llvm::StringRef (preamble));
661+ auto preamble_filename = std::format (" {:016x}.h" , preamble_hash);
662+ auto preamble_dir = path::join (config.cache_dir , " header_context" );
663+ auto preamble_path = path::join (preamble_dir, preamble_filename);
664+
665+ if (!llvm::sys::fs::exists (preamble_path)) {
666+ auto ec = llvm::sys::fs::create_directories (preamble_dir);
667+ if (ec) {
668+ LOG_WARN (" resolve_header_context: cannot create dir {}: {}" ,
669+ preamble_dir,
670+ ec.message ());
671+ return std::nullopt ;
672+ }
673+ if (auto result = fs::write (preamble_path, preamble); !result) {
674+ LOG_WARN (" resolve_header_context: cannot write preamble {}: {}" ,
675+ preamble_path,
676+ result.error ().message ());
677+ return std::nullopt ;
678+ }
679+ LOG_INFO (" resolve_header_context: wrote preamble {} for header path_id={}" ,
680+ preamble_path,
681+ header_path_id);
682+ }
683+
684+ return HeaderFileContext{host_path_id, preamble_path, preamble_hash};
685+ }
686+
565687bool MasterServer::fill_compile_args (llvm::StringRef path,
566688 std::string& directory,
567689 std::vector<std::string>& arguments) {
568690 auto results = cdb.lookup (path, {.query_toolchain = true });
569- if (results.empty ()) {
570- LOG_WARN (" No CDB entry for {}" , path);
691+ if (!results.empty ()) {
692+ auto & ctx = results.front ();
693+ directory = ctx.directory .str ();
694+ arguments.clear ();
695+ for (auto * arg: ctx.arguments ) {
696+ arguments.emplace_back (arg);
697+ }
698+ return true ;
699+ }
700+
701+ // No direct CDB entry — try to compile the header in context of a host source.
702+ auto path_id = path_pool.intern (path);
703+
704+ // Use cached context if available; otherwise resolve.
705+ const HeaderFileContext* ctx_ptr = nullptr ;
706+ auto ctx_it = header_file_contexts.find (path_id);
707+ if (ctx_it != header_file_contexts.end ()) {
708+ ctx_ptr = &ctx_it->second ;
709+ } else {
710+ auto resolved = resolve_header_context (path_id);
711+ if (!resolved) {
712+ LOG_WARN (" No CDB entry and no header context for {}" , path);
713+ return false ;
714+ }
715+ header_file_contexts[path_id] = std::move (*resolved);
716+ ctx_ptr = &header_file_contexts[path_id];
717+ }
718+
719+ auto host_path = path_pool.resolve (ctx_ptr->host_path_id );
720+ auto host_results = cdb.lookup (host_path, {.query_toolchain = true });
721+ if (host_results.empty ()) {
722+ LOG_WARN (" fill_compile_args: host {} has no CDB entry" , host_path);
571723 return false ;
572724 }
573- auto & ctx = results.front ();
574- directory = ctx.directory .str ();
725+
726+ auto & host_ctx = host_results.front ();
727+ directory = host_ctx.directory .str ();
575728 arguments.clear ();
576- for (auto * arg: ctx.arguments ) {
577- arguments.emplace_back (arg);
578- }
729+
730+ // Copy host arguments, skipping the last element if it looks like a source
731+ // file path (i.e. not a flag). The header path is used as the main file by
732+ // the caller via CompileParams::path.
733+ auto num_args = host_ctx.arguments .size ();
734+ std::size_t copy_count = num_args;
735+ if (copy_count > 0 ) {
736+ llvm::StringRef last (host_ctx.arguments [copy_count - 1 ]);
737+ if (!last.starts_with (" -" ))
738+ copy_count -= 1 ;
739+ }
740+ for (std::size_t i = 0 ; i < copy_count; ++i) {
741+ arguments.emplace_back (host_ctx.arguments [i]);
742+ }
743+
744+ // Inject the preamble before the header so the compiler sees all context
745+ // code that normally precedes this header in the host translation unit.
746+ arguments.insert (arguments.begin () + 1 , ctx_ptr->preamble_path );
747+ arguments.insert (arguments.begin () + 1 , " -include" );
748+
749+ LOG_INFO (" fill_compile_args: using header context for {} (host={}, preamble={})" ,
750+ path,
751+ host_path,
752+ ctx_ptr->preamble_path );
579753 return true ;
580754}
581755
@@ -2018,6 +2192,22 @@ void MasterServer::register_handlers() {
20182192 }
20192193 }
20202194
2195+ // Invalidate header contexts whose host is the saved file.
2196+ // Collect entries to erase to avoid modifying the map while iterating.
2197+ llvm::SmallVector<std::uint32_t , 4 > stale_headers;
2198+ for (auto & [hdr_id, hdr_ctx]: header_file_contexts) {
2199+ if (hdr_ctx.host_path_id == path_id)
2200+ stale_headers.push_back (hdr_id);
2201+ }
2202+ for (auto hdr_id: stale_headers) {
2203+ header_file_contexts.erase (hdr_id);
2204+ auto doc_it = documents.find (hdr_id);
2205+ if (doc_it != documents.end ()) {
2206+ doc_it->second .ast_dirty = true ;
2207+ LOG_DEBUG (" didSave: invalidated header context for path_id={}" , hdr_id);
2208+ }
2209+ }
2210+
20212211 // Trigger background indexing after save.
20222212 schedule_indexing ();
20232213
0 commit comments