@@ -182,7 +182,6 @@ pub const BuildFile = struct {
182182pub const Handle = struct {
183183 uri : Uri ,
184184 tree : Ast ,
185- trigram_store : TrigramStore ,
186185 /// Contains one entry for every import in the document
187186 import_uris : std .ArrayListUnmanaged (Uri ) = .empty ,
188187 /// Contains one entry for every cimport in the document
@@ -198,6 +197,7 @@ pub const Handle = struct {
198197 lock : std.Thread.Mutex = .{},
199198 condition : std.Thread.Condition = .{},
200199
200+ trigram_store : TrigramStore = undefined ,
201201 document_scope : DocumentScope = undefined ,
202202 zir : std.zig.Zir = undefined ,
203203 zoir : std.zig.Zoir = undefined ,
@@ -230,6 +230,11 @@ pub const Handle = struct {
230230 /// `false` indicates the document only exists because it is a dependency of another document
231231 /// or has been closed with `textDocument/didClose`
232232 lsp_synced : bool = false ,
233+ /// true if a thread has acquired the permission to compute the `TrigramStore`
234+ /// all other threads will wait until the given thread has computed the `TrigramStore` before reading it.
235+ has_trigram_store_lock : bool = false ,
236+ /// true if `handle.impl.trigram_store` has been set
237+ has_trigram_store : bool = false ,
233238 /// true if a thread has acquired the permission to compute the `DocumentScope`
234239 /// all other threads will wait until the given thread has computed the `DocumentScope` before reading it.
235240 has_document_scope_lock : bool = false ,
@@ -245,7 +250,7 @@ pub const Handle = struct {
245250 /// all other threads will wait until the given thread has computed the `std.zig.Zoir` before reading it.
246251 /// true if `handle.impl.zoir` has been set
247252 has_zoir : bool = false ,
248- _ : u25 = 0 ,
253+ _ : u23 = 0 ,
249254 };
250255
251256 const ZirOrZoirStatus = enum {
@@ -269,14 +274,10 @@ pub const Handle = struct {
269274 var cimports = try collectCImports (allocator , tree );
270275 errdefer cimports .deinit (allocator );
271276
272- var trigram_store : TrigramStore = try .init (allocator , tree , .@"utf-16" );
273- errdefer trigram_store .deinit ();
274-
275277 return .{
276278 .uri = uri ,
277279 .tree = tree ,
278280 .cimports = cimports ,
279- .trigram_store = trigram_store ,
280281 .impl = .{
281282 .status = .init (@bitCast (Status {
282283 .lsp_synced = lsp_synced ,
@@ -298,6 +299,7 @@ pub const Handle = struct {
298299 if (status .has_zir ) self .impl .zir .deinit (allocator );
299300 if (status .has_zoir ) self .impl .zoir .deinit (allocator );
300301 if (status .has_document_scope ) self .impl .document_scope .deinit (allocator );
302+ if (status .has_trigram_store ) self .impl .trigram_store .deinit (allocator );
301303 allocator .free (self .tree .source );
302304 self .tree .deinit (allocator );
303305
@@ -307,8 +309,6 @@ pub const Handle = struct {
307309 for (self .cimports .items (.source )) | source | allocator .free (source );
308310 self .cimports .deinit (allocator );
309311
310- self .trigram_store .deinit (allocator );
311-
312312 switch (self .impl .associated_build_file ) {
313313 .none , .resolved = > {},
314314 .unresolved = > | * payload | payload .deinit (allocator ),
@@ -319,7 +319,19 @@ pub const Handle = struct {
319319
320320 pub fn getDocumentScope (self : * Handle ) error {OutOfMemory }! DocumentScope {
321321 if (self .getStatus ().has_document_scope ) return self .impl .document_scope ;
322- return try self .getDocumentScopeCold ();
322+ return try self .getLazy (DocumentScope , "document_scope" , struct {
323+ fn create (handle : * Handle , allocator : std.mem.Allocator ) error {OutOfMemory }! DocumentScope {
324+ var document_scope : DocumentScope = try .init (allocator , handle .tree );
325+ errdefer document_scope .deinit (allocator );
326+
327+ // remove unused capacity
328+ document_scope .extra .shrinkAndFree (allocator , document_scope .extra .items .len );
329+ try document_scope .declarations .setCapacity (allocator , document_scope .declarations .len );
330+ try document_scope .scopes .setCapacity (allocator , document_scope .scopes .len );
331+
332+ return document_scope ;
333+ }
334+ });
323335 }
324336
325337 /// Asserts that `getDocumentScope` has been previously called on `handle`.
@@ -330,16 +342,55 @@ pub const Handle = struct {
330342 return self .impl .document_scope ;
331343 }
332344
345+ pub fn getTrigramStore (self : * Handle ) error {OutOfMemory }! TrigramStore {
346+ if (self .getStatus ().has_trigram_store ) return self .impl .trigram_store ;
347+ return try self .getLazy (TrigramStore , "trigram_store" , struct {
348+ fn create (handle : * Handle , allocator : std.mem.Allocator ) error {OutOfMemory }! TrigramStore {
349+ return try .init (allocator , handle .tree , .@"utf-16" ); // TODO
350+ }
351+ });
352+ }
353+
354+ /// Asserts that `getTrigramStore` has been previously called on `handle`.
355+ pub fn getTrigramStoreCached (self : * Handle ) TrigramStore {
356+ if (builtin .mode == .Debug ) {
357+ std .debug .assert (self .getStatus ().has_trigram_store );
358+ }
359+ return self .impl .trigram_store ;
360+ }
361+
333362 pub fn getZir (self : * Handle ) error {OutOfMemory }! std.zig.Zir {
334363 std .debug .assert (self .tree .mode == .zig );
335364 if (self .getStatus ().has_zir ) return self .impl .zir ;
336- return try self .getZirOrZoirCold (.zir );
365+ return try self .getLazy (std .zig .Zir , "zir" , struct {
366+ fn create (handle : * Handle , allocator : std.mem.Allocator ) error {OutOfMemory }! std.zig.Zir {
367+ const tracy_zone = tracy .traceNamed (@src (), "AstGen.generate" );
368+ defer tracy_zone .end ();
369+
370+ var zir = try std .zig .AstGen .generate (allocator , handle .tree );
371+ errdefer zir .deinit (allocator );
372+
373+ // remove unused capacity
374+ var instructions = zir .instructions .toMultiArrayList ();
375+ try instructions .setCapacity (allocator , instructions .len );
376+ zir .instructions = instructions .slice ();
377+
378+ return zir ;
379+ }
380+ });
337381 }
338382
339383 pub fn getZoir (self : * Handle ) error {OutOfMemory }! std.zig.Zoir {
340384 std .debug .assert (self .tree .mode == .zon );
341385 if (self .getStatus ().has_zoir ) return self .impl .zoir ;
342- return try self .getZirOrZoirCold (.zoir );
386+ return try self .getLazy (std .zig .Zoir , "zoir" , struct {
387+ fn create (handle : * Handle , allocator : std.mem.Allocator ) error {OutOfMemory }! std.zig.Zoir {
388+ const tracy_zone = tracy .traceNamed (@src (), "ZonGen.generate" );
389+ defer tracy_zone .end ();
390+
391+ return try std .zig .ZonGen .generate (allocator , handle .tree , .{});
392+ }
393+ });
343394 }
344395
345396 /// Returns the associated build file (build.zig) of the handle.
@@ -425,103 +476,43 @@ pub const Handle = struct {
425476 return .none ;
426477 }
427478
428- fn getDocumentScopeCold (self : * Handle ) error {OutOfMemory }! DocumentScope {
479+ fn getLazy (
480+ self : * Handle ,
481+ comptime T : type ,
482+ comptime name : []const u8 ,
483+ comptime Context : type ,
484+ ) error {OutOfMemory }! T {
429485 @branchHint (.cold );
430486 const tracy_zone = tracy .trace (@src ());
431487 defer tracy_zone .end ();
432488
433- self .impl .lock .lock ();
434- defer self .impl .lock .unlock ();
435- while (true ) {
436- const status = self .getStatus ();
437- if (status .has_document_scope ) break ;
438- if (status .has_document_scope_lock or
439- self .impl .status .bitSet (@bitOffsetOf (Status , "has_document_scope_lock" ), .release ) != 0 )
440- {
441- // another thread is currently computing the document scope
442- self .impl .condition .wait (& self .impl .lock );
443- continue ;
444- }
445- defer self .impl .condition .broadcast ();
446-
447- self .impl .document_scope = blk : {
448- var document_scope : DocumentScope = try .init (self .impl .allocator , self .tree );
449- errdefer document_scope .deinit (self .impl .allocator );
450-
451- // remove unused capacity
452- document_scope .extra .shrinkAndFree (self .impl .allocator , document_scope .extra .items .len );
453- try document_scope .declarations .setCapacity (self .impl .allocator , document_scope .declarations .len );
454- try document_scope .scopes .setCapacity (self .impl .allocator , document_scope .scopes .len );
455-
456- break :blk document_scope ;
457- };
458- const old_has_document_scope = self .impl .status .bitSet (@bitOffsetOf (Status , "has_document_scope" ), .release ); // atomically set has_document_scope
459- std .debug .assert (old_has_document_scope == 0 ); // race condition: another thread set `has_document_scope` even though we hold the lock
460- }
461- return self .impl .document_scope ;
462- }
463-
464- fn getZirOrZoirCold (self : * Handle , comptime kind : enum { zir , zoir }) error {OutOfMemory }! switch (kind ) {
465- .zir = > std .zig .Zir ,
466- .zoir = > std .zig .Zoir ,
467- } {
468- @branchHint (.cold );
469- const tracy_zone = tracy .trace (@src ());
470- defer tracy_zone .end ();
471-
472- const has_field = "has_" ++ @tagName (kind );
473- const has_lock_field = "has_" ++ @tagName (kind ) ++ "_lock" ;
489+ const has_data_field_name = "has_" ++ name ;
490+ const has_lock_field_name = "has_" ++ name ++ "_lock" ;
474491
475492 self .impl .lock .lock ();
476493 defer self .impl .lock .unlock ();
477494 while (true ) {
478495 const status = self .getStatus ();
479- if (@field (status , has_field )) break ;
480- if (@field (status , has_lock_field ) or
481- self .impl .status .bitSet (@bitOffsetOf (Status , has_lock_field ), .release ) != 0 )
496+ if (@field (status , has_data_field_name )) break ;
497+ if (@field (status , has_lock_field_name ) or
498+ self .impl .status .bitSet (@bitOffsetOf (Status , has_lock_field_name ), .release ) != 0 )
482499 {
483- // another thread is currently computing the ZIR
500+ // another thread is currently computing the data
484501 self .impl .condition .wait (& self .impl .lock );
485502 continue ;
486503 }
487504 defer self .impl .condition .broadcast ();
488505
489- switch (kind ) {
490- .zir = > {
491- const tracy_zone_inner = tracy .traceNamed (@src (), "AstGen.generate" );
492- defer tracy_zone_inner .end ();
493-
494- var zir = try std .zig .AstGen .generate (self .impl .allocator , self .tree );
495- errdefer zir .deinit (self .impl .allocator );
496-
497- // remove unused capacity
498- var instructions = zir .instructions .toMultiArrayList ();
499- try instructions .setCapacity (self .impl .allocator , instructions .len );
500- zir .instructions = instructions .slice ();
501-
502- self .impl .zir = zir ;
503- },
504- .zoir = > {
505- const tracy_zone_inner = tracy .traceNamed (@src (), "ZonGen.generate" );
506- defer tracy_zone_inner .end ();
507-
508- var zoir = try std .zig .ZonGen .generate (self .impl .allocator , self .tree , .{});
509- errdefer zoir .deinit (self .impl .allocator );
510-
511- self .impl .zoir = zoir ;
512- },
513- }
506+ @field (self .impl , name ) = try Context .create (self , self .impl .allocator );
507+ errdefer comptime unreachable ;
514508
515- const old_has = self .impl .status .bitSet (@bitOffsetOf (Status , has_field ), .release ); // atomically set has_[zir|zoir]
516- std .debug .assert (old_has == 0 ); // race condition: another thread set Zir or Zoir even though we hold the lock
509+ const old_has_data = self .impl .status .bitSet (@bitOffsetOf (Status , has_data_field_name ), .release );
510+ std .debug .assert (old_has_data == 0 ); // race condition
517511 }
518- return switch (kind ) {
519- .zir = > self .impl .zir ,
520- .zoir = > self .impl .zoir ,
521- };
512+ return @field (self .impl , name );
522513 }
523514
524- fn getStatus (self : * const Handle ) Status {
515+ pub fn getStatus (self : * const Handle ) Status {
525516 return @bitCast (self .impl .status .load (.acquire ));
526517 }
527518
@@ -1006,6 +997,47 @@ fn invalidateBuildFileWorker(self: *DocumentStore, build_file: *BuildFile) void
1006997 }
1007998}
1008999
1000+ pub fn loadTrigramStores (self : * DocumentStore ) error {OutOfMemory }! []* DocumentStore.Handle {
1001+ if (builtin .single_threaded ) {
1002+ for (self .handles .values ()) | handle | {
1003+ _ = try handle .getTrigramStore ();
1004+ }
1005+ return ;
1006+ }
1007+
1008+ const handles = handles : {
1009+ self .lock .lock ();
1010+ defer self .lock .unlock ();
1011+ break :handles try self .allocator .dupe (* DocumentStore .Handle , self .handles .values ());
1012+ };
1013+ errdefer self .allocator .free (handles );
1014+
1015+ const loadTrigramStore = struct {
1016+ fn loadTrigramStore (
1017+ handle : * Handle ,
1018+ did_out_of_memory : * std .atomic .Value (bool ),
1019+ ) void {
1020+ _ = handle .getTrigramStore () catch {
1021+ did_out_of_memory .store (true , .release );
1022+ };
1023+ }
1024+ }.loadTrigramStore ;
1025+
1026+ var wait_group : std.Thread.WaitGroup = .{};
1027+ var did_out_of_memory : std .atomic .Value (bool ) = .init (false );
1028+
1029+ for (handles ) | handle | {
1030+ const status = handle .getStatus ();
1031+ if (status .has_trigram_store ) continue ;
1032+ self .thread_pool .spawnWg (& wait_group , loadTrigramStore , .{ handle , & did_out_of_memory });
1033+ }
1034+ self .thread_pool .waitAndWork (& wait_group );
1035+
1036+ if (did_out_of_memory .load (.acquire )) return error .OutOfMemory ;
1037+
1038+ return handles ;
1039+ }
1040+
10091041pub fn isBuildFile (uri : Uri ) bool {
10101042 return std .mem .endsWith (u8 , uri , "/build.zig" );
10111043}
0 commit comments