Skip to content

Commit eb326e1

Browse files
committed
M:N threading
* add std.atomic.QueueMpsc.isEmpty * make std.debug.global_allocator thread-safe * std.event.Loop: now you have to choose between - initSingleThreaded - initMultiThreaded * std.event.Loop multiplexes coroutines onto kernel threads * Remove std.event.Loop.stop. Instead the event loop run() function returns once there are no pending coroutines. * fix crash in ir.cpp for calling methods under some conditions * small progress self-hosted compiler, analyzing top level declarations * Introduce std.event.Lock for synchronizing coroutines * introduce std.event.Locked(T) for data that only 1 coroutine should modify at once. * make the self hosted compiler use multi threaded event loop * make std.heap.DirectAllocator thread-safe See #174 TODO: * call sched_getaffinity instead of hard coding thread pool size 4 * support for Windows and MacOS * #1194 * #1197
1 parent d8295c1 commit eb326e1

File tree

10 files changed

+833
-114
lines changed

10 files changed

+833
-114
lines changed

src-self-hosted/main.zig

+2-3
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,8 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Mo
384384
const zig_lib_dir = introspect.resolveZigLibDir(allocator) catch os.exit(1);
385385
defer allocator.free(zig_lib_dir);
386386

387-
var loop = try event.Loop.init(allocator);
387+
var loop: event.Loop = undefined;
388+
try loop.initMultiThreaded(allocator);
388389

