Skip to content

Commit fc80102

Browse files
committed
lazy and parallel trigram store creation
1 parent be86d79 commit fc80102

File tree

2 files changed

+128
-96
lines changed

2 files changed

+128
-96
lines changed

src/DocumentStore.zig

Lines changed: 121 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,6 @@ pub const BuildFile = struct {
182182
pub 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+
10091041
pub fn isBuildFile(uri: Uri) bool {
10101042
return std.mem.endsWith(u8, uri, "/build.zig");
10111043
}

src/Server.zig

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1981,15 +1981,14 @@ fn selectionRangeHandler(server: *Server, arena: std.mem.Allocator, request: typ
19811981
fn workspaceSymbolHandler(server: *Server, arena: std.mem.Allocator, request: types.WorkspaceSymbolParams) Error!lsp.ResultType("workspace/symbol") {
19821982
if (request.query.len < 3) return null;
19831983

1984-
// TODO: take this and get copy of handle ptrs
1985-
server.document_store.lock.lock();
1986-
defer server.document_store.lock.unlock();
1984+
const handles = try server.document_store.loadTrigramStores();
1985+
defer server.document_store.allocator.free(handles);
19871986

19881987
var symbols: std.ArrayListUnmanaged(types.WorkspaceSymbol) = .empty;
19891988
var declaration_buffer: std.ArrayListUnmanaged(TrigramStore.Declaration.Index) = .empty;
19901989

1991-
for (server.document_store.handles.keys(), server.document_store.handles.values()) |uri, handle| {
1992-
const trigram_store = &handle.trigram_store;
1990+
for (handles) |handle| {
1991+
const trigram_store = handle.getTrigramStoreCached();
19931992

19941993
declaration_buffer.clearRetainingCapacity();
19951994
try trigram_store.declarationsForQuery(arena, request.query, &declaration_buffer);
@@ -1998,15 +1997,16 @@ fn workspaceSymbolHandler(server: *Server, arena: std.mem.Allocator, request: ty
19981997
const names = slice.items(.name);
19991998
const ranges = slice.items(.range);
20001999

2000+
try symbols.ensureUnusedCapacity(arena, declaration_buffer.items.len);
20012001
for (declaration_buffer.items) |declaration| {
20022002
const name = names[@intFromEnum(declaration)];
20032003
const range = ranges[@intFromEnum(declaration)];
2004-
try symbols.append(arena, .{
2004+
symbols.appendAssumeCapacity(.{
20052005
.name = trigram_store.names.items[name.start..name.end],
20062006
.kind = .Variable,
20072007
.location = .{
20082008
.Location = .{
2009-
.uri = uri,
2009+
.uri = handle.uri,
20102010
.range = range,
20112011
},
20122012
},

0 commit comments

Comments
 (0)