389390
var module = try Module.create(
390391
&loop,
@@ -493,8 +494,6 @@ async fn processBuildEvents(module: *Module, watch: bool) void {
493494
switch (build_event) {
494495
Module.Event.Ok => {
495496
std.debug.warn("Build succeeded\n");
496-
// for now we stop after 1
497-
module.loop.stop();
498497
return;
499498
},
500499
Module.Event.Error => |err| {

src-self-hosted/module.zig

+242-15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const std = @import("std");
22
const os = std.os;
33
const io = std.io;
44
const mem = std.mem;
5+
const Allocator = mem.Allocator;
56
const Buffer = std.Buffer;
67
const llvm = @import("llvm.zig");
78
const c = @import("c.zig");
@@ -13,6 +14,7 @@ const ArrayList = std.ArrayList;
1314
const errmsg = @import("errmsg.zig");
1415
const ast = std.zig.ast;
1516
const event = std.event;
17+
const assert = std.debug.assert;
1618

1719
pub const Module = struct {
1820
loop: *event.Loop,
@@ -81,6 +83,8 @@ pub const Module = struct {
8183
link_out_file: ?[]const u8,
8284
events: *event.Channel(Event),
8385

86+
exported_symbol_names: event.Locked(Decl.Table),
87+
8488
// TODO handle some of these earlier and report them in a way other than error codes
8589
pub const BuildError = error{
8690
OutOfMemory,
@@ -232,6 +236,7 @@ pub const Module = struct {
232236
.test_name_prefix = null,
233237
.emit_file_type = Emit.Binary,
234238
.link_out_file = null,
239+
.exported_symbol_names = event.Locked(Decl.Table).init(loop, Decl.Table.init(loop.allocator)),
235240
});
236241
}
237242

@@ -272,38 +277,91 @@ pub const Module = struct {
272277
return;
273278
};
274279
await (async self.events.put(Event.Ok) catch unreachable);
280+
// for now we stop after 1
281+
return;
275282
}
276283
}
277284

278285
async fn addRootSrc(self: *Module) !void {
279286
const root_src_path = self.root_src_path orelse @panic("TODO handle null root src path");
287+
// TODO async/await os.path.real
280288
const root_src_real_path = os.path.real(self.a(), root_src_path) catch |err| {
281289
try printError("unable to get real path '{}': {}", root_src_path, err);
282290
return err;
283291
};
284292
errdefer self.a().free(root_src_real_path);
285293

294+
// TODO async/await readFileAlloc()
286295
const source_code = io.readFileAlloc(self.a(), root_src_real_path) catch |err| {
287296
try printError("unable to open '{}': {}", root_src_real_path, err);
288297
return err;
289298
};
290299
errdefer self.a().free(source_code);
291300

292-
var tree = try std.zig.parse(self.a(), source_code);
293-
defer tree.deinit();
294-
295-
//var it = tree.root_node.decls.iterator();
296-
//while (it.next()) |decl_ptr| {
297-
// const decl = decl_ptr.*;
298-
// switch (decl.id) {
299-
// ast.Node.Comptime => @panic("TODO"),
300-
// ast.Node.VarDecl => @panic("TODO"),
301-
// ast.Node.UseDecl => @panic("TODO"),
302-
// ast.Node.FnDef => @panic("TODO"),
303-
// ast.Node.TestDecl => @panic("TODO"),
304-
// else => unreachable,
305-
// }
306-
//}
301+
var parsed_file = ParsedFile{
302+
.tree = try std.zig.parse(self.a(), source_code),
303+
.realpath = root_src_real_path,
304+
};
305+
errdefer parsed_file.tree.deinit();
306+
307+
const tree = &parsed_file.tree;
308+
309+
// create empty struct for it
310+
const decls = try Scope.Decls.create(self.a(), null);
311+
errdefer decls.destroy();
312+
313+
var it = tree.root_node.decls.iterator(0);
314+
while (it.next()) |decl_ptr| {
315+
const decl = decl_ptr.*;
316+
switch (decl.id) {
317+
ast.Node.Id.Comptime => @panic("TODO"),
318+
ast.Node.Id.VarDecl => @panic("TODO"),
319+
ast.Node.Id.FnProto => {
320+
const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl);
321+
322+
const name = if (fn_proto.name_token) |name_token| tree.tokenSlice(name_token) else {
323+
@panic("TODO add compile error");
324+
//try self.addCompileError(
325+
// &parsed_file,
326+
// fn_proto.fn_token,
327+
// fn_proto.fn_token + 1,
328+
// "missing function name",
329+
//);
330+
continue;
331+
};
332+
333+
const fn_decl = try self.a().create(Decl.Fn{
334+
.base = Decl{
335+
.id = Decl.Id.Fn,
336+
.name = name,
337+
.visib = parseVisibToken(tree, fn_proto.visib_token),
338+
.resolution = Decl.Resolution.Unresolved,
339+
},
340+
.value = Decl.Fn.Val{ .Unresolved = {} },
341+
.fn_proto = fn_proto,
342+
});
343+
errdefer self.a().destroy(fn_decl);
344+
345+
// TODO make this parallel
346+
try await try async self.addTopLevelDecl(tree, &fn_decl.base);
347+
},
348+
ast.Node.Id.TestDecl => @panic("TODO"),
349+
else => unreachable,
350+
}
351+
}
352+
}
353+
354+
async fn addTopLevelDecl(self: *Module, tree: *ast.Tree, decl: *Decl) !void {
355+
const is_export = decl.isExported(tree);
356+
357+
{
358+
const exported_symbol_names = await try async self.exported_symbol_names.acquire();
359+
defer exported_symbol_names.release();
360+
361+
if (try exported_symbol_names.value.put(decl.name, decl)) |other_decl| {
362+
@panic("TODO report compile error");
363+
}
364+
}
307365
}
308366

309367
pub fn link(self: *Module, out_file: ?[]const u8) !void {
@@ -350,3 +408,172 @@ fn printError(comptime format: []const u8, args: ...) !void {
350408
const out_stream = &stderr_file_out_stream.stream;
351409
try out_stream.print(format, args);
352410
}
411+
412+
fn parseVisibToken(tree: *ast.Tree, optional_token_index: ?ast.TokenIndex) Visib {
413+
if (optional_token_index) |token_index| {
414+
const token = tree.tokens.at(token_index);
415+
assert(token.id == Token.Id.Keyword_pub);
416+
return Visib.Pub;
417+
} else {
418+
return Visib.Private;
419+
}
420+
}
421+
422+
pub const Scope = struct {
423+
id: Id,
424+
parent: ?*Scope,
425+
426+
pub const Id = enum {
427+
Decls,
428+
Block,
429+
};
430+
431+
pub const Decls = struct {
432+
base: Scope,
433+
table: Decl.Table,
434+
435+
pub fn create(a: *Allocator, parent: ?*Scope) !*Decls {
436+
const self = try a.create(Decls{
437+
.base = Scope{
438+
.id = Id.Decls,
439+
.parent = parent,
440+
},
441+
.table = undefined,
442+
});
443+
errdefer a.destroy(self);
444+
445+
self.table = Decl.Table.init(a);
446+
errdefer self.table.deinit();
447+
448+
return self;
449+
}
450+
451+
pub fn destroy(self: *Decls) void {
452+
self.table.deinit();
453+
self.table.allocator.destroy(self);
454+
self.* = undefined;
455+
}
456+
};
457+
458+
pub const Block = struct {
459+
base: Scope,
460+
};
461+
};
462+
463+
pub const Visib = enum {
464+
Private,
465+
Pub,
466+
};
467+
468+
pub const Decl = struct {
469+
id: Id,
470+
name: []const u8,
471+
visib: Visib,
472+
resolution: Resolution,
473+
474+
pub const Table = std.HashMap([]const u8, *Decl, mem.hash_slice_u8, mem.eql_slice_u8);
475+
476+
pub fn isExported(base: *const Decl, tree: *ast.Tree) bool {
477+
switch (base.id) {
478+
Id.Fn => {
479+
const fn_decl = @fieldParentPtr(Fn, "base", base);
480+
return fn_decl.isExported(tree);
481+
},
482+
else => return false,
483+
}
484+
}
485+
486+
pub const Resolution = enum {
487+
Unresolved,
488+
InProgress,
489+
Invalid,
490+
Ok,
491+
};
492+
493+
pub const Id = enum {
494+
Var,
495+
Fn,
496+
CompTime,
497+
};
498+
499+
pub const Var = struct {
500+
base: Decl,
501+
};
502+
503+
pub const Fn = struct {
504+
base: Decl,
505+
value: Val,
506+
fn_proto: *const ast.Node.FnProto,
507+
508+
// TODO https://github.com/ziglang/zig/issues/683 and then make this anonymous
509+
pub const Val = union {
510+
Unresolved: void,
511+
Ok: *Value.Fn,
512+
};
513+
514+
pub fn externLibName(self: Fn, tree: *ast.Tree) ?[]const u8 {
515+
return if (self.fn_proto.extern_export_inline_token) |tok_index| x: {
516+
const token = tree.tokens.at(tok_index);
517+
break :x switch (token.id) {
518+
Token.Id.Extern => tree.tokenSlicePtr(token),
519+
else => null,
520+
};
521+
} else null;
522+
}
523+
524+
pub fn isExported(self: Fn, tree: *ast.Tree) bool {
525+
if (self.fn_proto.extern_export_inline_token) |tok_index| {
526+
const token = tree.tokens.at(tok_index);
527+
return token.id == Token.Id.Keyword_export;
528+
} else {
529+
return false;
530+
}
531+
}
532+
};
533+
534+
pub const CompTime = struct {
535+
base: Decl,
536+
};
537+
};
538+
539+
pub const Value = struct {
540+
pub const Fn = struct {};
541+
};
542+
543+
pub const Type = struct {
544+
id: Id,
545+
546+
pub const Id = enum {
547+
Type,
548+
Void,
549+
Bool,
550+
NoReturn,
551+
Int,
552+
Float,
553+
Pointer,
554+
Array,
555+
Struct,
556+
ComptimeFloat,
557+
ComptimeInt,
558+
Undefined,
559+
Null,
560+
Optional,
561+
ErrorUnion,
562+
ErrorSet,
563+
Enum,
564+
Union,
565+
Fn,
566+
Opaque,
567+
Promise,
568+
};
569+
570+
pub const Struct = struct {
571+
base: Type,
572+
decls: *Scope.Decls,
573+
};
574+
};
575+
576+
pub const ParsedFile = struct {
577+
tree: ast.Tree,
578+
realpath: []const u8,
579+
};

src/ir.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -13278,7 +13278,7 @@ static TypeTableEntry *ir_analyze_instruction_call(IrAnalyze *ira, IrInstruction
1327813278
FnTableEntry *fn_table_entry = fn_ref->value.data.x_bound_fn.fn;
1327913279
IrInstruction *first_arg_ptr = fn_ref->value.data.x_bound_fn.first_arg;
1328013280
return ir_analyze_fn_call(ira, call_instruction, fn_table_entry, fn_table_entry->type_entry,
13281-
nullptr, first_arg_ptr, is_comptime, call_instruction->fn_inline);
13281+
fn_ref, first_arg_ptr, is_comptime, call_instruction->fn_inline);
1328213282
} else {
1328313283
ir_add_error_node(ira, fn_ref->source_node,
1328413284
buf_sprintf("type '%s' not a function", buf_ptr(&fn_ref->value.type->name)));

std/atomic/queue_mpsc.zig

+17
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ pub fn QueueMpsc(comptime T: type) type {
1515

1616
pub const Node = std.atomic.Stack(T).Node;
1717

18+
/// Not thread-safe. The call to init() must complete before any other functions are called.
19+
/// No deinitialization required.
1820
pub fn init() Self {
1921
return Self{
2022
.inboxes = []std.atomic.Stack(T){
@@ -26,12 +28,15 @@ pub fn QueueMpsc(comptime T: type) type {
2628
};
2729
}
2830

31+
/// Fully thread-safe. put() may be called from any thread at any time.
2932
pub fn put(self: *Self, node: *Node) void {
3033
const inbox_index = @atomicLoad(usize, &self.inbox_index, AtomicOrder.SeqCst);
3134
const inbox = &self.inboxes[inbox_index];
3235
inbox.push(node);
3336
}
3437

38+
/// Must be called by only 1 consumer at a time. Every call to get() and isEmpty() must complete before
39+
/// the next call to get().
3540
pub fn get(self: *Self) ?*Node {
3641
if (self.outbox.pop()) |node| {
3742
return node;
@@ -43,6 +48,18 @@ pub fn QueueMpsc(comptime T: type) type {
4348
}
4449
return self.outbox.pop();
4550
}
51+
52+
/// Must be called by only 1 consumer at a time. Every call to get() and isEmpty() must complete before
53+
/// the next call to isEmpty().
54+
pub fn isEmpty(self: *Self) bool {
55+
if (!self.outbox.isEmpty()) return false;
56+
const prev_inbox_index = @atomicRmw(usize, &self.inbox_index, AtomicRmwOp.Xor, 0x1, AtomicOrder.SeqCst);
57+
const prev_inbox = &self.inboxes[prev_inbox_index];
58+
while (prev_inbox.pop()) |node| {
59+
self.outbox.push(node);
60+
}
61+
return self.outbox.isEmpty();
62+
}
4663
};
4764
}
4865

0 commit comments

Comments
 (0)