all: tidy

This commit is contained in:
Loïc Grégoire
2026-10-07 18:03:06 -04:00
parent 6cd9117a05
commit 619c373d47
78 changed files with 1676 additions and 1118 deletions
+2 -2
View File
@@ -13,8 +13,8 @@
.hash = "N-V-__8AAAAAjAwsopZIlbp4U6NN1Eh573uVdLTwl1mkFUug",
},
.zonfig = .{
.url = "git+https://github.com/rdmsr/zonfig?ref=HEAD#7cb4e030077acbef88458aef2d3fec4331b3b6da",
.hash = "zonfig-0.1.0-V0D8-aRMFAAQ3Yb-0u8JNpdceK6lUOWUP7EY5BRXz12C",
.url = "git+https://github.com/rdmsr/zonfig?ref=HEAD#d02cbb9262c5e617676a252116c9cee3560b6e59",
.hash = "zonfig-0.1.0-V0D8-YtLFAAKh8PQgGaoCiGG1WUxWdqcY-iTysVPllQV",
},
},
+17 -1
View File
@@ -28,8 +28,24 @@ pub const Config = struct {
};
pub fn parseConfig(b: *std.Build) !Config {
const gpa = b.allocator;
var arena_allocator: std.heap.ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diag: std.zon.parse.Diagnostics = undefined;
const file = try std.Io.Dir.cwd().readFileAllocOptions(b.graph.io, ".config.zig.zon", b.allocator, std.Io.Limit.unlimited, .@"1", 0);
return std.zon.parse.fromSlice(Config, b.allocator, file, null, .{ .ignore_unknown_fields = true });
return std.zon.parse.fromSlice(
Config,
.{
.gpa = gpa,
.source = file,
.arena = arena,
.diagnostics = &diag,
.ignore_unknown_fields = true,
},
);
}
pub fn getPlatform(config: Config) !Platform {
+1 -1
View File
@@ -11,4 +11,4 @@ timeout: 3
module_path: boot():/boot/kernel
module_string: kernel
cmdline: test=fireworks
+1 -1
View File
@@ -58,7 +58,7 @@ pub fn addRun(b: *std.Build, kernel: *std.Build.Step.Compile, loader: *std.Build
switch (plat.arch) {
.x86_64 => {
qemu.addArgs(&.{ "-serial", "stdio", "-m", "1G", "-smp", "4" });
qemu.addArgs(&.{ "-serial", "stdio", "-m", "1G", "-smp", "1" });
if (!debug and builtin.target.os.tag == .linux and builtin.cpu.arch == .x86_64) qemu.addArgs(&.{ "-enable-kvm", "-cpu", "host,+invtsc" });
},
-4
View File
@@ -38,10 +38,6 @@ const Msr = enum(u32) {
_,
};
fn read_msr(msr: Msr) u64 {
return amd64.rdmsr(@intFromEnum(msr));
}
fn write_msr(msr: Msr, value: u64) void {
amd64.wrmsr(@intFromEnum(msr), value);
}
+44 -73
View File
@@ -1,5 +1,6 @@
//! Wrappers over amd64 CPU definitions and CPUID.
const std = @import("std");
const assert = std.debug.assert;
pub const hypervisor = @import("hypervisor.zig");
@@ -286,14 +287,14 @@ pub const Cr4 = packed struct(u64) {
};
pub inline fn read_cr(comptime n: u8) u64 {
comptime std.debug.assert(n != 1);
comptime assert(n != 1);
return asm volatile (std.fmt.comptimePrint("mov %%cr{}, %[ret]", .{n})
: [ret] "=r" (-> u64),
);
}
pub inline fn write_cr(comptime n: u8, value: u64) void {
comptime std.debug.assert(n != 1);
comptime assert(n != 1);
asm volatile (std.fmt.comptimePrint("mov %[value], %%cr{}", .{n})
:
: [value] "r" (value),
@@ -519,23 +520,23 @@ const known_vendors = [_]struct {
};
pub const CpuFeatures = struct {
x2apic: bool,
five_level_paging: bool,
gib_pages: bool,
tsc_deadline: bool,
fxsave: bool,
xsave: bool,
invariant_tsc: bool,
nx: bool,
pcid: bool,
smap: bool,
smep: bool,
pge: bool,
syscall: bool,
umip: bool,
vendor: CpuVendor,
family: u8,
brand_string: [48]u8,
x2apic: bool = false,
five_level_paging: bool = false,
gib_pages: bool = false,
tsc_deadline: bool = false,
fxsave: bool = false,
xsave: bool = false,
invariant_tsc: bool = false,
nx: bool = false,
pcid: bool = false,
smap: bool = false,
smep: bool = false,
pge: bool = false,
syscall: bool = false,
umip: bool = false,
vendor: CpuVendor = undefined,
family: u8 = 0,
brand_string: [48]u8 = undefined,
};
pub const HypervisorVendor = enum(u32) {
@@ -563,71 +564,59 @@ fn detect_vendor(string: [12]u8) CpuVendor {
pub fn detect_cpu_features() void {
const vendor_info = CpuidRequest.execute(.VendorInfo);
const max_ext = CpuidRequest.execute(.HighestExtendedFunction).eax;
const max_basic = vendor_info.eax;
var x2apic = false;
var five_level_paging = false;
var gib_pages = false;
var tsc_deadline = false;
var xsave = false;
var fxsave = false;
var invtsc = false;
var nx = false;
var pcid = false;
var smap = false;
var smep = false;
var pge = false;
var hypervisor_flag = false;
var syscall = false;
var family: u8 = 0;
var umip = false;
var vendor_string: [12]u8 = undefined;
var brand_string: [48]u8 = undefined;
cpu_features = .{};
std.mem.writeInt(u32, vendor_string[0..4], vendor_info.ebx, .little);
std.mem.writeInt(u32, vendor_string[4..8], vendor_info.edx, .little);
std.mem.writeInt(u32, vendor_string[8..12], vendor_info.ecx, .little);
const vendor = detect_vendor(vendor_string);
cpu_features.vendor = detect_vendor(vendor_string);
if (max_basic >= 0x1) {
const r = CpuidRequest.execute(.FeatureInfo);
const ecx: FeatureInfoEcx = @bitCast(r.ecx);
const edx: FeatureInfoEdx = @bitCast(r.edx);
x2apic = ecx.x2apic;
tsc_deadline = ecx.tsc_deadline;
xsave = ecx.xsave;
pcid = ecx.pcid;
fxsave = edx.fxsave;
pge = edx.pge;
cpu_features.x2apic = ecx.x2apic;
cpu_features.tsc_deadline = ecx.tsc_deadline;
cpu_features.xsave = ecx.xsave;
cpu_features.pcid = ecx.pcid;
cpu_features.fxsave = edx.fxsave;
cpu_features.pge = edx.pge;
hypervisor_flag = ecx.hypervisor;
const sig = r.eax;
family = @truncate((sig >> 8) & 0xf);
var family: u8 = @truncate((sig >> 8) & 0xf);
if (family == 0xf) {
family +%= @truncate((sig >> 20) & 0xff);
}
cpu_features.family = family;
}
if (max_basic >= 0x7) {
const r = CpuidRequest.execute(.{ .ExtendedFeatures = .First });
const ecx: ExtendedFeaturesEcx = @bitCast(r.ecx);
const ebx: ExtendedFeaturesEbx = @bitCast(r.ebx);
smap = ebx.smap;
smep = ebx.smep;
five_level_paging = ecx.la57;
umip = ecx.umip;
cpu_features.smap = ebx.smap;
cpu_features.smep = ebx.smep;
cpu_features.five_level_paging = ecx.la57;
cpu_features.umip = ecx.umip;
}
if (max_ext >= 0x80000001) {
const r = CpuidRequest.execute(.ExtendedInfo);
const edx: ExtendedProcessorInfoEdx = @bitCast(r.edx);
gib_pages = edx.pdpe1gb;
nx = edx.nx;
syscall = edx.syscall_sysret;
cpu_features.gib_pages = edx.pdpe1gb;
cpu_features.nx = edx.nx;
cpu_features.syscall = edx.syscall_sysret;
}
if (max_ext >= 0x80000004) {
@@ -638,6 +627,8 @@ pub fn detect_cpu_features() void {
};
var off: usize = 0;
const brand_string = &cpu_features.brand_string;
for (parts) |p| {
for ([_]u32{ p.eax, p.ebx, p.ecx, p.edx }) |reg| {
const dst: *[4]u8 = @ptrCast(brand_string[off..].ptr);
@@ -650,32 +641,12 @@ pub fn detect_cpu_features() void {
if (max_ext >= 0x80000007) {
const r = CpuidRequest.execute(.PowerManagementInfo);
const edx: PowerManagementInfoEdx = @bitCast(r.edx);
invtsc = edx.invtsc;
cpu_features.invariant_tsc = edx.invtsc;
}
if (hypervisor_flag) {
hypervisor.detect();
}
cpu_features = .{
.x2apic = x2apic,
.five_level_paging = five_level_paging,
.gib_pages = gib_pages,
.tsc_deadline = tsc_deadline,
.fxsave = fxsave,
.xsave = xsave,
.invariant_tsc = invtsc,
.nx = nx,
.pcid = pcid,
.smap = smap,
.smep = smep,
.vendor = vendor,
.brand_string = brand_string,
.pge = pge,
.family = family,
.umip = umip,
.syscall = syscall,
};
}
pub const TssDescriptor = extern struct {
+2
View File
@@ -1,3 +1,5 @@
//! Wrappers over riscv64 CPU definitions.
pub const name: []const u8 = "riscv64";
pub const BootInfo = struct {};
+17 -11
View File
@@ -39,7 +39,7 @@ const Cursor = struct {
const font_file = @embedFile("sun12x22-ascii.psf");
const text_color: u32 = 0xFFFFFFFF;
const bg_color: u32 = 0xFF000000;
const bg_color: u32 = 0xFF574c8f;
const crash_bg_color: u32 = 0xFF0000FF;
const window_padding: usize = 10;
@@ -93,8 +93,8 @@ fn load_font() void {
fn plot_character(x: usize, y: usize, c: u8) void {
const bytes_per_row = font.glyph_size / font.height;
const glyph_offset = font.header_size + (@as(usize, c) * font.glyph_size);
const glyph_data = font_file[glyph_offset..(glyph_offset + font.glyph_size)];
const offset = font.header_size + (@as(usize, c) * font.glyph_size);
const glyph_data = font_file[offset .. offset + font.glyph_size];
const start_x = window_padding + x;
const start_y = window_padding + y;
@@ -102,14 +102,19 @@ fn plot_character(x: usize, y: usize, c: u8) void {
var row_data: u32 = 0;
for (0..bytes_per_row) |b| {
row_data = (row_data << 8) | @as(u32, glyph_data[row * bytes_per_row + b]);
row_data = (row_data << 8) |
@as(u32, glyph_data[row * bytes_per_row + b]);
}
const shift_base = bytes_per_row * 8;
for (0..font.width) |col| {
if ((row_data & (@as(u32, 1) << @intCast(shift_base - 1 - col))) != 0) {
framebuffer[(start_y + row) * framebuffer_width + (start_x + col)] = text_color;
const mask = @as(u32, 1) << @intCast(shift_base - 1 - col);
if (row_data & mask != 0) {
framebuffer[
(start_y + row) * framebuffer_width + (start_x + col)
] = text_color;
}
}
}
@@ -134,7 +139,9 @@ fn scroll() void {
const dst_y = window_padding + (console_height - 1) * font.height + row;
for (0..content_w) |col| {
framebuffer[dst_y * framebuffer_width + window_padding + col] = bg_color;
framebuffer[
dst_y * framebuffer_width + window_padding + col
] = bg_color;
}
}
}
@@ -187,11 +194,10 @@ pub fn init(boot_info: *r.BootInfo) void {
content_h = framebuffer_height - (window_padding * 2);
console_width = @intCast(content_w / font.width);
console_height = std.math.divCeil(
u16,
@intCast(content_h),
console_height = @divFloor(
@as(u16, @intCast(content_h)),
font.height,
) catch unreachable;
);
for (0..framebuffer_width * framebuffer_height) |i| {
framebuffer[i] = bg_color;
+7 -1
View File
@@ -1,3 +1,5 @@
//! Initialization of the executive.
const std = @import("std");
const r = @import("root");
const pl = r.pl;
@@ -24,7 +26,11 @@ pub fn init() void {
if (rtl.cmdline.get_string(cline, "test")) |tst| {
// Run a test if specified.
if (r.tests.tests.get(tst)) |func| {
const t = ps.thread.create_kernel(.Default, .{ .func = func, .arg = r.boot_info }, true) catch
const t = ps.thread.create_kernel(
.Default,
.{ .func = func, .arg = r.boot_info },
true,
) catch
@panic("oom");
ke.sched.enqueue(&t.kern);
} else {
+1
View File
@@ -1,3 +1,4 @@
//! Private executive definitions.
pub const init_mod = @import("init.zig");
pub const work = @import("work.zig");
pub const init = init_mod.init;
+2
View File
@@ -1,3 +1,5 @@
//! Public executive definitions.
pub const private = @import("private.zig");
const p = private;
+16 -10
View File
@@ -44,18 +44,16 @@ const Context = struct {
pub const InternedString = packed struct(usize) {
bits: usize,
const Self = @This();
fn from_ptr(e: *Entry) Self {
fn from_ptr(e: *Entry) InternedString {
return .{ .bits = @intFromPtr(e) };
}
fn as_entry(self: *const Self) ?*Entry {
fn as_entry(self: *const InternedString) ?*Entry {
if (self.bits & 1 != 0) return null;
return @ptrFromInt(self.bits);
}
fn from_inline(s: []const u8) Self {
fn from_inline(s: []const u8) InternedString {
var buf: [@sizeOf(usize)]u8 = @splat(0);
// byte 0: tag (bit 0) | len (bits 1-7)
@@ -71,7 +69,7 @@ pub const InternedString = packed struct(usize) {
return @sizeOf(usize) > 4 and len <= 7;
}
pub fn slice(self: *const Self) []const u8 {
pub fn slice(self: *const InternedString) []const u8 {
if (self.bits & 1 != 0) {
const buf: *const [@sizeOf(usize)]u8 = @ptrCast(&self.bits);
const len: usize = buf[0] >> 1;
@@ -97,7 +95,7 @@ pub fn init() void {
table.init(.{
.smr = domain,
.policy = .Balanced,
}) catch unreachable;
}) catch @panic("Failed to initialize string interning");
entry_zone.init("str entry", .{
.smr = domain,
@@ -114,6 +112,7 @@ pub fn retain(string: []const u8) InternedString {
// Fast path where the string already exists.
const ipl = ke.smr.enter(domain);
defer ke.smr.exit(domain, ipl);
if (table.get(string)) |e| {
_ = e.ref.fetchAdd(1, .monotonic);
return .from_ptr(e);
@@ -121,10 +120,17 @@ pub fn retain(string: []const u8) InternedString {
}
// The string (probably) doesn't exist, create a new one.
var new_entry: *Entry = entry_zone.create() catch unreachable;
var new_entry: *Entry = entry_zone.create() catch {
// This can't error since the allocation will wait for free memory.
unreachable;
};
new_entry.string = mm.zone.gpa.dupe(u8, string) catch unreachable;
new_entry.ref = .init(1);
new_entry.string = mm.zone.gpa.dupe(u8, string) catch {
// This can't error since the allocation will wait for free memory.
unreachable;
};
new_entry.ref = std.atomic.Value(u32).init(1);
const ipl = ke.smr.enter(domain);
const entry = table.get_or_insert(string, &new_entry.link);
+55 -42
View File
@@ -1,13 +1,17 @@
//! Generic resource allocator, based on the vmem allocator described in:
//! Jeff Bonwick and Jonathan Adams'
//! "Magazines and Vmem: Extending the Slab Allocator to Many CPUs and Arbitrary Resources"
const std = @import("std");
//! "Magazines and Vmem: Extending the Slab Allocator to Many CPUs and
//! Arbitrary Resources"
const rtl = @import("rtl");
const r = @import("root");
const mm = r.mm;
const mmp = mm.private;
const ke = r.ke;
const std = @import("std");
const assert = std.debug.assert;
pub const Policy = enum {
/// Use the smallest free segment that can satisfy the request.
/// This tends to minimize fragmentation.
@@ -37,9 +41,9 @@ const Segment = struct {
};
const Linkage = union {
/// Linkage into a freelist, valid when type == .Free
/// Linkage into a freelist, valid when .Free
free_link: rtl.List.Entry,
/// Linkage into the allocated segments tree, valid when type == .Allocated
/// Linkage into the allocated segments tree, valid when .Allocated
tree_link: rtl.bst.Node,
};
/// The type of the segment.
@@ -80,6 +84,8 @@ const max_qcaches = 16;
var seg_zone: mmp.zone.TypedZone(Segment) = undefined;
const SegmentTree = rtl.RBTree(Segment.cmp);
pub const Arena = struct {
/// Name of the arena, for debugging purposes.
name: []const u8,
@@ -90,7 +96,7 @@ pub const Arena = struct {
/// Power of two freelists for free segments, indexed by log2(size).
freelists: [freelist_count]rtl.List,
/// Tree for allocated segments, indexed by base address.
allocated_segments: rtl.RBTree(Segment.cmp),
allocated_segments: SegmentTree,
/// Last segment allocated from, for NextFit policy.
rotor: ?*Segment,
/// Maximum size (**in bytes**) to do quantum caching on.
@@ -105,14 +111,12 @@ pub const Arena = struct {
lock: ke.Mutex,
const Self = @This();
const Span = struct { addr: usize, size: usize };
const Source = struct {
arena: *Self,
import: *const fn (*Self, usize, opts: AllocOptions) mm.Error!usize,
release: *const fn (*Self, addr: usize, size: usize) void,
arena: *Arena,
import: *const fn (*Arena, usize, opts: AllocOptions) mm.Error!usize,
release: *const fn (*Arena, addr: usize, size: usize) void,
};
const InitOptions = struct {
@@ -124,7 +128,7 @@ pub const Arena = struct {
/// Initialize the area for use.
pub fn init(
self: *Self,
self: *Arena,
name: []const u8,
opts: InitOptions,
) !void {
@@ -142,7 +146,11 @@ pub const Arena = struct {
self.qshift = std.math.log2_int(usize, self.quantum);
if (opts.span == null) {
rtl.assert(opts.source != null, "Arena init: null span without source", .{});
rtl.assert(
opts.source != null,
"Arena init: null span without source",
.{},
);
}
self.list.init();
@@ -170,7 +178,7 @@ pub const Arena = struct {
self.lock = ke.Mutex.init("arena");
self.allocated_segments = .init();
self.allocated_segments = SegmentTree.init();
self.rotor = null;
// Add initial span.
@@ -179,7 +187,7 @@ pub const Arena = struct {
}
}
pub fn deinit(self: *Self) void {
pub fn deinit(self: *Arena) void {
// Free all segments in the segment list
var entry = self.list.first();
while (entry != &self.list.head) {
@@ -191,12 +199,12 @@ pub const Arena = struct {
}
/// Add a span of memory to the arena.
pub fn add(self: *Self, span: Span) !void {
pub fn add(self: *Arena, span: Span) !void {
return self.add_opts(span, .Span);
}
/// Add a span of memory to the arena.
fn add_opts(self: *Self, span: Span, kind: Segment.Type) mm.Error!void {
fn add_opts(self: *Arena, span: Span, kind: Segment.Type) mm.Error!void {
const new_free_seg = try self.alloc_segment();
new_free_seg.* = Segment{
@@ -226,12 +234,15 @@ pub const Arena = struct {
/// Allocate a segment of memory from the arena.
/// Options:
/// - policy: allocation policy to use. Default is .InstantFit.
/// - alignment: if specified, the returned segment will be aligned to this boundary.
/// - min: if specified, the returned segment will be at least on this address.
/// - max: if specified, the returned segment will be at most on this address.
/// - alignment: if specified, the returned segment will be aligned to
/// this boundary.
/// - min: if specified, the returned segment will be at least on this
/// address.
/// - max: if specified, the returned segment will be at most on this
/// address.
/// Returns the base address of the allocated segment.
pub fn alloc(
self: *Self,
self: *Arena,
size: usize,
options: AllocOptions,
) mm.Error!usize {
@@ -252,7 +263,7 @@ pub const Arena = struct {
}
fn alloc_impl(
self: *Self,
self: *Arena,
size: usize,
options: AllocOptions,
) mm.Error!usize {
@@ -263,20 +274,20 @@ pub const Arena = struct {
.NextFit => self.next_fit(size, options),
};
if (ret != null) break :blk ret.?;
if (ret != null) break :blk ret orelse unreachable;
if (ret == null) {
try self.import(size, options);
}
// This is guaranteed to terminate eventually, either when the import
// brings in new free memory, or when it runs out.
// This is guaranteed to terminate eventually, either when
// the import brings in new free memory, or when it runs out.
};
const start, var seg = result;
std.debug.assert(seg.type == .Free);
std.debug.assert(seg.size >= size);
assert(seg.type == .Free);
assert(seg.size >= size);
// Remove the segment from the freelist.
self.remove_segment_from_freelist(seg);
@@ -303,11 +314,13 @@ pub const Arena = struct {
self.add_segment_to_freelist(left_seg);
}
// right split: seg is larger than needed, so split into allocated + free remainder.
// right split: seg is larger than needed, so split into alloc + free
// remainder.
// e.g. seg=[0x100, 0x10000], size=0x1000:
// before: [0x100, 0x10000] being processed
// after: [0x100, 0x1100] allocated, [0x1100, 0x10000] free
// the quantum check avoids creating a remainder too small to ever be useful.
// the quantum check avoids creating a remainder too small to ever be
// useful.
if (seg.size != size and (seg.size - size) > self.quantum - 1) {
const new_seg = try self.alloc_segment();
new_seg.* = .{
@@ -345,7 +358,7 @@ pub const Arena = struct {
}
/// Try to import memory from the parent arena.
fn import(self: *Self, size: usize, options: AllocOptions) mm.Error!void {
fn import(self: *Arena, size: usize, options: AllocOptions) mm.Error!void {
const src = self.source orelse return error.OutOfMemory;
// Ensure no funny stuff happens wrt the lock.
@@ -362,7 +375,7 @@ pub const Arena = struct {
);
}
pub fn free(self: *Self, addr: usize, size: usize) void {
pub fn free(self: *Arena, addr: usize, size: usize) void {
if (size <= self.qcache_max) {
const i = size >> self.qshift;
self.qcaches[i - 1].free(@ptrFromInt(addr));
@@ -375,7 +388,7 @@ pub const Arena = struct {
return self.free_impl(addr, size);
}
fn free_impl(self: *Self, addr: usize, size: usize) void {
fn free_impl(self: *Arena, addr: usize, size: usize) void {
// Find the allocated segment containing addr.
var search_node = Segment{
@@ -449,7 +462,7 @@ pub const Arena = struct {
}
/// Try to release a segment back to its source.
fn release(self: *Self, seg: *Segment) bool {
fn release(self: *Arena, seg: *Segment) bool {
const prev_entry = seg.link.prev;
if (prev_entry == &self.list.head) {
@@ -479,7 +492,7 @@ pub const Arena = struct {
// Implementation for the instant fit policy.
fn instant_fit(
self: *Self,
self: *Arena,
size: usize,
options: AllocOptions,
) ?struct { usize, *Segment } {
@@ -506,7 +519,7 @@ pub const Arena = struct {
// something in the lower freelist, so check there.
// This is O(n) but should happen very rarely.
if (!is_pow2) {
std.debug.assert(orig_idx != 0);
assert(orig_idx != 0);
var it = self.freelists[orig_idx].iterator();
while (it.next()) : (it.advance()) {
@@ -522,7 +535,7 @@ pub const Arena = struct {
// Implementation for the best fit policy.
// Find the smallest segment that can satisfy the request.
fn best_fit(
self: *Self,
self: *Arena,
size: usize,
options: AllocOptions,
) ?struct { usize, *Segment } {
@@ -552,7 +565,7 @@ pub const Arena = struct {
// Continue searching from the last allocated segment, wrapping around at
// the end of the arena.
fn next_fit(
self: *Self,
self: *Arena,
size: usize,
options: AllocOptions,
) ?struct { usize, *Segment } {
@@ -588,7 +601,7 @@ pub const Arena = struct {
// Try to fit the given segment to the request with the given options.
// Returns the fitted address if successful.
fn try_to_fit(
self: *Self,
self: *Arena,
segment: *const Segment,
size: usize,
options: AllocOptions,
@@ -610,22 +623,22 @@ pub const Arena = struct {
return null;
}
fn alloc_segment(self: *Self) !*Segment {
fn alloc_segment(self: *Arena) !*Segment {
_ = self;
return seg_zone.create();
}
fn free_segment(self: *Self, segment: *Segment) void {
fn free_segment(self: *Arena, segment: *Segment) void {
seg_zone.destroy(segment);
_ = self;
}
fn add_segment_to_freelist(self: *Self, segment: *Segment) void {
fn add_segment_to_freelist(self: *Arena, segment: *Segment) void {
const freelist = self.freelist_for_size(segment.size);
freelist.insert_tail(&segment.linkage.free_link);
}
fn remove_segment_from_freelist(self: *Self, segment: *Segment) void {
fn remove_segment_from_freelist(self: *Arena, segment: *Segment) void {
_ = self;
segment.linkage.free_link.remove();
}
@@ -634,7 +647,7 @@ pub const Arena = struct {
return std.math.log2_int(usize, size);
}
inline fn freelist_for_size(self: *Self, size: usize) *rtl.List {
inline fn freelist_for_size(self: *Arena, size: usize) *rtl.List {
return &self.freelists[freelist_index(size)];
}
};
+30 -19
View File
@@ -6,8 +6,8 @@
//! - High
//! - Normal
//! - Low
//! High and Normal worker pools are per-cpu, and there is one global worker pool
//! for low-priority work.
//! High and Normal worker pools are per-cpu,
//! and there is one global worker pool for low-priority work.
//!
//! Each CPU worker pool comprises of at least 2 threads, and the low pool
//! has 4 threads by default. An additional thread will get spawned by the
@@ -69,7 +69,7 @@ pub const WorkItem = struct {
.func = func,
.arg = arg,
.link = undefined,
.enqueued = .init(false),
.enqueued = std.atomic.Value(bool).init(false),
.priority = priority,
};
}
@@ -89,7 +89,7 @@ pub const DelayedWorkItem = struct {
) void {
self.* = .{
.item = undefined,
.dpc = .init(work_dpc),
.dpc = ke.Dpc.init(work_dpc),
.timer = undefined,
};
@@ -117,14 +117,17 @@ const Pool = struct {
const how_many = min_threads_for_prio(prio);
self.total_threads = .init(how_many);
self.total_threads = std.atomic.Value(usize).init(how_many);
for (0..how_many) |_| {
const ctx: Context = .init(self, 0);
const ctx = Context.init(self, 0);
var td = try ps.thread.create_kernel(
WorkItem.Priority.to_sched_prio(self.prio),
.{ .func = work_loop, .arg = @ptrFromInt(ctx.value) },
.{
.func = work_loop,
.arg = @ptrFromInt(ctx.value),
},
true,
);
@@ -139,7 +142,7 @@ const Pool = struct {
}
}
/// Return whether or not a new worker thread should be spawned on this pool.
/// Return whether or not a new worker thread should be added to this pool.
fn should_grow(self: *Pool) bool {
// NOTE: lockless racy check whether or not the queue is empty
// and its active count.
@@ -166,20 +169,23 @@ const Pool = struct {
}
/// Spawn a dynamic worker thread on this pool.
fn grow(self: *Pool, cpu: ?u32) !void {
fn grow(self: *Pool, cpu: ?u32) void {
if (self.total_threads.load(.monotonic) >=
max_threads_for_prio(self.prio))
return;
_ = self.total_threads.fetchAdd(1, .monotonic);
const ctx: Context = .init(self, 1);
const ctx = Context.init(self, 1);
var td = try ps.thread.create_kernel(
var td = ps.thread.create_kernel(
WorkItem.Priority.to_sched_prio(self.prio),
.{ .func = work_loop, .arg = @ptrFromInt(ctx.value) },
true,
);
) catch {
// This will wait for free memory.
unreachable;
};
if (cpu != null) {
td.kern.last_cpu = cpu;
@@ -205,15 +211,15 @@ var low: Pool = undefined;
fn work_loop(p: ?*anyopaque) void {
const ctx: Context = .{ .value = @intFromPtr(p) };
const dynamic = ctx.get_tag() == 1;
const dynamic = ctx.tag() == 1;
while (true) {
const timeout: ?r.Nanoseconds = if (dynamic)
.from(r.Seconds.init(5))
else
null;
const item = ctx.get_ptr().queue.remove(timeout) catch {
_ = ctx.get_ptr().total_threads.fetchSub(1, .monotonic);
const item = ctx.ptr().queue.remove(timeout) catch {
_ = ctx.ptr().total_threads.fetchSub(1, .monotonic);
// We haven't had work in a while, exit.
ps.thread.exit();
@@ -244,7 +250,12 @@ pub fn enqueue(item: *WorkItem) void {
const ipl = ke.ipl.raise(.Dispatch);
defer ke.ipl.lower(ipl);
if (item.enqueued.cmpxchgStrong(false, true, .acquire, .monotonic) != null) {
if (item.enqueued.cmpxchgStrong(
false,
true,
.acquire,
.monotonic,
) != null) {
return;
}
@@ -286,16 +297,16 @@ fn pool_manager(_: ?*anyopaque) void {
const c = percpu.remote(cpu);
if (c.high.should_grow()) {
_ = c.high.grow(cpu) catch {};
_ = c.high.grow(cpu);
}
if (c.normal.should_grow()) {
_ = c.normal.grow(cpu) catch {};
_ = c.normal.grow(cpu);
}
}
if (low.should_grow()) {
_ = low.grow(null) catch {};
_ = low.grow(null);
}
}
}
+6 -1
View File
@@ -1,3 +1,5 @@
//! amd64-specific kernel code.
const r = @import("root");
const ke = r.ke;
const kep = ke.private;
@@ -139,7 +141,10 @@ pub fn switch_cont_to_normal(
);
}
pub fn call_continuation(ctx: *Context, continuation: ke.Continuation) noreturn {
pub fn call_continuation(
ctx: *Context,
continuation: ke.Continuation,
) noreturn {
const td: *ke.Thread = @alignCast(@fieldParentPtr("context", ctx));
amd64_call_continuation(
+3 -1
View File
@@ -1,3 +1,5 @@
//! amd64-specific kernel initialization code.
const amd64 = @import("arch");
const int = @import("int.zig");
const r = @import("root");
@@ -26,7 +28,7 @@ var gdt = extern struct {
.tss = undefined,
};
extern fn gdt_load(gdtr: *const amd64.Gdtr) callconv(.{ .x86_64_sysv = .{} }) void;
extern fn gdt_load(gdtr: *const amd64.Gdtr) callconv(.c) void;
fn early_cpu_init() linksection(r.init) void {
const gdtr: amd64.Gdtr = .{
+56 -22
View File
@@ -1,3 +1,5 @@
//! amd64 interrupt handling.
const amd64 = @import("arch");
const std = @import("std");
const r = @import("root");
@@ -45,15 +47,36 @@ const exception_msg = [32][]const u8{
"Security Exception",
};
export fn isr_handler_main(frame: *const amd64.IrqFrame) callconv(.{ .x86_64_sysv = .{} }) void {
fn ignore(_: std.Io.Writer.Error!void) void {}
export fn isr_handler_main(frame: *const amd64.IrqFrame) callconv(.c) void {
const ipl = ke.ipl.set_hardware(.Device);
if (frame.intno < 32) {
std.log.err("Unhandled exception: 0x{x} ({s}), err=0x{x}, pc=0x{x}", .{ frame.intno, exception_msg[frame.intno], frame.errcode, frame.rip });
std.log.err("RAX=0x{x:0>16} RBX=0x{x:0>16} RCX=0x{x:0>16} RDX=0x{x:0>16}", .{ frame.rax, frame.rbx, frame.rcx, frame.rdx });
std.log.err("RSI=0x{x:0>16} RDI=0x{x:0>16} RBP=0x{x:0>16} RSP=0x{x:0>16}", .{ frame.rsi, frame.rdi, frame.rbp, frame.rsp });
std.log.err("R8= 0x{x:0>16} R9=0x{x:0>16} R10=0x{x:0>16} R11=0x{x:0>16}", .{ frame.r8, frame.r9, frame.r10, frame.r11 });
std.log.err("R12=0x{x:0>16} R13=0x{x:0>16} R14=0x{x:0>16} R15=0x{x:0>16}", .{ frame.r12, frame.r13, frame.r14, frame.r15 });
std.log.err("Unhandled exception: 0x{x} ({s}), err=0x{x}, pc=0x{x}", .{
frame.intno,
exception_msg[frame.intno],
frame.errcode,
frame.rip,
});
std.log.err(
"RAX=0x{x:0>16} RBX=0x{x:0>16} RCX=0x{x:0>16} RDX=0x{x:0>16}",
.{ frame.rax, frame.rbx, frame.rcx, frame.rdx },
);
std.log.err(
"RSI=0x{x:0>16} RDI=0x{x:0>16} RBP=0x{x:0>16} RSP=0x{x:0>16}",
.{ frame.rsi, frame.rdi, frame.rbp, frame.rsp },
);
std.log.err(
"R8= 0x{x:0>16} R9=0x{x:0>16} R10=0x{x:0>16} R11=0x{x:0>16}",
.{ frame.r8, frame.r9, frame.r10, frame.r11 },
);
std.log.err(
"R12=0x{x:0>16} R13=0x{x:0>16} R14=0x{x:0>16} R15=0x{x:0>16}",
.{ frame.r12, frame.r13, frame.r14, frame.r15 },
);
const cr2 = amd64.read_cr(2);
const cr3 = amd64.read_cr(3);
const rflags = amd64.rflags();
@@ -61,21 +84,27 @@ export fn isr_handler_main(frame: *const amd64.IrqFrame) callconv(.{ .x86_64_sys
var buf: [128]u8 = undefined;
var writer = std.Io.Writer.fixed(&buf);
writer.print("0x{x:0>8} [", .{@as(u64, @bitCast(rflags))}) catch {};
if (rflags.carry) writer.writeAll(" CF") catch {};
if (rflags.parity) writer.writeAll(" PF") catch {};
if (rflags.auxiliary) writer.writeAll(" AF") catch {};
if (rflags.zero) writer.writeAll(" ZF") catch {};
if (rflags.sign) writer.writeAll(" SF") catch {};
if (rflags.trap) writer.writeAll(" TF") catch {};
if (rflags.interrupt_enable) writer.writeAll(" IF") catch {};
if (rflags.direction) writer.writeAll(" DF") catch {};
if (rflags.overflow) writer.writeAll(" OF") catch {};
if (rflags.resume_) writer.writeAll(" RF") catch {};
if (rflags.virtual_8086_mode) writer.writeAll(" VM") catch {};
writer.print(" IOPL={d}]", .{rflags.iopl}) catch {};
writer.print("0x{x:0>8} [", .{@as(u64, @bitCast(rflags))}) catch {
// Shouldn't fail.
};
std.log.err("CR2=0x{x:0>16} CR3=0x{x:0>16} RFLAGS={s}", .{ cr2, cr3, buf[0..writer.end] });
if (rflags.carry) ignore(writer.writeAll(" CF"));
if (rflags.parity) ignore(writer.writeAll(" PF"));
if (rflags.auxiliary) ignore(writer.writeAll(" AF"));
if (rflags.zero) ignore(writer.writeAll(" ZF"));
if (rflags.sign) ignore(writer.writeAll(" SF"));
if (rflags.trap) ignore(writer.writeAll(" TF"));
if (rflags.interrupt_enable) ignore(writer.writeAll(" IF"));
if (rflags.direction) ignore(writer.writeAll(" DF"));
if (rflags.overflow) ignore(writer.writeAll(" OF"));
if (rflags.resume_) ignore(writer.writeAll(" RF"));
if (rflags.virtual_8086_mode) ignore(writer.writeAll(" VM"));
ignore(writer.print(" IOPL={d}]", .{rflags.iopl}));
std.log.err(
"CR2=0x{x:0>16} CR3=0x{x:0>16} RFLAGS={s}",
.{ cr2, cr3, buf[0..writer.end] },
);
kep.panic.panic_with_frame("Unhandled exception", frame.rbp);
}
@@ -102,7 +131,7 @@ export fn isr_handler_main(frame: *const amd64.IrqFrame) callconv(.{ .x86_64_sys
}
}
extern fn idt_load(idt_ptr: *const amd64.Idtr) callconv(.{ .x86_64_sysv = .{} }) void;
extern fn idt_load(idt_ptr: *const amd64.Idtr) callconv(.c) void;
pub fn init() linksection(r.init) void {
const idtr: amd64.Idtr = .{
@@ -111,7 +140,12 @@ pub fn init() linksection(r.init) void {
};
for (&idt, 0..256) |*entry, i| {
entry.* = .init(0x28, 0, .InterruptGate, __interrupt_vectors[i]);
entry.* = amd64.IdtEntry.init(
0x28,
0,
.InterruptGate,
__interrupt_vectors[i],
);
}
idt_load(&idtr);
+2
View File
@@ -1,3 +1,5 @@
//! CPU-local data.
const config = @import("config");
const rtl = @import("rtl");
const r = @import("root");
+8 -4
View File
@@ -1,10 +1,14 @@
const std = @import("std");
//! Deferred procedure calls (DPC).
const config = @import("config");
const rtl = @import("rtl");
const r = @import("root");
const ke = r.ke;
const kep = ke.private;
const std = @import("std");
const assert = std.debug.assert;
/// Deferred Procedure Call (DPC) structure.
/// Used for scheduling work to be done when IPL is lowered below `.Dispatch`.
pub const Dpc = struct {
@@ -23,7 +27,7 @@ pub const Dpc = struct {
.link = undefined,
.func = func,
.arg = undefined,
.inserted = .init(false),
.inserted = std.atomic.Value(bool).init(false),
};
}
};
@@ -108,7 +112,7 @@ fn dispatch_queue(cpu: u32) void {
const arg = dpc.arg;
dpc_cpu.lock.release(ipl);
std.debug.assert(ke.ipl.current() == .Dispatch);
assert(ke.ipl.current() == .Dispatch);
dpc.func(dpc, arg);
}
@@ -137,7 +141,7 @@ fn dispatch_queue(cpu: u32) void {
kep.shootdown.process_shootdowns();
if (sched_cpu.next_thread != null) {
const curtd = sched_cpu.current_thread.?;
const curtd = sched_cpu.current_thread orelse unreachable;
if (!curtd.smr_sections.is_empty()) {
kep.smr.mark_thread_stalled(curtd);
+5 -5
View File
@@ -1,3 +1,5 @@
//! Kernel events.
const ke = @import("root").ke;
const kep = ke.private;
@@ -5,8 +7,6 @@ const kep = ke.private;
pub const Event = struct {
hdr: kep.wait.DispatchHeader,
const Self = @This();
pub const Type = enum {
/// Wake only one waiter and decrease signaled.
Synchronization,
@@ -15,7 +15,7 @@ pub const Event = struct {
};
/// Initialize an event with a specified type.
pub fn init(self: *Self, @"type": Type) void {
pub fn init(self: *Event, @"type": Type) void {
self.hdr.init(switch (@"type") {
.Synchronization => .Synchronization,
.Notification => .Notification,
@@ -23,7 +23,7 @@ pub const Event = struct {
}
/// Signal an event, waking its waiter(s).
pub fn signal(self: *Self) void {
pub fn signal(self: *Event) void {
const ipl = self.hdr.lock.acquire();
self.hdr.signaled = 1;
kep.wait.satisfy_wait(&self.hdr);
@@ -31,7 +31,7 @@ pub const Event = struct {
}
/// Reset an event.
pub fn reset(self: *Self) void {
pub fn reset(self: *Event) void {
const ipl = self.hdr.lock.acquire();
self.hdr.signaled = 0;
self.hdr.lock.release(ipl);
+6 -3
View File
@@ -31,7 +31,7 @@ const PerCpu = struct {
const percpu = ke.CpuLocal(PerCpu, undefined);
var frozen_cpus: std.atomic.Value(u32) = .init(0);
var frozen_cpus = std.atomic.Value(u32).init(0);
/// Call a function on another CPU.
/// The function will be executed synchronously, i.e this will block until
@@ -69,13 +69,16 @@ pub fn unicast(
/// The function will be executed synchronously, i.e this will block until
/// the function finishes running. The callback is called with the sender cpu
/// and provided argument as arguments.
pub fn broadcast(func: *const fn (u32, ?*anyopaque) void, arg: ?*anyopaque) void {
pub fn broadcast(
func: *const fn (u32, ?*anyopaque) void,
arg: ?*anyopaque,
) void {
const ipl = ke.ipl.raise(.Dispatch);
const curcpu = percpu.local();
curcpu.func = func;
curcpu.arg = arg;
curcpu.counter = .init(@truncate(ke.ncpus - 1));
curcpu.counter = std.atomic.Value(u16).init(@truncate(ke.ncpus - 1));
for (0..ke.ncpus) |i| {
if (i == ke.cpu.current()) continue;
+10 -6
View File
@@ -1,8 +1,12 @@
const std = @import("std");
//! Interrupt priority levels.
const r = @import("root");
const ke = r.ke;
const kep = ke.private;
const std = @import("std");
const assert = std.debug.assert;
/// Interrupt priority level (IPL)
pub const Ipl = enum(u8) {
/// All interrupts enabled.
@@ -19,7 +23,7 @@ pub const Ipl = enum(u8) {
}
pub fn value(self: Ipl) u8 {
return @intFromEnum(self);
return @backingInt(self);
}
};
@@ -36,7 +40,7 @@ pub const cpu_ipl = ke.ExportedCpuLocal(
);
pub const percpu = ke.CpuLocal(PerCpu, .{
.pending_softints = .init(0),
.pending_softints = std.atomic.Value(u8).init(0),
});
pub fn current() Ipl {
@@ -109,7 +113,7 @@ pub fn set_hardware(new: Ipl) Ipl {
/// Mark a software interrupt of IPL `ipl` on `cpu` as pending.
pub fn set_softint_pending(cpu: u32, ipl: Ipl) void {
std.debug.assert(ipl.value() <= Ipl.Dispatch.value());
assert(ipl.value() <= Ipl.Dispatch.value());
_ = percpu.remote(cpu).pending_softints.bitSet(
@intCast(ipl.value()),
.monotonic,
@@ -118,7 +122,7 @@ pub fn set_softint_pending(cpu: u32, ipl: Ipl) void {
/// Mark a software interrupt of IPL `ipl` on `cpu` as handled.
pub fn clear_softint_pending(cpu: u32, ipl: Ipl) void {
std.debug.assert(ipl.value() <= Ipl.Dispatch.value());
assert(ipl.value() <= Ipl.Dispatch.value());
_ = percpu.remote(cpu).pending_softints.bitReset(
@intCast(ipl.value()),
.monotonic,
@@ -128,7 +132,7 @@ pub fn clear_softint_pending(cpu: u32, ipl: Ipl) void {
/// Check whether a software interrupt of IPL `ipl`
/// is pending on the current CPU.
pub fn is_softint_pending(ipl: Ipl) bool {
std.debug.assert(ipl.value() <= Ipl.Dispatch.value());
assert(ipl.value() <= Ipl.Dispatch.value());
const shift: u3 = @intCast(ipl.value());
const bit: u8 = @as(u8, 1) << shift;
return percpu.local().pending_softints.load(.monotonic) & bit != 0;
+2
View File
@@ -1,3 +1,5 @@
//! Kernel logging.
const std = @import("std");
const config = @import("config");
const r = @import("root");
+48 -39
View File
@@ -62,10 +62,10 @@
//! # Wrap handling
//!
//! Data blocks are never split across the physical boundary of the data array.
//! If an allocation would cross the boundary, the block is placed at offset 0 of
//! the next generation and a stub (containing only the descriptor ID) is left at
//! the original position so the data ring can be walked linearly during eviction.
//!
//! If an allocation would cross the boundary, the block is placed at offset 0
//! of the next generation and a stub (containing only the descriptor ID) is
//! left at the original position so the data ring can be walked linearly
//! during eviction.
const std = @import("std");
const r = @import("root");
@@ -154,10 +154,10 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
/// Beginning of the oldest data block.
tail_lpos: std.atomic.Value(usize),
const Self = @This();
const Ring = @This();
// Return the exact Id of the descriptor that occupied this physical slot
// one generation ago.
// Return the exact Id of the descriptor that occupied this physical
// slot one generation ago.
fn prev_wrap(id: Id) Id {
return id -% desc_count;
}
@@ -178,15 +178,15 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
return sz;
}
fn to_desc(self: *Self, id: Id) *Desc {
fn to_desc(self: *Ring, id: Id) *Desc {
return &self.descs[id & desc_mask];
}
fn to_info(self: *Self, id: Id) *Info {
fn to_info(self: *Ring, id: Id) *Info {
return &self.infos[id & desc_mask];
}
fn to_block(self: *Self, lpos: usize) *DataBlock {
fn to_block(self: *Ring, lpos: usize) *DataBlock {
return @ptrCast(@alignCast(&self.data[lpos & data_mask]));
}
@@ -200,7 +200,7 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
// Uses wrapping subtraction so that:
// - if current > next (another CPU already advanced past the target),
// returns false.
// - if current == next (already at target), the -1 wraps to usize max,
// - if current == next (already at target), the -1 wraps to usize_max,
// returns false.
// - if the distance exceeds data_size (should never happen),
// returns false.
@@ -214,7 +214,7 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
desc: Desc = undefined,
};
fn read_desc(self: *Self, id: Id) ReadDescResult {
fn read_desc(self: *Ring, id: Id) ReadDescResult {
const desc = self.to_desc(id);
const info = self.to_info(id);
var state_var = desc.state.load(.acquire);
@@ -245,7 +245,7 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
}
fn read_desc_finalized(
self: *Self,
self: *Ring,
id: Id,
seq: u64,
desc_out: *Desc,
@@ -263,7 +263,7 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
return error.DataLost;
}
fn free_desc(self: *Self, id: Id) void {
fn free_desc(self: *Ring, id: Id) void {
var desc = self.to_desc(id);
const expected_state: State = .{
.state = .Published,
@@ -282,7 +282,7 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
);
}
fn free_data(self: *Self, begin: usize, end: usize) ?usize {
fn free_data(self: *Ring, begin: usize, end: usize) ?usize {
var desc: Desc = undefined;
var cur_begin = begin;
@@ -330,7 +330,7 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
return cur_begin;
}
fn data_advance_tail(self: *Self, new_tail_lpos: usize) !void {
fn data_advance_tail(self: *Ring, new_tail_lpos: usize) !void {
if (new_tail_lpos & 1 != 0) {
// No data.
return;
@@ -355,8 +355,8 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
break;
}
} else {
// Failed to free the data, which means we either lost a race
// or the descriptor is still in use.
// Failed to free the data, which means we either lost a
// race or the descriptor is still in use.
// Reload the tail position to try again.
const new_tail = self.tail_lpos.load(.acquire);
if (new_tail == cur_tail) {
@@ -372,7 +372,7 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
}
// Try advancing the tail.
fn advance_tail(self: *Self, tail_id: Id) !void {
fn advance_tail(self: *Ring, tail_id: Id) !void {
const res = self.read_desc(tail_id);
const tail_desc = res.desc;
@@ -387,7 +387,8 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
return error.ReservationFailed;
},
.Miss => {
if (tail_desc.state.load(.monotonic).id == prev_wrap(tail_id)) {
const tail_desc_id = tail_desc.state.load(.monotonic).id;
if (tail_desc_id == prev_wrap(tail_id)) {
// This must mean that the descriptor is currently
// getting reserved by another writer.
return error.ReservationFailed;
@@ -411,8 +412,9 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
if (next_res.state == .Published or next_res.state == .Free) {
// Advance the tail to the next descriptor.
// release: push all previous state changes before the tail update,
// guaranteeing the new tail is visible after all evictions.
// release: push all previous state changes before the tail
// update, guaranteeing the new tail is visible after all
// evictions.
_ = self.tail_id.cmpxchgStrong(
@as(usize, tail_id),
@as(usize, tail_id +% 1),
@@ -433,7 +435,7 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
}
// Reserve a new descriptor and make space for it if needed.
fn reserve_desc(self: *Self) !Id {
fn reserve_desc(self: *Ring) !Id {
var head_id: Id = @truncate(self.head_id.load(.acquire));
var new_id: Id = undefined;
var prev_id: Id = undefined;
@@ -520,7 +522,12 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
return next_lpos;
}
fn alloc_data(self: *Self, size: usize, id: Id, out_position: *BlkPos) ReserveError![]u8 {
fn alloc_data(
self: *Ring,
size: usize,
id: Id,
out_position: *BlkPos,
) ReserveError![]u8 {
const blk_size = to_block_size(size);
var head_lpos = self.head_lpos.load(.monotonic);
var new_head_lpos = head_lpos;
@@ -534,7 +541,8 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
while (true) {
new_head_lpos = get_next_lpos(head_lpos, blk_size);
// Make room if needed, try advancing the tail until we have enough space.
// Make room if needed, try advancing the tail until we have
// enough space.
self.data_advance_tail(new_head_lpos -% data_size) catch |e| {
out_position.begin = lpos_no_data;
out_position.end = lpos_no_data;
@@ -570,7 +578,7 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
return @as([*]u8, @ptrCast(&blk.data))[0..size];
}
fn read_internal(self: *Self, seq: u64, buf: ?[]u8) !Info {
fn read_internal(self: *Ring, seq: u64, buf: ?[]u8) !Info {
const rdesc = self.to_desc(@truncate(seq));
const info = self.to_info(@truncate(seq));
var desc: Desc = undefined;
@@ -601,14 +609,14 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
}
/// Initialize and return a new ring buffer instance.
pub fn init() Self {
pub fn init() Ring {
// Start near usize max so that the first wrap of the ID/lpos space
// happens almost immediately, testing overflow handling early
// rather than after billions of records.
const dummy_id: Id = 0 -% @as(Id, desc_count + 1);
const blk0: usize = -%@as(usize, data_size);
var self: Self = std.mem.zeroes(Self);
var self: Ring = std.mem.zeroes(Ring);
// Place a dummy descriptor in the last slot. head_id and tail_id
// both start here so that the first reservation (head_id + 1) lands
@@ -638,7 +646,7 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
}
/// Reserve space for a record and return a reservation.
pub fn reserve(self: *Self, size: usize) ReserveError!Reservation {
pub fn reserve(self: *Ring, size: usize) ReserveError!Reservation {
if (!check_size(size)) {
return error.NoSpace;
}
@@ -657,15 +665,15 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
if (seq == 0 and (id & desc_mask) != 0) {
// First time this slot has been used.
// Slot 0 never hits this branch, its seq is set to -(desc_count)
// in init() so that the else branch below produces seq=0 on
// its first use.
// Slot 0 never hits this branch, its seq is set
// to -(desc_count) in init() so that the else branch
// below produces seq=0 on its first use.
info.sequence = @as(u64, id & desc_mask);
} else {
// Slot is being recycled (or is slot 0 on first use).
// Adding desc_count advances the sequence by one full ring wrap,
// keeping sequence numbers monotonically increasing across
// generations.
// Adding desc_count advances the sequence by one full
// ring wrap, keeping sequence numbers monotonically increasing
// across generations.
info.sequence = seq +% desc_count;
}
@@ -686,7 +694,7 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
}
/// Publish a previously reserved record.
pub fn publish(self: *Self, res: Reservation) void {
pub fn publish(self: *Ring, res: Reservation) void {
const desc = self.to_desc(res.id);
_ = desc.state.cmpxchgStrong(
@@ -707,7 +715,7 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
}
/// Return the first readable sequence currently retained by the ring.
pub fn first_seq(self: *Self) u64 {
pub fn first_seq(self: *Ring) u64 {
var seq: u64 = undefined;
while (true) {
@@ -723,11 +731,12 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
}
/// Read a record at `seq` into `buf`.
pub fn read(self: *Self, seq: u64, buf: ?[]u8) ReadError!Info {
pub fn read(self: *Ring, seq: u64, buf: ?[]u8) ReadError!Info {
var cur_seq = seq;
while (true) {
return self.read_internal(cur_seq, buf) catch |err| switch (err) {
return self.read_internal(cur_seq, buf) catch |err|
switch (err) {
error.Invalid => {
const first = self.first_seq();
if (cur_seq < first) {
+9 -5
View File
@@ -1,3 +1,5 @@
//! Adaptive lock implementation.
const std = @import("std");
const rtl = @import("rtl");
const config = @import("config");
@@ -22,7 +24,7 @@ pub const Mutex = struct {
};
}
return .{ .owner = .init(null) };
return .{ .owner = std.atomic.Value(?*ke.Thread).init(null) };
}
pub fn is_locked(self: *Mutex) bool {
@@ -31,7 +33,8 @@ pub const Mutex = struct {
pub fn acquire(m: *Mutex) void {
const ipl = ke.ipl.raise(.Dispatch);
const curtd = kep.sched.percpu.local().current_thread.?;
const curtd = kep.sched.percpu.local().current_thread orelse
unreachable;
if (config.warden) {
kep.warden.check(m.wd, .Lock);
@@ -98,7 +101,7 @@ pub const Mutex = struct {
kep.turnstile.block(
ts,
m,
.{ .single = owner.? },
.{ .single = owner orelse unreachable },
.Exclusive,
);
@@ -133,6 +136,7 @@ pub const Mutex = struct {
}
var waiters: rtl.List = undefined;
const turn = ts orelse unreachable;
// Note: wake up all waiters.
// This so-called "lock barging" (name from WTF::ParkingLot) has been
@@ -140,9 +144,9 @@ pub const Mutex = struct {
// see this mysterious 70s paper:
// https://dl.acm.org/doi/pdf/10.1145/850657.850659
kep.turnstile.signal(
ts.?,
turn,
.Exclusive,
ts.?.waiters,
turn.waiters,
null,
&waiters,
);
+29 -9
View File
@@ -1,3 +1,5 @@
//! Kernel panic handling code.
const std = @import("std");
const config = @import("config");
const r = @import("root");
@@ -43,7 +45,10 @@ fn get_symbol_name(addr: usize) ?Symbol {
const sym = symbols[left - 1];
if (sym.addr <= addr) {
return .{ .name = sym.name_ptr[0..sym.name_len], .offset = addr - sym.addr };
return .{
.name = sym.name_ptr[0..sym.name_len],
.offset = addr - sym.addr,
};
}
}
@@ -57,7 +62,11 @@ fn is_kernel_text(addr: usize) bool {
}
fn walk_stack_frame(base: usize) void {
var frame: ?*StackFrame = @ptrFromInt(if (config.arch == .riscv64) base - 16 else base);
var frame: ?*StackFrame = @ptrFromInt(if (config.arch == .riscv64)
base - 16
else
base);
var depth: usize = 0;
while (frame) |f| : (depth += 1) {
if (depth > 64) break;
@@ -65,16 +74,27 @@ fn walk_stack_frame(base: usize) void {
if (!is_kernel_text(ret_addr)) {
break;
}
const sym = get_symbol_name(ret_addr) orelse Symbol{ .name = "???", .offset = 0 };
std.log.err(" #{d} {s}+0x{x} - 0x{x}", .{ depth, sym.name, sym.offset, ret_addr });
frame = if (config.arch == .riscv64 and f.prev != null)
@ptrFromInt(@intFromPtr(f.prev.?) - 16)
else
f.prev;
const sym = get_symbol_name(ret_addr) orelse
Symbol{ .name = "???", .offset = 0 };
std.log.err(" #{d} {s}+0x{x} - 0x{x}", .{
depth,
sym.name,
sym.offset,
ret_addr,
});
if (config.arch == .riscv64) {
if (f.prev) |fr| {
frame = @ptrFromInt(@intFromPtr(fr) - 16);
}
} else {
frame = f.prev;
}
}
}
var crash_count: std.atomic.Value(u8) = .init(0);
var crash_count = std.atomic.Value(u8).init(0);
pub fn panic_with_frame(
msg: []const u8,
+2
View File
@@ -1,3 +1,5 @@
//! Private kernel definitions.
const config = @import("config");
const init_mod = @import("init.zig");
const r = @import("root");
+6 -4
View File
@@ -9,7 +9,9 @@ const r = @import("root");
const rtl = @import("rtl");
const ke = r.ke;
const kep = ke.private;
const std = @import("std");
const assert = std.debug.assert;
pub const Queue = struct {
hdr: kep.wait.DispatchHeader,
@@ -56,11 +58,11 @@ pub const Queue = struct {
/// This blocks until an item is actually popped.
pub fn remove(self: *Queue, timeout: ?r.Nanoseconds) !*rtl.List.Entry {
const ipl = self.hdr.lock.acquire();
const td = kep.sched.percpu.local().current_thread.?;
const td = kep.sched.percpu.local().current_thread orelse unreachable;
if (td.queue) |q| {
std.debug.assert(q == self);
std.debug.assert(self.active > 0);
assert(q == self);
assert(self.active > 0);
self.active -= 1;
if (self.hdr.signaled > 0 and self.active < self.max_active) {
@@ -90,7 +92,7 @@ pub const Queue = struct {
/// than the queue.
pub fn signal_wait(queue: *Queue) void {
const ipl = queue.hdr.lock.acquire();
std.debug.assert(queue.active > 0);
assert(queue.active > 0);
queue.active -= 1;
+55 -10
View File
@@ -1,3 +1,5 @@
//! Kernel riscv64-specific code.
const r = @import("root");
const ke = r.ke;
const rv64 = @import("arch");
@@ -20,7 +22,12 @@ pub const ThreadContext = extern struct {
return .{ .sp = stack };
}
pub fn reset(self: *@This(), stack_top: r.VAddr, entry: *const fn (?*anyopaque) void, arg: ?*anyopaque) @This() {
pub fn reset(
self: *@This(),
stack_top: r.VAddr,
entry: *const fn (?*anyopaque) void,
arg: ?*anyopaque,
) @This() {
self.sp = stack_top & ~@as(usize, 15);
self.ra = @intFromPtr(&thread_start);
self.s = @splat(0);
@@ -35,11 +42,27 @@ pub const ThreadContext = extern struct {
};
extern fn riscv_context_load(ctx: *ThreadContext) callconv(.c) noreturn;
extern fn riscv_context_switch(old: *ThreadContext, new: *ThreadContext, lock: *u8, switching: *bool) callconv(.c) void;
extern fn riscv_context_switch_cont(old: *ke.Thread.Context, new: *ThreadContext, lock: *u8, switching: *bool) callconv(.c) void;
extern fn riscv_context_switch(
old: *ThreadContext,
new: *ThreadContext,
lock: *u8,
switching: *bool,
) callconv(.c) void;
extern fn riscv_context_switch_cont(
old: *ke.Thread.Context,
new: *ThreadContext,
lock: *u8,
switching: *bool,
) callconv(.c) void;
extern fn thread_start() callconv(.c) noreturn;
export fn riscv_thread_entry(entry_addr: usize, arg_addr: usize) callconv(.c) noreturn {
export fn riscv_thread_entry(
entry_addr: usize,
arg_addr: usize,
) callconv(.c) noreturn {
const entry: *const fn (?*anyopaque) void = @ptrFromInt(entry_addr);
entry(@ptrFromInt(arg_addr));
@panic("Thread entry returned");
@@ -49,24 +72,46 @@ export fn riscv_free_old_stack(stack: usize) callconv(.c) void {
ke.private.thread.stack_cache_free(stack);
}
export fn riscv_run_continuation(func_addr: usize, arg: ?*anyopaque) callconv(.c) noreturn {
export fn riscv_run_continuation(
func_addr: usize,
arg: ?*anyopaque,
) callconv(.c) noreturn {
ke.ipl.lower(.Passive);
const func: *const fn (?*anyopaque) void = @ptrFromInt(func_addr);
func(arg);
@panic("Continuation returned");
}
pub fn switch_normal_to_normal(old: *ke.Thread.Context, new: *ke.Thread.Context) void {
pub fn switch_normal_to_normal(
old: *ke.Thread.Context,
new: *ke.Thread.Context,
) void {
const thread: *ke.Thread = @alignCast(@fieldParentPtr("context", old));
riscv_context_switch(&old.impl, &new.impl, &thread.lock.inner.locked.raw, &thread.switching.raw);
riscv_context_switch(
&old.impl,
&new.impl,
&thread.lock.inner.locked.raw,
&thread.switching.raw,
);
}
pub fn switch_cont_to_normal(old: *ke.Thread.Context, new: *ke.Thread.Context) void {
pub fn switch_cont_to_normal(
old: *ke.Thread.Context,
new: *ke.Thread.Context,
) void {
const thread: *ke.Thread = @alignCast(@fieldParentPtr("context", old));
riscv_context_switch_cont(old, &new.impl, &thread.lock.inner.locked.raw, &thread.switching.raw);
riscv_context_switch_cont(
old,
&new.impl,
&thread.lock.inner.locked.raw,
&thread.switching.raw,
);
}
pub fn call_continuation(ctx: *ke.Thread.Context, continuation: ke.Continuation) noreturn {
pub fn call_continuation(
ctx: *ke.Thread.Context,
continuation: ke.Continuation,
) noreturn {
const sp = ctx.stack_top & ~@as(usize, 15);
asm volatile (
\\mv sp, %[stack]
+7 -1
View File
@@ -1,3 +1,5 @@
//! riscv64 interrupt handling.
const r = @import("root");
const ke = r.ke;
const std = @import("std");
@@ -13,5 +15,9 @@ export fn riscv_trap_handler(cause: usize, pc: usize) callconv(.c) void {
}
return;
}
std.debug.panic("trap: cause = 0x{x}, pc = 0x{x}, tval = 0x{x}", .{ cause, pc, rv64.read_csr("stval") });
std.debug.panic("trap: cause = 0x{x}, pc = 0x{x}, tval = 0x{x}", .{
cause,
pc,
rv64.read_csr("stval"),
});
}
+2
View File
@@ -1,3 +1,5 @@
//! Public kernel interfaces.
// === Exported Modules ===
pub const private = @import("private.zig");
+88 -63
View File
@@ -91,9 +91,9 @@
//! balancing, the load is preferred as to prioritize latency
//! (i.e. you want to run a thread ASAP).
//!
//! See `sched_ule.c` in FreeBSD for the original implementation this is based on.
//! See `sched_ule.c` in FreeBSD for the original implementation this is based
//! on.
const std = @import("std");
const rtl = @import("rtl");
const config = @import("config");
const r = @import("root");
@@ -101,10 +101,13 @@ const ke = r.ke;
const kep = ke.private;
const pl = r.pl;
const std = @import("std");
const assert = std.debug.assert;
const runqueues_n = 64;
const interactivity_threshold = 30;
const scaling_factor = 50;
const preempt_threshold = @intFromEnum(ke.Priority.LowRealtime);
const preempt_threshold = @backingInt(ke.Priority.LowRealtime);
const balance_interval = @as(u64, config.sched_balance_interval);
const sched_migration_cost = ke.Tunable(
u64,
@@ -122,8 +125,9 @@ const pct_window_max = 11 * std.time.us_per_s;
// Each thread carries a decaying exponential weighted moving average (EWMA)
// of the time it has spent runnable (running or in a queue). Each CPU maintains
// the sum of its threads' values, which is used in load balancing decisions to
// estimate the *actual* load of a CPU (and not just its thread count). Each value
// is updated using periods of 1024us (~1ms) with a half-life of 32ms, i.e y^32 = 0.5.
// estimate the *actual* load of a CPU (and not just its thread count).
// Each value is updated using periods of 1024us (~1ms) with a half-life of
// 32ms, i.e y^32 = 0.5.
// Math goes more in depth in comments below.
const pelt_scale = 1024;
const pelt_decay = 32;
@@ -146,7 +150,7 @@ const pelt_halflife = pelt_scale * pelt_decay;
pub const pelt_load_avg_max = 47742;
// pelt_inv_table[k] = floor(2^32 * 2^(-k/pelt_decay))
// NOTE: Linux uses 2^32 - 1 as base but this practically yields the same values.
// NOTE: Linux uses 2^32-1 as base but this practically yields the same values.
const pelt_inv_table: [pelt_decay]u32 = .{
0xffffffff, 0xfa83b2db, 0xf5257d15, 0xefe4b99b,
0xeac0c6e7, 0xe5b906e7, 0xe0ccdeec, 0xdbfbb797,
@@ -223,7 +227,7 @@ pub const Average = struct {
// Amount of the period that has been accounted for
period_contrib: usize = 0,
/// Timestamp (us) up to which history has been accounted.
last_update: r.Microseconds = .init(0),
last_update: r.Microseconds = r.Microseconds.init(0),
};
pub const Accounting = struct {
@@ -240,7 +244,7 @@ pub const Accounting = struct {
pub const percpu = ke.CpuLocal(PerCpu, undefined);
var balance_timer: ke.Timer = undefined;
var balance_dpc: ke.Dpc = .init(balance);
var balance_dpc = ke.Dpc.init(balance);
// LCG values taken from FreeBSD.
var lcg = std.Random.lcg.Wrapping(u32).init(0, 69069, 5);
@@ -260,15 +264,15 @@ fn init_cpu() linksection(r.init) void {
.status = 0,
.queues = undefined,
},
.queues_lock = .init("runq"),
.queues_lock = ke.SpinLock.init("runq"),
.idle_queue = undefined,
.steal_work = false,
.load = .init(0),
.load_avg = .init(0),
.est_load_avg = .init(0),
.migratable = .init(0),
.current_thread_prio = .init(0),
.resched_dpc = .init(kep.sched.clock),
.load = std.atomic.Value(usize).init(0),
.load_avg = std.atomic.Value(usize).init(0),
.est_load_avg = std.atomic.Value(usize).init(0),
.migratable = std.atomic.Value(usize).init(0),
.current_thread_prio = std.atomic.Value(u8).init(0),
.resched_dpc = ke.Dpc.init(kep.sched.clock),
.resched_timer = undefined,
.start_timer = false,
.preemption_reason = .None,
@@ -318,7 +322,8 @@ fn pelt_do_decay(val: u64, n: u64) u64 {
if (rem != 0) {
// Fractional remainder, index into the table.
// The table gives us 2^32 * 2^(-k/pelt_decay), shift by 32 to remove the 2^32.
// The table gives us 2^32 * 2^(-k/pelt_decay), shift by 32 to remove
// the 2^32.
v = (v * pelt_inv_table[(rem * pelt_decay) / pelt_halflife]) >> 32;
}
@@ -360,10 +365,12 @@ fn pelt_update(avg: *Average, runnable: bool) void {
// d1 * y^p
const c1 = pelt_do_decay(d1, decay_time);
// For a full period, its contribution is 1024y + 1024y^2 + ... + 1024y^(p-1)
// inf
// For a full period, its contribution is:
// 1024y + 1024y^2 + ... + 1024y^(p-1)
//
// We know load_avg_max = p * Sum y^n
// n=0
//
// So, load_avg_max - load_avg_max * y^p yields:
// p-1 p-1
// p * Sum y^n, subtracting the first term (n=0) gives us p * Sum
@@ -541,7 +548,7 @@ fn recompute_priority(td: *ke.Thread) void {
// Choose a priority based on score, the lower the score,
// the higher the priority it will be.
// This is a simple formula I came up with that is probably good enough.
@intCast(@intFromEnum(ke.Priority.LowInteractive) +
@intCast(@backingInt(ke.Priority.LowInteractive) +
((interactivity_threshold - score) / 2))
else blk: {
// Thread is not interactive, compute priority from recent CPU usage.
@@ -560,14 +567,14 @@ fn recompute_priority(td: *ke.Thread) void {
// positive decreases it.
const nice_off: i16 = @as(i16, td.nice) + ke.Priority.nice_max;
const raw: i16 = @as(i16, @intFromEnum(ke.Priority.HighBatch)) -
const raw: i16 = @as(i16, @backingInt(ke.Priority.HighBatch)) -
@as(i16, cpu_pri_off) -
nice_off;
break :blk @intCast(std.math.clamp(
raw,
@intFromEnum(ke.Priority.LowBatch),
@intFromEnum(ke.Priority.HighBatch),
@backingInt(ke.Priority.LowBatch),
@backingInt(ke.Priority.HighBatch),
));
};
@@ -589,8 +596,12 @@ fn calendar_queue_increment(cpu: *PerCpu) void {
}
}
fn pick_realtime_thread(cpu: *PerCpu, minimum_prio: u8, target: ?u32) ?*ke.Thread {
std.debug.assert(cpu.queues_lock.is_locked());
fn pick_realtime_thread(
cpu: *PerCpu,
minimum_prio: u8,
target: ?u32,
) ?*ke.Thread {
assert(cpu.queues_lock.is_locked());
const runq = &cpu.realtime_queue;
const minprio = to_sched_prio(minimum_prio);
@@ -610,7 +621,7 @@ fn pick_realtime_thread(cpu: *PerCpu, minimum_prio: u8, target: ?u32) ?*ke.Threa
const curr_queue = &runq.queues[i];
std.debug.assert(!curr_queue.is_empty());
assert(!curr_queue.is_empty());
// Get the first thread of the queue.
var td: *ke.Thread = @fieldParentPtr(
@@ -647,7 +658,7 @@ fn pick_realtime_thread(cpu: *PerCpu, minimum_prio: u8, target: ?u32) ?*ke.Threa
}
fn pick_batch_thread(cpu: *PerCpu, target: ?u32) ?*ke.Thread {
std.debug.assert(cpu.queues_lock.is_locked());
assert(cpu.queues_lock.is_locked());
const runq = &cpu.calendar_queue;
if (runq.status == 0) return null;
@@ -669,7 +680,7 @@ fn pick_batch_thread(cpu: *PerCpu, target: ?u32) ?*ke.Thread {
const curr_queue = &runq.queues[i];
std.debug.assert(!curr_queue.is_empty());
assert(!curr_queue.is_empty());
// Get the first thread of the queue.
var td: *ke.Thread = @fieldParentPtr(
@@ -710,7 +721,7 @@ fn pick_batch_thread(cpu: *PerCpu, target: ?u32) ?*ke.Thread {
}
fn pick_idle_thread(cpu: *PerCpu, target: ?u32) ?*ke.Thread {
std.debug.assert(cpu.queues_lock.is_locked());
assert(cpu.queues_lock.is_locked());
if (cpu.idle_queue.is_empty()) return null;
// Just get the first thread from the queue that can run.
@@ -743,7 +754,7 @@ fn insert_in_list(list: *rtl.List, link: *rtl.List.Entry, head: bool) void {
// Insert a thread into its respective queue.
fn insert_in_queue(cpu: *PerCpu, td: *ke.Thread, preempted: bool) void {
std.debug.assert(cpu.queues_lock.is_locked());
assert(cpu.queues_lock.is_locked());
td.cpu = null;
@@ -766,7 +777,7 @@ fn insert_in_queue(cpu: *PerCpu, td: *ke.Thread, preempted: bool) void {
// The insertion index is determined by insidx and the the priority of
// the thread, higher priority threads will be put closer to insidx,
// which ensures that they are ran more frequently in proportion.
var idx = (cpu.insidx + (@intFromEnum(ke.Priority.HighBatch) -
var idx = (cpu.insidx + (@backingInt(ke.Priority.HighBatch) -
td.priority)) % runqueues_n;
if (preempted) {
@@ -800,8 +811,8 @@ fn insert_in_queue(cpu: *PerCpu, td: *ke.Thread, preempted: bool) void {
// Remove a thread from a queue.
fn remove_from_queue(cpu: *PerCpu, td: *ke.Thread) void {
std.debug.assert(td.lock.is_locked());
std.debug.assert(cpu.queues_lock.is_locked());
assert(td.lock.is_locked());
assert(cpu.queues_lock.is_locked());
// Decrease load.
_ = cpu.load.fetchSub(1, .monotonic);
@@ -840,12 +851,12 @@ fn select_thread(cur: ?*ke.Thread, cpu: *PerCpu, target: ?u32) ?*ke.Thread {
return td;
}
if (cur) |curtd| {
if (curtd.priority_class() == .Realtime or curtd.is_interactive()) {
// Nothing lower can preempt this thread.
if (cur != null and (cur.?.priority_class() == .Realtime or
cur.?.is_interactive()))
{
return null;
}
}
// Then try picking a batch thread.
if (pick_batch_thread(cpu, target)) |td| {
@@ -856,10 +867,12 @@ fn select_thread(cur: ?*ke.Thread, cpu: *PerCpu, target: ?u32) ?*ke.Thread {
return td;
}
if (cur != null and cur.?.priority_class() == .Idle) {
// Idle threads don't preempt each other
if (cur) |curtd| {
if (curtd.priority_class() == .Idle) {
// Idle threads don't preempt each other.
return null;
}
}
// Finally try picking from the idle queue.
if (pick_idle_thread(cpu, target)) |td| {
@@ -890,7 +903,7 @@ fn should_prio_preempt(td: *ke.Thread, other_prio: u8, remote: bool) bool {
}
if (class == .Timeshare and td.is_interactive() and
other_prio < @intFromEnum(ke.Priority.LowInteractive) and remote)
other_prio < @backingInt(ke.Priority.LowInteractive) and remote)
{
// Interactive threads always preempt batch non-interactive ones
// on remote CPUs.
@@ -924,7 +937,9 @@ fn should_preempt_cpu(td: *ke.Thread, cpu: *PerCpu, remote: bool) bool {
// Find a suitable CPU to run the thread.
fn pick_cpu(td: *ke.Thread, slept: u64) u32 {
// CPU selection policy in order:
// 1. Pick the last CPU the thread ran on if it would run immediately or if it's pinned.
// 1. Pick the last CPU the thread ran on if it would run immediately or
// if it's pinned.
//
// 2. Pick the least loaded CPU on which the thread can run immediately.
// 3. Pick the least loaded CPU overall.
@@ -993,12 +1008,12 @@ fn pick_cpu(td: *ke.Thread, slept: u64) u32 {
// If we found a preemptible CPU, pick that one.
// Otherwise pick the least loaded one.
return least_preempt orelse least.?;
return least_preempt orelse least orelse unreachable;
}
// Enqueue a thread on a CPU.
fn enqueue_on_cpu(c: u32, td: *ke.Thread) void {
std.debug.assert(td.lock.is_locked());
assert(td.lock.is_locked());
const cpu = percpu.remote(c);
cpu.queues_lock.acquire_no_ipl();
@@ -1097,7 +1112,7 @@ fn do_switch(cpu: *PerCpu, cur: *ke.Thread, next: *ke.Thread) void {
next.can_receive_stack();
if (next.continuation != null and !handoff) {
std.debug.assert(next.context.stack_top != 0);
assert(next.context.stack_top != 0);
next.context.reset(
next.context.stack_top,
@@ -1107,7 +1122,7 @@ fn do_switch(cpu: *PerCpu, cur: *ke.Thread, next: *ke.Thread) void {
}
if (next.context.stack_top == 0) {
std.debug.assert(handoff);
assert(handoff);
}
next.state.store(.Running, .monotonic);
@@ -1138,7 +1153,7 @@ fn do_switch(cpu: *PerCpu, cur: *ke.Thread, next: *ke.Thread) void {
/// Handle preemption.
/// This is called in DPC dispatch when it notices `next_thread` is set.
pub fn handle_preemption(cpu: *PerCpu) void {
const cur = cpu.current_thread.?;
const cur = cpu.current_thread orelse unreachable;
var next: *ke.Thread = undefined;
while (true) {
@@ -1157,7 +1172,7 @@ pub fn handle_preemption(cpu: *PerCpu) void {
}
if (next.context.stack_top == 0 and next.continuation != null) {
// This thread has no stack, try allocating one from the stack cache.
// This thread has no stack, try getting one from the stack cache.
// Otherwise, the thread will be waiting for its stack to get
// allocated and we pick another one in the meantime.
const new_stack = allocate_stack(next);
@@ -1170,14 +1185,18 @@ pub fn handle_preemption(cpu: *PerCpu) void {
detach_load_avg(cpu, next);
continue;
} else if (next.continuation != null) {
next.context.reset(next.context.stack_top, ke.thread.call_continuation, next);
next.context.reset(
next.context.stack_top,
ke.thread.call_continuation,
next,
);
}
// Found a thread with a stack.
break;
}
std.debug.assert(cur != next);
assert(cur != next);
cpu.current_thread_prio.store(next.priority, .monotonic);
@@ -1209,7 +1228,7 @@ pub fn handle_preemption(cpu: *PerCpu) void {
/// Called every time slice in DPC context.
pub fn clock(_: *ke.Dpc, _: ?*anyopaque) void {
const cpu = percpu.local();
std.debug.assert(kep.ipl.current() == .Dispatch);
assert(kep.ipl.current() == .Dispatch);
const curtd = cpu.current_thread orelse return;
@@ -1222,8 +1241,8 @@ pub fn clock(_: *ke.Dpc, _: ?*anyopaque) void {
cpu.queues_lock.acquire_no_ipl();
defer cpu.queues_lock.release_no_ipl();
// Advance the insert index every tick, while keeping a separation of 1 with runidx.
// This ensures fairness.
// Advance the insert index every tick, while keeping a separation of 1
// with runidx. This ensures fairness.
if (cpu.runidx == cpu.insidx) {
cpu.insidx = (cpu.insidx + 1) % runqueues_n;
@@ -1273,7 +1292,7 @@ pub fn enqueue(td: *ke.Thread) void {
/// Yield on the current CPU.
pub fn yield(continuation: ?ke.Continuation) void {
const ipl = ke.ipl.raise(.Dispatch);
const td = percpu.local().current_thread.?;
const td = percpu.local().current_thread orelse unreachable;
td.lock.acquire_no_ipl();
td.switching.store(true, .monotonic);
@@ -1292,7 +1311,7 @@ pub fn yield(continuation: ?ke.Continuation) void {
ke.ipl.lower(ipl);
}
/// Yield the current CPU with the current thread already locked at Dispatch IPL.
/// Yield on the current CPU with the thread already locked at Dispatch IPL.
pub fn yield_locked(continuation: ?ke.Continuation) void {
const sched_cpu = percpu.local();
@@ -1321,16 +1340,17 @@ pub fn yield_locked(continuation: ?ke.Continuation) void {
break;
}
if (next == cur) {
const n = next orelse unreachable;
if (n == cur) {
// We just selected the same thread again.
break;
}
while (next.?.switching.load(.monotonic) == true) {
while (n.switching.load(.monotonic) == true) {
std.atomic.spinLoopHint();
}
if (next) |n| {
const curstack = n.context.stack_top;
const can_handoff =
@@ -1353,7 +1373,6 @@ pub fn yield_locked(continuation: ?ke.Continuation) void {
detach_load_avg(sched_cpu, n);
next = null;
}
}
sched_cpu.queues_lock.acquire_no_ipl();
}
@@ -1385,7 +1404,7 @@ pub fn yield_locked(continuation: ?ke.Continuation) void {
/// Block the currently running thread.
pub fn block(cont: ?ke.Continuation) void {
const ipl = ke.ipl.raise(.Dispatch);
const td = percpu.local().current_thread.?;
const td = ke.thread.current();
td.lock.acquire_no_ipl();
block_locked(td, cont);
@@ -1424,8 +1443,8 @@ pub fn unblock(td: *ke.Thread) void {
}
pub fn unblock_locked(td: *ke.Thread) void {
std.debug.assert(td.lock.is_locked());
std.debug.assert(td.state.load(.monotonic) == .Blocked);
assert(td.lock.is_locked());
assert(td.state.load(.monotonic) == .Blocked);
const now = ke.time.read_time();
@@ -1450,7 +1469,7 @@ pub fn unblock_locked(td: *ke.Thread) void {
}
pub fn update_priority_locked(td: *ke.Thread, new_prio: u8) void {
std.debug.assert(td.lock.is_locked());
assert(td.lock.is_locked());
const old_prio = td.priority;
@@ -1476,7 +1495,7 @@ pub fn update_priority_locked(td: *ke.Thread, new_prio: u8) void {
}
if (state == .Running) {
std.debug.assert(td.last_cpu == c);
assert(td.last_cpu == c);
cpu.current_thread_prio.store(new_prio, .monotonic);
@@ -1625,12 +1644,18 @@ fn find_least_loaded(exclude: ?*ke.CpuMask) ?u32 {
return least;
}
fn steal_thread_from_cpu(cpu: *PerCpu, curcpu: ?*PerCpu, target: ?u32) ?*ke.Thread {
fn steal_thread_from_cpu(
cpu: *PerCpu,
curcpu: ?*PerCpu,
target: ?u32,
) ?*ke.Thread {
cpu.queues_lock.acquire_no_ipl();
defer cpu.queues_lock.release_no_ipl();
if (curcpu != null and curcpu.?.load.load(.monotonic) > 0)
if (curcpu) |c| {
if (c.load.load(.monotonic) > 0)
return null;
}
const td = select_thread(null, cpu, target);
+9 -11
View File
@@ -1,4 +1,5 @@
//! Kernel-side mechanism for asynchronous TLB shootdown and deferred memory reclamation.
//! Kernel-side mechanism for asynchronous TLB shootdowns and deferred
//! memory reclamation.
const std = @import("std");
const config = @import("config");
@@ -32,8 +33,9 @@ const PerCpu = struct {
/// States attached to this CPU.
states: [64]ShootdownState,
/// States relevant to each CPU.
/// If CPU0 sent a shootdown to CPU1, then CPU0 will have `valid_states[CPU1]`
/// pointing to that shootdown, which will sit in `states`.
/// If CPU0 sent a shootdown to CPU1, then CPU0 will have
/// `valid_states[CPU1]` pointing to that shootdown,
/// which will sit in `states`.
valid_states: [config.ncpus]std.atomic.Value(u64),
/// CPUs that have sent this CPU a shootdown.
senders: ke.AtomicCpuMask,
@@ -52,17 +54,17 @@ pub var shootdowns: rtl.HandoffList = undefined;
fn pcpu_init() linksection(r.init) void {
const local = percpu.local();
local.valid_states = @splat(.init(0));
local.senders = .init(false);
local.valid_states = @splat(std.atomic.Value(u64).init(0));
local.senders = ke.AtomicCpuMask.init(false);
local.states = @splat(.{
.base = 0,
.npages = 0,
.state = .init(slot_free),
.state = std.atomic.Value(u16).init(slot_free),
.payload = undefined,
.link = undefined,
});
local.npages = .init(0);
local.npages = std.atomic.Value(u32).init(0);
}
comptime {
@@ -178,10 +180,6 @@ pub fn process_shootdowns() void {
}
}
fn process_shootdowns_kick(_: u32, _: ?*anyopaque) void {
process_shootdowns();
}
/// Submit a shootdown to occur asynchronously on `target_mask`.
/// Returns an error if a synchronous shootdown was done instead.
pub fn submit(state: ShootdownState, target_mask: ke.CpuMask) !void {
+22 -14
View File
@@ -1,14 +1,16 @@
//! Safe memory reclamation implementation.
//! based on FreeBSD's "Global Unbounded Sequences".
const r = @import("root");
const rtl = @import("rtl");
const std = @import("std");
const config = @import("config");
const r = @import("root");
const ke = r.ke;
const kep = ke.private;
const std = @import("std");
const assert = std.debug.assert;
pub const Sequence = u64;
pub const seq_invalid: Sequence = 0;
@@ -34,15 +36,15 @@ pub const Domain = struct {
}
pub fn init(self: *Domain, preempt: bool) void {
self.clock.read_seq = .init(seq_init);
self.clock.write_seq = .init(seq_init);
self.clock.read_seq = std.atomic.Value(Sequence).init(seq_init);
self.clock.write_seq = std.atomic.Value(Sequence).init(seq_init);
self.preempt = preempt;
for (0..ke.ncpus) |i| {
self.cpus[i] = .{
.current_seq = .init(seq_invalid),
.current_seq = std.atomic.Value(Sequence).init(seq_invalid),
.stall_lock = ke.SpinLock.init("stall_lock"),
.stall_seq = .init(seq_invalid),
.stall_seq = std.atomic.Value(Sequence).init(seq_invalid),
.stalled = undefined,
.stall_goal = seq_invalid,
};
@@ -74,12 +76,17 @@ pub const Tracker = struct {
};
/// Used by tests.
pub var full_scans: std.atomic.Value(usize) = .init(0);
pub var full_scans = std.atomic.Value(usize).init(0);
/// Scan all CPUs and return the minimum observed value.
/// If `should_wait` is true, this will spinloop (or block)
/// until all CPUs have reached the given goal.
fn scan(dom: *Domain, goal: Sequence, clock: Clock, should_wait: bool) Sequence {
fn scan(
dom: *Domain,
goal: Sequence,
clock: Clock,
should_wait: bool,
) Sequence {
rtl.barrier.fence(.seq_cst);
const clk_write = clock.write_seq.raw;
@@ -92,7 +99,6 @@ fn scan(dom: *Domain, goal: Sequence, clock: Clock, should_wait: bool) Sequence
for (0..ke.ncpus) |i| {
const cpu = &dom.cpus[i];
var seq = cpu.current_seq.load(.monotonic);
while (seq != seq_invalid) {
@@ -169,8 +175,8 @@ fn scan(dom: *Domain, goal: Sequence, clock: Clock, should_wait: bool) Sequence
.{ .shared = &cpu.stalled },
.Exclusive,
);
ke.ipl.lower(ipl);
ke.ipl.lower(ipl);
seq = cpu.stall_seq.load(.monotonic);
}
@@ -260,7 +266,9 @@ pub fn advance(dom: *Domain) Sequence {
return dom.clock.write_seq.fetchAdd(seq_incr, .release) + seq_incr;
}
/// Pretend-advance the write sequence and return the value for use as a wait goal.
/// Pretend-advance the write sequence and return the value for use as a wait
/// goal.
///
/// It is guaranteed that all previous memory writes made by
/// the calling thread are visible.
/// The global clock isn't advanced unlike `advance`, it is only when commit()
@@ -303,7 +311,7 @@ fn enter_internal(dom: *Domain) Sequence {
// on a full memory barrier, as it is guaranteed that the CPU was treated as
// inactive and cannot possibly hold a reference to anything the poll
// declared reclaimable. See scan().
std.debug.assert(cpu.current_seq.load(.monotonic) == seq_invalid);
assert(cpu.current_seq.load(.monotonic) == seq_invalid);
const wr_seq = dom.clock.write_seq.load(.monotonic);
@@ -420,7 +428,7 @@ pub fn exit_preempt(dom: *Domain, tracker: *Tracker) void {
return;
}
const cpu = tracker.cpu.?;
const cpu = tracker.cpu orelse unreachable;
// We got preempted.
cpu.stall_lock.acquire_no_ipl();
@@ -449,7 +457,7 @@ pub fn exit_preempt(dom: *Domain, tracker: *Tracker) void {
}
if (wake and turnstile != null) {
const ts = turnstile.?;
const ts = turnstile orelse unreachable;
var waiters: rtl.List = undefined;
kep.turnstile.signal(ts, .Exclusive, ts.waiters, null, &waiters);
kep.turnstile.exit(cpu, turnstile);
+41 -31
View File
@@ -1,3 +1,5 @@
//! Kernel spinlocks.
const std = @import("std");
const rtl = @import("rtl");
const config = @import("config");
@@ -8,19 +10,23 @@ const kep = ke.private;
fn LockTemplate(comptime T: type) type {
comptime {
if (!@hasDecl(T, "acquire_no_ipl")) @compileError("lock backend must implement acquire_no_ipl");
if (!@hasDecl(T, "release_no_ipl")) @compileError("lock backend must implement release_no_ipl");
if (!@hasDecl(T, "try_acquire_no_ipl")) @compileError("lock backend must implement try_acquire_no_ipl");
if (!@hasDecl(T, "is_locked")) @compileError("lock backend must implement is_locked");
if (!@hasDecl(T, "acquire_no_ipl"))
@compileError("lock backend must implement acquire_no_ipl");
if (!@hasDecl(T, "release_no_ipl"))
@compileError("lock backend must implement release_no_ipl");
if (!@hasDecl(T, "try_acquire_no_ipl"))
@compileError("lock backend must implement try_acquire_no_ipl");
if (!@hasDecl(T, "is_locked"))
@compileError("lock backend must implement is_locked");
}
return struct {
inner: T,
wd: kep.warden.LockData = undefined,
const Self = @This();
const Lock = @This();
pub fn init(comptime class: []const u8) Self {
pub fn init(comptime class: []const u8) Lock {
if (config.warden) {
const cl = kep.warden.find_or_create_lock_class(class);
return .{
@@ -32,26 +38,28 @@ fn LockTemplate(comptime T: type) type {
}
/// Acquire the lock, raising IPL to `ipl`. Returns the previous IPL.
pub fn acquire_at(self: *Self, ipl: ke.Ipl) ke.Ipl {
pub fn acquire_at(self: *Lock, ipl: ke.Ipl) ke.Ipl {
const old_ipl = ke.ipl.raise(ipl);
self.acquire_no_ipl();
return old_ipl;
}
/// Acquire the lock, raising IPL to `.Dispatch`. Returns the previous IPL.
pub fn acquire(self: *Self) ke.Ipl {
/// Acquire the lock, raising IPL to `.Dispatch`.
/// Returns the previous IPL.
pub fn acquire(self: *Lock) ke.Ipl {
return self.acquire_at(.Dispatch);
}
/// Release the lock and restore IPL to `ipl`.
pub fn release(self: *Self, ipl: ke.Ipl) void {
pub fn release(self: *Lock, ipl: ke.Ipl) void {
self.release_no_ipl();
ke.ipl.lower(ipl);
}
/// Try to acquire the lock at IPL `.Dispatch`.
/// Returns the previous IPL on success, null if the lock is already held.
pub fn try_acquire(self: *Self) ?ke.Ipl {
/// Returns the previous IPL on success,
/// null if the lock is already held.
pub fn try_acquire(self: *Lock) ?ke.Ipl {
const old_ipl = ke.ipl.raise(.Dispatch);
if (self.inner.try_acquire_no_ipl()) {
if (config.warden) {
@@ -66,7 +74,7 @@ fn LockTemplate(comptime T: type) type {
}
/// Acquire the lock without changing the IPL.
pub fn acquire_no_ipl(self: *Self) void {
pub fn acquire_no_ipl(self: *Lock) void {
if (config.warden) {
kep.warden.check(self.wd, .Spin);
}
@@ -79,7 +87,7 @@ fn LockTemplate(comptime T: type) type {
}
/// Release the lock without changing the IPL.
pub fn release_no_ipl(self: *Self) void {
pub fn release_no_ipl(self: *Lock) void {
if (config.warden) {
kep.warden.released(self, .Spin);
}
@@ -89,7 +97,7 @@ fn LockTemplate(comptime T: type) type {
/// Try to acquire the lock without changing the IPL.
/// Returns true if the lock was acquired.
pub fn try_acquire_no_ipl(self: *Self) bool {
pub fn try_acquire_no_ipl(self: *Lock) bool {
const ret = self.inner.try_acquire_no_ipl();
if (ret and config.warden) {
@@ -100,7 +108,7 @@ fn LockTemplate(comptime T: type) type {
}
/// Returns true if the lock is currently held.
pub fn is_locked(self: *Self) bool {
pub fn is_locked(self: *Lock) bool {
return self.inner.is_locked();
}
};
@@ -110,16 +118,16 @@ fn LockTemplate(comptime T: type) type {
pub const SpinLock = LockTemplate(struct {
locked: std.atomic.Value(u8),
const Self = @This();
const Lock = @This();
pub fn init() Self {
pub fn init() Lock {
return .{
.locked = .init(0),
.locked = std.atomic.Value(u8).init(0),
};
}
/// Acquire the lock without changing the IPL.
pub fn acquire_no_ipl(self: *Self) void {
pub fn acquire_no_ipl(self: *Lock) void {
while (true) {
if (self.locked.cmpxchgWeak(0, 1, .acquire, .monotonic) == null)
return;
@@ -131,16 +139,16 @@ pub const SpinLock = LockTemplate(struct {
}
/// Release the lock without changing the IPL.
pub fn release_no_ipl(self: *Self) void {
pub fn release_no_ipl(self: *Lock) void {
self.locked.store(0, .release);
}
/// Try to acquire the lock. Return true if lock was acquired.
pub fn try_acquire_no_ipl(self: *Self) bool {
pub fn try_acquire_no_ipl(self: *Lock) bool {
return self.locked.cmpxchgStrong(0, 1, .acquire, .monotonic) == null;
}
pub fn is_locked(self: *Self) bool {
pub fn is_locked(self: *Lock) bool {
return self.locked.load(.monotonic) == 1;
}
});
@@ -199,15 +207,15 @@ pub const QSpinLock = LockTemplate(struct {
const pending_val: u32 = 0x00000100;
const pending_loops = 512;
const Self = @This();
const Lock = @This();
data: Data,
pub fn init() Self {
pub fn init() Lock {
return .{ .data = .{ .val = std.atomic.Value(u32).init(0) } };
}
pub fn acquire_no_ipl(self: *Self) void {
pub fn acquire_no_ipl(self: *Lock) void {
// Fast path: try to acquire the lock.
var v = self.data.val.cmpxchgStrong(
0,
@@ -262,11 +270,11 @@ pub const QSpinLock = LockTemplate(struct {
self.data.split.locked_pending.store(1, .release);
}
pub fn release_no_ipl(self: *Self) void {
pub fn release_no_ipl(self: *Lock) void {
self.data.low_word.locked.store(0, .release);
}
pub fn try_acquire_no_ipl(self: *Self) bool {
pub fn try_acquire_no_ipl(self: *Lock) bool {
return self.data.val.cmpxchgStrong(
0,
locked_val,
@@ -275,11 +283,11 @@ pub const QSpinLock = LockTemplate(struct {
) == null;
}
pub fn is_locked(self: *Self) bool {
pub fn is_locked(self: *Lock) bool {
return self.data.low_word.locked.load(.monotonic) != 0;
}
fn queue(self: *Self) void {
fn queue(self: *Lock) void {
// Claim an index from our CPU.
const idx = pcpu.local().curr_idx;
pcpu.local().curr_idx += 1;
@@ -369,6 +377,8 @@ pub const QSpinLock = LockTemplate(struct {
std.atomic.spinLoopHint();
}
next.?.locked.store(1, .release);
const n = next orelse unreachable;
n.locked.store(1, .release);
}
});
+27 -24
View File
@@ -1,4 +1,5 @@
const std = @import("std");
//! Kernel thread management.
const rtl = @import("rtl");
const config = @import("config");
@@ -6,9 +7,10 @@ const r = @import("root");
const ke = r.ke;
const kep = ke.private;
pub const Priority = enum(u8) {
const Self = @This();
const std = @import("std");
const assert = std.debug.assert;
pub const Priority = enum(u8) {
/// Reserved for the idle thread.
IdleThread = 0,
/// Background kernel work.
@@ -42,18 +44,18 @@ pub const Priority = enum(u8) {
// The low and top 20 priorities from the batch range are reserved
// for nice.
pub const cpu_range = @intFromEnum(Self.HighBatch) - (nice_max * 2) - 1;
pub const cpu_range = @backingInt(Priority.HighBatch) - (nice_max * 2) - 1;
pub fn class_from_prio(prio: u8) Class {
if (prio >= @intFromEnum(Self.LowRealtime) and
prio <= @intFromEnum(Self.HighRealtime))
if (prio >= @backingInt(Priority.LowRealtime) and
prio <= @backingInt(Priority.HighRealtime))
return .Realtime;
if (prio >= @intFromEnum(Self.LowBatch) and
prio < @intFromEnum(Self.LowRealtime))
if (prio >= @backingInt(Priority.LowBatch) and
prio < @backingInt(Priority.LowRealtime))
return .Timeshare;
if (prio <= @intFromEnum(Self.Idle))
if (prio <= @backingInt(Priority.Idle))
return .Idle;
unreachable;
@@ -110,7 +112,7 @@ pub const Thread = struct {
lock: ke.SpinLock,
/// Niceness value.
nice: i8,
/// Effective priority value of the thread: `max(base_priority, inherited_prio)`.
/// Effective priority value of the thread: `max(base, inherited)`.
priority: u8,
/// Base priority value of the thread.
base_priority: u8,
@@ -188,17 +190,17 @@ pub const Thread = struct {
.context = .init_with_stack(opts.stack),
.lock = ke.SpinLock.init("thread"),
.nice = 0,
.priority = @intFromEnum(opts.priority),
.base_priority = @intFromEnum(opts.priority),
.priority = @backingInt(opts.priority),
.base_priority = @backingInt(opts.priority),
.inherited_prio = 0,
.pinned = false,
.state = .init(.Ready),
.state = std.atomic.Value(State).init(.Ready),
.runq_link = .{},
.last_cpu = null,
.cpu = null,
.runq = null,
.runq_idx = 0,
.wait_status = .init(.Satisfied),
.wait_status = std.atomic.Value(kep.wait.Status).init(.Satisfied),
.inner_waitblocks = undefined,
.waitblocks = &thread.inner_waitblocks,
.wait_reason = null,
@@ -213,11 +215,11 @@ pub const Thread = struct {
.queue = null,
.queue_item = null,
.continuation = opts.entry,
.switching = .init(false),
.switching = std.atomic.Value(bool).init(false),
.avg = .{},
.acct = .{},
.smr_sections = undefined,
.hard_affinity = .init(true),
.hard_affinity = ke.CpuMask.init(true),
.warden_data = .{},
};
@@ -249,8 +251,8 @@ pub const Thread = struct {
}
pub fn is_interactive(self: *Thread) bool {
return self.priority >= @intFromEnum(Priority.LowInteractive) and
self.priority <= @intFromEnum(Priority.HighInteractive);
return self.priority >= @backingInt(Priority.LowInteractive) and
self.priority <= @backingInt(Priority.HighInteractive);
}
pub fn can_relinquish_stack(self: *Thread) bool {
@@ -309,7 +311,7 @@ pub fn exit() void {
/// Return the currently running thread.
pub fn current() *ke.Thread {
return kep.sched.percpu.local().current_thread.?;
return kep.sched.percpu.local().current_thread orelse unreachable;
}
// -- Stack allocation and continuations ---------------------------------------
@@ -344,7 +346,8 @@ const Cpu = struct {
/// Number of excess stacks allowed in the global stack depot before trimming.
/// Note that this is in addition to the global minimum of `ncpus` stacks.
/// Keep this low for minimal memory overhead, but potentially increased latency.
/// Keep this low for minimal memory overhead, but potentially
/// increased latency.
const excess_stacks = ke.Tunable(u32, 1, "ke.thread.excess_stacks");
var global_depot_lock: ke.SpinLock = undefined;
@@ -355,7 +358,7 @@ const percpu_count_max = 2;
const wma_unit = 256;
pub var thread_stack_queue: rtl.HandoffList = .init(stack_activation);
pub var thread_stack_queue = rtl.HandoffList.init(stack_activation);
inline fn wma_mix(old: u32, new: u32) u32 {
// Keep 75% of old sample and 25% of new.
@@ -380,14 +383,14 @@ pub fn init() void {
pub fn call_continuation(ptr: ?*anyopaque) noreturn {
const td: *ke.Thread = @ptrCast(@alignCast(ptr));
std.debug.assert(td.continuation != null);
assert(td.continuation != null);
// Clean things that were set up by the wait (e.g timeout).
_ = kep.wait.post_wait(td) catch {
// Don't care about the status.
};
const cont = td.continuation.?;
const cont = td.continuation orelse unreachable;
td.continuation = null;
kep.impl.call_continuation(&td.context, cont);
@@ -466,7 +469,7 @@ pub fn stack_cache_update() ?*Stack {
// If we have more than `excess_stacks` sitting unused, then trim.
// Ensure there are still `ncpus` stacks sitting in the depot.
if (excess > excess_stacks.load()) {
std.debug.assert(global_depot.count >= excess);
assert(global_depot.count >= excess);
// We want every CPU to have at least one stack sitting in the depot.
const floor: u32 = @truncate(ke.ncpus);
+2 -5
View File
@@ -43,9 +43,6 @@ var state = rtl.SeqLock(State).init(.{
});
// Convert a TimeCounter's count to nanoseconds.
// For 64-bit counters (`mask == maxInt(u64)`), uses a fast multiply-shift path
// via precomputed `p` and `n` to avoid 128-bit division at runtime.
// Falls back to a division-based conversion for narrower counters.
fn ticks_to_ns(tc: *TimeCounter, count: u64) u64 {
if (tc.p != 0) {
const val: u128 = @as(u128, @intCast(count)) * tc.n;
@@ -132,8 +129,8 @@ pub fn update_overflow() void {
const cur_count = tc.read_count() & tc.mask;
if (s.initial_count > cur_count) {
// Counter has wrapped, use wrapping arithmetic to figure out by how much,
// then fold it into the offset and reset `initial_count`.
// Counter has wrapped, use wrapping arithmetic to figure out by how
// much, then fold it into the offset and reset `initial_count`.
const overflowed_by = (cur_count -% s.initial_count) & tc.mask;
state.store(.{
.initial_count = cur_count,
+8 -6
View File
@@ -10,9 +10,11 @@ const pl = r.pl;
const std = @import("std");
const assert = std.debug.assert;
const TimerHeap = rtl.PairingHeap(.Min, kep.timer.cmp_timer);
const PerCpu = struct {
/// Heap of pending timers on this CPU.
timers: rtl.PairingHeap(.min, kep.timer.cmp_timer),
timers: TimerHeap,
/// Lock over the timer heap.
lock: ke.SpinLock,
dpc: ke.Dpc,
@@ -44,8 +46,8 @@ pub const Timer = struct {
pub fn init(self: *Timer) void {
self.* = .{
.hdr = undefined,
.state = .init(.Stopped),
.deadline = .init(0),
.state = std.atomic.Value(State).init(.Stopped),
.deadline = r.Nanoseconds.init(0),
.dpc = null,
.node = .{},
.cpu = null,
@@ -56,7 +58,7 @@ pub const Timer = struct {
};
const percpu = ke.CpuLocal(PerCpu, .{
.timers = .init(),
.timers = TimerHeap.init(),
.lock = undefined,
.dpc = .init(handle_expiry),
});
@@ -127,7 +129,7 @@ pub fn cancel(timer: *Timer) void {
}
},
.Pending => {
const cpu = timer.cpu.?;
const cpu = timer.cpu orelse unreachable;
cpu.lock.acquire_no_ipl();
// Re-check under the lock.
@@ -170,7 +172,7 @@ pub fn clock() void {
// Called in a DPC when a timer has expired.
fn handle_expiry(_: *ke.Dpc, _: ?*anyopaque) void {
std.debug.assert(kep.ipl.current() == .Dispatch);
assert(kep.ipl.current() == .Dispatch);
const cpu = percpu.local();
while (true) {
+3 -3
View File
@@ -53,12 +53,12 @@ fn store_int(comptime T: type, addr: *anyopaque, value: anytype) bool {
}
fn apply_num(
comptime ValueT: type,
comptime VT: type,
cmdline: []const u8,
entry: *const Entry,
info: std.builtin.Type.Int,
) void {
const value = rtl.cmdline.get_number(ValueT, cmdline, entry.name) catch |e| {
const value = rtl.cmdline.get_number(VT, cmdline, entry.name) catch |e| {
if (e == error.Format) {
std.log.warn(
"Invalid number value for '{s}', falling back.",
@@ -68,7 +68,7 @@ fn apply_num(
return;
};
const sign = @typeInfo(ValueT).int.signedness;
const sign = @typeInfo(VT).int.signedness;
_ = switch (info.bits) {
8 => store_int(@Int(sign, 8), entry.address, value),
+25 -19
View File
@@ -22,9 +22,10 @@
//! a lock, which might get preempted by another medium-priority thread.
//!
//! Priority inheritance is achieved through turnstiles by walking a
//! given turnstile's owner chain and willing the priority of the highest priority
//! waiter, i.e it will go through turnstile.owner.turnstile.owner...
//! until the end of the chain, inheriting the waiter's priority along the way.
//! given turnstile's owner chain and willing the priority of the
//! highest priority waiter, i.e it will go through
//! turnstile.owner.turnstile.owner... until the end of the chain,
//! inheriting the waiter's priority along the way.
//!
//! ## Locking
//! ----------
@@ -41,15 +42,17 @@
//!
//! ## Resources
//! ------------
//! See the original Illumos implementation, the Solaris book or FreeBSD book for
//! a more in-depth description.
//! See the original Illumos implementation, the Solaris book or FreeBSD book
//! for a more in-depth description.
//! I have also written more about turnstiles here:
//! https://rdmsr.github.io/writing/turnstiles/.
//! https://gregoire.dev/writing/turnstiles/.
const ke = @import("root").ke;
const kep = ke.private;
const rtl = @import("rtl");
const std = @import("std");
const assert = std.debug.assert;
const num_chains = 128;
const hash_mask = num_chains - 1;
@@ -229,7 +232,8 @@ fn update_prio(td: *ke.Thread) void {
/// `to.lock` and the owning turnstile's lock.
fn donate_to(boost: *Boost, to: *ke.Thread, pri: u8) void {
// A lend only ever raises.
if (boost.donated != null and pri <= boost.donated.?) return;
if (boost.donated != null and pri <= boost.donated orelse unreachable)
return;
if (boost.donated == null) to.turnstiles_owned.insert_head(&boost.link);
boost.donated = pri;
@@ -306,7 +310,7 @@ fn propagate(curtd: *ke.Thread) void {
// We hold `thread.lock` and `root.lock`.
const obj = thread.waiting_on orelse break;
std.debug.assert(thread.waiting_on == obj);
assert(thread.waiting_on == obj);
const ts = thread.turnstile;
@@ -319,8 +323,8 @@ fn propagate(curtd: *ke.Thread) void {
// on the same turnstile.
if (!ts.lock.try_acquire_no_ipl()) {
// The turnstile could not be acquired, drop everything and
// restart the walk from the start. Priority inheritance is idem-
// potent so there is no issue in applying it many times.
// restart the walk from the start. Priority inheritance is
// idempotent so there is no issue in applying it many times.
thread.lock.release_no_ipl();
if (root) |ro| ro.lock.release_no_ipl();
@@ -443,9 +447,9 @@ pub fn block(
const ipl = ke.ipl.current();
var ts = turnstile;
const queue_idx = @intFromEnum(queue);
const queue_idx = @backingInt(queue);
std.debug.assert(chain.lock.is_locked());
assert(chain.lock.is_locked());
if (ts) |turn| {
// Another thread already donated its turnstile,
@@ -500,7 +504,8 @@ pub fn block(
_ = ke.ipl.raise(ipl);
}
/// Signal the end of a turnstile-backed wait and return a list of waiters to wake.
/// Signal the end of a turnstile-backed wait and return a list of waiters
/// to wake.
/// Do hand-off to new_owner if specified.
/// The locks acquired by `lookup` are still held on return.
pub fn signal(
@@ -510,7 +515,7 @@ pub fn signal(
new_owner: ?*ke.Thread,
waiters: *rtl.List,
) void {
const queue_idx = @intFromEnum(queue);
const queue_idx = @backingInt(queue);
// Revoke any priority we gave to the owner(s).
revoke(ts);
@@ -546,8 +551,9 @@ pub fn signal(
pub fn wakeup(waiters: *rtl.List) void {
// Now wake all the waiters, this ensures that the turnstile and chain lock
// hold times stay low.
// Also, if we did this *before* unlocking `ts`, there is no guarantee that it
// is still alive, as the last waiter could've woken up, exited, and freed it.
// Also, if we did this *before* unlocking `ts`, there is no guarantee that
// it is still alive, as the last waiter could've woken up, exited,
// and freed it.
// Instead of special casing it, let's just do the wakeup in a nicer
// environment here.
var it = waiters.iterator();
@@ -562,9 +568,9 @@ pub fn wakeup(waiters: *rtl.List) void {
fn dequeue(ts: *Turnstile, td: *ke.Thread) *Waiter {
td.lock.acquire_no_ipl();
std.debug.assert(td.turnstile == ts);
std.debug.assert(td.turnstile_waiter != null);
std.debug.assert(!ts.queues[0].is_empty() or !ts.queues[1].is_empty());
assert(td.turnstile == ts);
assert(td.turnstile_waiter != null);
assert(!ts.queues[0].is_empty() or !ts.queues[1].is_empty());
const waiter = td.turnstile_waiter.?;
waiter.link.remove();
+4 -4
View File
@@ -133,7 +133,7 @@ pub fn wait_any(
const ipl = ke.ipl.raise(.Dispatch);
defer ke.ipl.lower(ipl);
const curtd = kep.sched.percpu.local().current_thread.?;
const curtd = ke.thread.current();
const obj_count = objects.len;
const has_timeout = opts.timeout != null;
const total_count = obj_count + @intFromBool(has_timeout);
@@ -264,7 +264,7 @@ pub fn wait_any(
/// Satisfy a wait on an object.
pub fn satisfy_wait(obj: *DispatchHeader) void {
std.debug.assert(obj.lock.is_locked());
assert(obj.lock.is_locked());
const all = obj.type == .Notification;
@@ -274,8 +274,8 @@ pub fn satisfy_wait(obj: *DispatchHeader) void {
var wb: *WaitBlock = @fieldParentPtr("link", obj.waitblocks.first());
var td = wb.thread;
std.debug.assert(wb.status == .Active);
std.debug.assert(wb.object == obj);
assert(wb.status == .Active);
assert(wb.object == obj);
// Remove it.
wb.link.remove();
+10 -6
View File
@@ -124,20 +124,24 @@ pub fn find_or_create_lock_class(name: []const u8) ?*Class {
const ipl = graph_lock.acquire_at(.High);
defer graph_lock.release(ipl);
var class, const bucket = find_lock_class(name);
const found, const bucket = find_lock_class(name);
if (class != null) {
return class;
if (found != null) {
return found;
}
assert(num_active_classes < num_classes);
// Get a new spot for our class.
class = &classes[num_active_classes];
class.?.name = name;
const class = &classes[num_active_classes];
class.name = name;
// Insert it so that we can find it by name.
bucket.?.insert(&class.?.entry) catch unreachable;
bucket.?.insert(&class.entry) catch {
// This would only fail if the lock class already existed.
// Since this was accounted for earlier, the error can never happen.
unreachable;
};
num_active_classes += 1;
+7 -1
View File
@@ -1,3 +1,5 @@
//! amd64-specific loader code.
const amd64 = @import("arch");
const r = @import("root");
const pmap = @import("../pmap.zig");
@@ -75,7 +77,11 @@ pub inline fn make_table_pte(pa: usize) Pte {
};
}
pub inline fn make_leaf_pte(pa: usize, flags: r.mem.MapFlags, level: usize) Pte {
pub inline fn make_leaf_pte(
pa: usize,
flags: r.mem.MapFlags,
level: usize,
) Pte {
return Pte{
.present = true,
.writable = flags.write,
+86 -48
View File
@@ -1,56 +1,70 @@
//! Limine-specific code for the loader.
const std = @import("std");
const limine = @import("limine.zig");
const r = @import("root");
pub export var limine_base_revision: [3]u64 linksection(".limine_requests") = limine.base_revision(6);
pub export var base_revision: [3]u64 linksection(".limine_requests") =
limine.base_revision(6);
pub export var framebuffer_request: limine.FramebufferRequest linksection(".limine_requests") = .{
pub export var framebuffer_request linksection(".limine_requests") =
limine.FramebufferRequest{
.id = limine.framebuffer_request_id,
.revision = 0,
.response = null,
};
};
pub export var limine_requests_start_marker: [4]u64 linksection(".limine_requests_start") = limine.requests_start_marker;
pub export var limine_requests_end_marker: [2]u64 linksection(".limine_requests_end") = limine.requests_end_marker;
pub export var start_marker: [4]u64 linksection(".limine_requests_start") =
limine.requests_start_marker;
pub export var memmap_request: limine.MemmapRequest linksection(".limine_requests") = .{
pub export var end_marker: [2]u64 linksection(".limine_requests_end") =
limine.requests_end_marker;
pub export var memmap_request linksection(".limine_requests") =
limine.MemmapRequest{
.id = limine.memmap_request_id,
.revision = 0,
.response = null,
};
};
pub export var rsdp_request: limine.RsdpRequest linksection(".limine_requests") = .{
pub export var rsdp_request linksection(".limine_requests") =
limine.RsdpRequest{
.id = limine.rsdp_request_id,
.revision = 0,
.response = null,
};
};
pub export var cmdline_request: limine.CmdlineRequest linksection(".limine_requests") = .{
pub export var cmdline_request linksection(".limine_requests") =
limine.CmdlineRequest{
.id = limine.cmdline_request_id,
.revision = 0,
.response = null,
};
};
pub export var hhdm_request: limine.HHDMRequest linksection(".limine_requests") = .{
pub export var hhdm_request linksection(".limine_requests") =
limine.HHDMRequest{
.id = limine.hhdm_request_id,
.revision = 0,
.response = null,
};
};
pub export var kernel_request: limine.ExecutableAddressRequest linksection(".limine_requests") = .{
pub export var kernel_request linksection(".limine_requests") =
limine.ExecutableAddressRequest{
.id = limine.executable_address_request_id,
.revision = 0,
.response = null,
};
};
pub export var module_request: limine.ModuleRequest linksection(".limine_requests") = .{
pub export var module_request linksection(".limine_requests") =
limine.ModuleRequest{
.id = limine.module_request_id,
.revision = 0,
.response = null,
};
};
var hhdm_offset: usize = 0;
pub fn p2v(pa: usize) usize {
return hhdm_request.response.?.offset + pa;
return hhdm_offset + pa;
}
pub fn get_image_layout() r.ImageLayout {
@@ -64,36 +78,12 @@ pub fn get_image_layout() r.ImageLayout {
var cmdline: [256]u8 = undefined;
export fn loader_entry() callconv(.c) void {
var kernel: ?*anyopaque = null;
var module_start: usize = 0;
var module_end: usize = 0;
if (module_request.response) |resp| {
const modules = resp.modules.?[0..resp.module_count];
for (modules) |mod| {
if (std.mem.eql(u8, std.mem.span(mod.string), "kernel")) {
kernel = mod.address;
const phys = @intFromPtr(mod.address) -
hhdm_request.response.?.offset;
const size: usize = @intCast(mod.size);
module_start = std.mem.alignForward(usize, phys, r.page_size);
module_end = std.mem.alignBackward(usize, phys + size, r.page_size);
break;
}
}
}
if (kernel == null) {
@panic("loader: kernel not found");
}
const mmap = memmap_request.response.?;
fn build_mmap(module_start: usize, module_end: usize) void {
const mmap = memmap_request.response orelse unreachable;
const entries = mmap.entries orelse unreachable;
for (0..mmap.entry_count) |i| {
const entry = mmap.entries.?[i];
const entry = entries[i];
if (entry.type == .KernelAndModules) {
const end = entry.base + entry.length;
@@ -112,10 +102,18 @@ export fn loader_entry() callconv(.c) void {
// anyway.
if (reclaim_start < reclaim_end) {
if (entry.base < reclaim_start) {
r.mem.add_entry(entry.base, reclaim_start - entry.base, .Reserved);
r.mem.add_entry(
entry.base,
reclaim_start - entry.base,
.Reserved,
);
}
r.mem.add_entry(reclaim_start, reclaim_end - reclaim_start, .LoaderReclaimable);
r.mem.add_entry(
reclaim_start,
reclaim_end - reclaim_start,
.LoaderReclaimable,
);
if (reclaim_end < end) {
r.mem.add_entry(reclaim_end, end - reclaim_end, .Reserved);
@@ -132,6 +130,46 @@ export fn loader_entry() callconv(.c) void {
else => .Reserved,
});
}
}
export fn loader_entry() callconv(.c) void {
var kernel: ?*anyopaque = null;
var module_start: usize = 0;
var module_end: usize = 0;
const hhdm_resp = hhdm_request.response orelse unreachable;
hhdm_offset = hhdm_resp.offset;
if (module_request.response) |resp| {
const limine_mods = resp.modules orelse unreachable;
const modules = limine_mods[0..resp.module_count];
for (modules) |mod| {
if (std.mem.eql(u8, std.mem.span(mod.string), "kernel")) {
kernel = mod.address;
const phys = @intFromPtr(mod.address) - hhdm_offset;
const size: usize = @intCast(mod.size);
module_start = std.mem.alignForward(
usize,
phys,
r.page_size,
);
module_end = std.mem.alignBackward(
usize,
phys + size,
r.page_size,
);
break;
}
}
}
if (kernel == null) {
@panic("loader: kernel not found");
}
build_mmap(module_start, module_end);
if (framebuffer_request.response) |resp| {
const fb = resp.framebuffers[0];
+16 -4
View File
@@ -4,7 +4,11 @@ const std = @import("std");
const Entry = r.BootInfo.MemMap.Entry;
const stack_size = 1024 * 16; // should be enough
extern fn jump_to_kernel(loader_info: usize, entry: usize, stack_top: usize) void;
extern fn jump_to_kernel(
loader_info: usize,
entry: usize,
stack_top: usize,
) void;
fn load_kernel(kernel: *anyopaque) usize {
const elf: *std.elf.Elf64.Ehdr = @ptrCast(@alignCast(kernel));
@@ -22,12 +26,18 @@ fn load_kernel(kernel: *anyopaque) usize {
@panic("loader: invalid ELF type (expected ET_EXEC)");
}
var phdr: *std.elf.Elf64.Phdr = @ptrFromInt(@intFromPtr(kernel) + elf.phoff);
var phdr: *std.elf.Elf64.Phdr = @ptrFromInt(
@intFromPtr(kernel) + elf.phoff,
);
for (0..elf.phnum) |_| {
switch (phdr.type) {
.LOAD => {
const as_pages = std.mem.alignForward(usize, phdr.memsz, r.page_size);
const as_pages = std.mem.alignForward(
usize,
phdr.memsz,
r.page_size,
);
const npages = as_pages / r.page_size;
var filesz_rem = phdr.filesz;
@@ -43,7 +53,9 @@ fn load_kernel(kernel: *anyopaque) usize {
const file_remaining = @min(filesz_rem, r.page_size);
const off: [*]u8 = @ptrFromInt(@intFromPtr(kernel) + phdr.offset + i * r.page_size);
const off: [*]u8 = @ptrFromInt(
@intFromPtr(kernel) + phdr.offset + i * r.page_size,
);
const source = off[0..file_remaining];
const va: [*]u8 = @ptrFromInt(addr);
const dest = va[0..file_remaining];
+31 -10
View File
@@ -100,7 +100,11 @@ pub fn alloc(size: usize) usize {
return va;
}
pub fn add_entry(base: usize, size: usize, @"type": r.BootInfo.MemMap.Entry.Type) void {
pub fn add_entry(
base: usize,
size: usize,
@"type": r.BootInfo.MemMap.Entry.Type,
) void {
if (memory_map.entry_count == memory_map.entries.len) {
@panic("loader: memory map is full");
}
@@ -190,13 +194,17 @@ fn map_self() void {
},
);
// Now go through every usable entry and map to the HHDM every part that isnt covered by [0, hhdm_minimum_max_address).
// We can't blindly map until the maximum usable physical address because on some CPUs this might cause MCEs.
// See https://github.com/torvalds/linux/commit/66520ebc2df3fe52eb4792f8101fac573b766baf
// Now go through every usable entry and map to the HHDM every part that
// isnt covered by [0, hhdm_minimum_max_address).
// We can't blindly map until the maximum usable physical address because
// on some CPUs this might cause MCEs.
// See Linux commit 66520ebc2df3fe52eb4792f8101fac573b766baf
for (0..memory_map.entry_count) |i| {
const entry = memory_map.entries[i];
if ((entry.type != .Free and entry.type != .LoaderReclaimable) or entry.size < r.page_size) {
if ((entry.type != .Free and entry.type != .LoaderReclaimable) or
entry.size < r.page_size)
{
continue;
}
@@ -233,20 +241,33 @@ fn map_pfndb() void {
for (0..memory_map.entry_count) |i| {
const entry = memory_map.entries[i];
if ((entry.type != .Free and entry.type != .LoaderReclaimable) or entry.size < r.page_size) {
if ((entry.type != .Free and entry.type != .LoaderReclaimable) or
entry.size < r.page_size)
{
continue;
}
const npages = std.math.divCeil(usize, entry.size, r.page_size) catch unreachable;
const npages = std.math.divCeil(usize, entry.size, r.page_size) catch
unreachable;
// 1. Calculate the exact virtual address range needed for this region's page structs.
// 1. Calculate the exact virtual address range needed for this
// region's page structs.
const start_pfn: usize = entry.base / r.page_size;
const exact_start = memory_layout.pfndb + (start_pfn * size);
const exact_end = exact_start + (npages * size);
// 2. Ensure the addresses are aligned on page boundaries.
const map_start = std.mem.alignBackward(usize, exact_start, r.page_size);
const map_end = std.mem.alignForward(usize, exact_end, r.page_size);
const map_start = std.mem.alignBackward(
usize,
exact_start,
r.page_size,
);
const map_end = std.mem.alignForward(
usize,
exact_end,
r.page_size,
);
// 3. Map the virtual pages.
pagemap.map_range_allocating(
+48 -17
View File
@@ -34,8 +34,9 @@ const ContiguousSource = struct {
}
};
/// A mapping source that allocates physical pages on demand. Each call to `next`
/// returns a single page, so this is intended to be consumed by `map_from` in a loop until the entire range is mapped.
/// A mapping source that allocates physical pages on demand. Each call to
/// `next` returns a single page, so this is intended to be consumed by
/// `map_from` in a loop until the entire range is mapped.
const AllocatingSource = struct {
flags: mem.MapFlags,
@@ -62,8 +63,6 @@ fn index_for_level(va: usize, level: usize) usize {
}
pub const PMap = struct {
const Self = @This();
const num_levels = r.arch.levels.len;
const entries_per_table = 512;
const index_mask = entries_per_table - 1;
@@ -78,7 +77,7 @@ pub const PMap = struct {
return is_leaf_level(level) and r.arch.is_leaf_level_enabled(level);
}
fn cursor(self: *Self, va: usize) Cursor {
fn cursor(self: *PMap, va: usize) Cursor {
var c = Cursor{
.pmap = self,
.va = va,
@@ -90,11 +89,11 @@ pub const PMap = struct {
return c;
}
pub fn activate(self: *Self) void {
pub fn activate(self: *PMap) void {
r.arch.activate(self.root_pa);
}
pub fn map_from(self: *Self, va: usize, size: usize, source: anytype) void {
pub fn map_from(self: *PMap, va: usize, size: usize, source: anytype) void {
var c = self.cursor(va);
var remain = size;
@@ -105,8 +104,15 @@ pub const PMap = struct {
}
}
/// Map a contiguous virtual address range to a contiguous physical address range.
pub fn map_contiguous_range(self: *Self, va: usize, pa: usize, size: usize, flags: r.mem.MapFlags) void {
/// Map a contiguous virtual address range to a contiguous physical address
/// range.
pub fn map_contiguous_range(
self: *PMap,
va: usize,
pa: usize,
size: usize,
flags: r.mem.MapFlags,
) void {
const src = ContiguousSource{
.flags = flags,
.size = size,
@@ -118,12 +124,22 @@ pub const PMap = struct {
}
/// Map a single virtual page to a physical page.
pub fn map_page(self: *Self, va: usize, pa: usize, flags: r.mem.MapFlags) void {
pub fn map_page(
self: *PMap,
va: usize,
pa: usize,
flags: r.mem.MapFlags,
) void {
self.map_contiguous_range(va, pa, r.page_size, flags);
}
/// Map a virtual address range to physical addresses
pub fn map_range_allocating(self: *Self, va: usize, size: usize, flags: mem.MapFlags) void {
pub fn map_range_allocating(
self: *PMap,
va: usize,
size: usize,
flags: mem.MapFlags,
) void {
const src = AllocatingSource{
.flags = flags,
};
@@ -132,7 +148,7 @@ pub const PMap = struct {
}
pub const Cursor = struct {
pmap: *Self,
pmap: *PMap,
va: usize,
/// Cached pointers to each level's page table, filled as the cursor
/// descends. Only indices in `[0, top_level]` are valid.
@@ -142,7 +158,11 @@ pub const PMap = struct {
/// Reset upward in `advance` when the VA crosses a level boundary.
top_level: usize,
fn walk_down(self: *Cursor, target_level: usize, allocate: bool) error{PageNotMapped}!void {
fn walk_down(
self: *Cursor,
target_level: usize,
allocate: bool,
) error{PageNotMapped}!void {
var current_level = self.top_level;
while (current_level > target_level) {
@@ -156,7 +176,9 @@ pub const PMap = struct {
const new_table_pa = mem.alloc_page();
const table_ptr: [*]r.arch.Pte = @ptrFromInt(r.mem.p2v(new_table_pa));
const table_ptr: [*]r.arch.Pte = @ptrFromInt(
r.mem.p2v(new_table_pa),
);
@memset(table_ptr[0..entries_per_table], r.arch.Pte.zero());
pte = r.arch.make_table_pte(new_table_pa);
@@ -193,14 +215,23 @@ pub const PMap = struct {
@panic("no leaf level can map current alignment/size");
}
pub fn map_range(self: *Cursor, pa: usize, size: usize, flags: mem.MapFlags) void {
pub fn map_range(
self: *Cursor,
pa: usize,
size: usize,
flags: mem.MapFlags,
) void {
var remain = size;
var current_pa = pa;
while (remain > 0) {
const target_level = self.choose_target_level(current_pa, remain);
const target_level = self.choose_target_level(
current_pa,
remain,
);
self.walk_down(target_level, true) catch @panic("walk_down failed during map_range");
self.walk_down(target_level, true) catch
@panic("walk_down failed during map_range");
const table = self.tables[target_level];
table[index_for_level(self.va, target_level)] =
+5 -1
View File
@@ -75,7 +75,11 @@ pub inline fn make_table_pte(pa: usize) Pte {
};
}
pub inline fn make_leaf_pte(pa: usize, flags: r.mem.MapFlags, level: usize) Pte {
pub inline fn make_leaf_pte(
pa: usize,
flags: r.mem.MapFlags,
level: usize,
) Pte {
_ = level;
return Pte{
.present = true,
+7 -2
View File
@@ -21,7 +21,11 @@ const DebugWriter = struct {
};
}
fn drain(_: *std.Io.Writer, data: []const []const u8, _: usize) std.Io.Writer.Error!usize {
fn drain(
_: *std.Io.Writer,
data: []const []const u8,
_: usize,
) std.Io.Writer.Error!usize {
var total_written: usize = 0;
for (data) |slice| {
for (slice) |byte| {
@@ -47,7 +51,8 @@ pub fn log(
// Calculate the length required.
var buf: [256]u8 = undefined;
const written = std.fmt.bufPrint(&buf, scope_str ++ fmt ++ "\n", args) catch return;
const written = std.fmt.bufPrint(&buf, scope_str ++ fmt ++ "\n", args) catch
return;
var writer = DebugWriter.init();
+3 -4
View File
@@ -35,10 +35,10 @@ fn reap_stacks() void {
// Reap all the excess stacks.
var entry: ?*ke.Stack = lst;
while (entry != null) {
const next = entry.?.next;
while (entry) |e| {
const next = e.next;
mm.heap.free(
(@intFromPtr(entry) + @sizeOf(ke.Stack)) -
(@intFromPtr(e) + @sizeOf(ke.Stack)) -
ps.thread.kernel_thread_stack_size,
ps.thread.kernel_thread_stack_size,
);
@@ -86,7 +86,6 @@ fn balance_manager(_: ?*anyopaque) void {
if (stack_reap_time == 0) {
stack_reap_time = stack_reap_interval;
reap_stacks();
}
}
+7 -1
View File
@@ -1,3 +1,5 @@
//! Kernel heap.
const r = @import("root");
const ke = r.ke;
const mm = r.mm;
@@ -43,7 +45,11 @@ pub fn alloc(size: usize, policy: mm.WaitPolicy) mm.Error!*anyopaque {
mmp.kernel_space.lock.acquire();
pte.* = mmp.impl.make_leaf_pte(page, .{ .read = true, .write = true }, 0);
pte.* = mmp.impl.make_leaf_pte(
page,
.{ .read = true, .write = true },
0,
);
}
mmp.kernel_space.lock.release();
+2
View File
@@ -1,3 +1,5 @@
//! Memory manager initialization.
const r = @import("root");
const pl = r.pl;
const mm = r.mm;
+9 -7
View File
@@ -1,17 +1,19 @@
const r = @import("root");
const std = @import("std");
const rtl = @import("rtl");
const pl = r.pl;
const mm = r.mm;
const mmp = mm.private;
const ke = r.ke;
const std = @import("std");
const assert = std.debug.assert;
const log = std.log.scoped(.@"mm/phys");
// How aggressive is memory reclamation.
const threshold_scale_factor = 10;
/// Threshold at which most memory allocations block, only high priority allocations are allowed.
/// Threshold at which most memory allocations block, only high priority
/// allocations are allowed.
pub var min_memory_threshold: usize = 0;
/// Threshold at which reclamation starts.
pub var low_memory_threshold: usize = 0;
@@ -29,14 +31,14 @@ var free_page_event: ke.Event = undefined;
var free_pages: usize = 0;
pub var usable_memory: std.atomic.Value(usize) = .init(0);
pub var usable_memory = std.atomic.Value(usize).init(0);
// Wait for pages to be available.
// List lock is held.
fn wait_for_pages(old_ipl: ke.Ipl) void {
var ipl = old_ipl;
std.debug.assert(@intFromEnum(ipl) < @intFromEnum(ke.Ipl.Dispatch));
assert(@backingInt(ipl) < @backingInt(ke.Ipl.Dispatch));
if (free_pages >= min_memory_threshold) {
return;
@@ -65,7 +67,7 @@ pub fn alloc() r.PAddr {
wait_for_pages(ipl);
std.debug.assert(!free_list.is_empty());
assert(!free_list.is_empty());
const head = free_list.first();
head.remove();
@@ -94,8 +96,8 @@ pub fn alloc_opts(opts: struct { policy: mm.WaitPolicy }) ?r.PAddr {
wait_for_pages(ipl);
}
std.debug.assert(free_pages > 0);
std.debug.assert(!free_list.is_empty());
assert(free_pages > 0);
assert(!free_list.is_empty());
const head = free_list.first();
head.remove();
+26 -15
View File
@@ -2,10 +2,12 @@
const r = @import("root");
const rtl = @import("rtl");
const std = @import("std");
const mm = r.mm;
const mmp = mm.private;
const std = @import("std");
const assert = std.debug.assert;
/// Describes a contiguous region of physical memory to be mapped
/// at a given virtual address.
const MapItem = struct {
@@ -45,8 +47,6 @@ fn index_for_level(va: usize, level: usize) usize {
}
pub const PMap = struct {
const Self = @This();
const num_levels = mmp.impl.levels.len;
const entries_per_table = 512;
const index_mask = entries_per_table - 1;
@@ -61,7 +61,7 @@ pub const PMap = struct {
return is_leaf_level(level) and mmp.impl.is_leaf_level_enabled(level);
}
fn cursor(self: *Self, va: r.VAddr) Cursor {
fn cursor(self: *PMap, va: r.VAddr) Cursor {
var c = Cursor{
.pmap = self,
.va = va,
@@ -73,11 +73,16 @@ pub const PMap = struct {
return c;
}
pub fn activate(self: *Self) void {
pub fn activate(self: *PMap) void {
mmp.impl.activate(self.root_pa);
}
pub fn map_from(self: *Self, va: r.VAddr, size: usize, source: anytype) void {
pub fn map_from(
self: *PMap,
va: r.VAddr,
size: usize,
source: anytype,
) void {
var c = self.cursor(va);
var remain = size;
@@ -91,7 +96,7 @@ pub const PMap = struct {
/// Map a contiguous virtual address range to a
/// contiguous physical address range.
pub fn map_contiguous_range(
self: *Self,
self: *PMap,
va: r.VAddr,
pa: r.PAddr,
size: usize,
@@ -107,7 +112,7 @@ pub const PMap = struct {
self.map_from(va, size, src);
}
pub fn query(self: *Self, va: r.VAddr) ?r.PAddr {
pub fn query(self: *PMap, va: r.VAddr) ?r.PAddr {
var c = self.cursor(va);
var level: usize = num_levels - 1;
@@ -129,8 +134,8 @@ pub const PMap = struct {
/// Unmap a contiguous range of virtual pages.
/// Only small pages are supported.
/// Return a pfn that points to the physical pages that were unmapped.
pub fn unmap(self: *Self, va: r.VAddr, size: usize) ?mmp.PfnList {
std.debug.assert(std.mem.isAligned(va, mm.page_size));
pub fn unmap(self: *PMap, va: r.VAddr, size: usize) ?mmp.PfnList {
assert(std.mem.isAligned(va, mm.page_size));
var c = self.cursor(va);
@@ -178,7 +183,7 @@ pub const PMap = struct {
}
pub const Cursor = struct {
pmap: *Self,
pmap: *PMap,
va: r.VAddr,
/// Cached pointers to each level's page table, filled as the cursor
/// descends. Only indices in `[0, top_level]` are valid.
@@ -283,9 +288,12 @@ pub fn wire_pte(
va: r.VAddr,
policy: mm.WaitPolicy,
) mm.Error!*mmp.impl.Pte {
std.debug.assert(space.lock.is_locked());
assert(space.lock.is_locked());
var table: [*]mmp.impl.Pte = @ptrFromInt(
mm.p2v(mmp.kernel_space.pmap.root_pa),
);
var table: [*]mmp.impl.Pte = @ptrFromInt(mm.p2v(mmp.kernel_space.pmap.root_pa));
var level = mmp.impl.levels.len - 1;
while (true) {
@@ -293,7 +301,7 @@ pub fn wire_pte(
var entry = table[idx];
if (level == 0) {
std.debug.assert(!entry.present);
assert(!entry.present);
return &table[idx];
}
@@ -302,7 +310,10 @@ pub fn wire_pte(
// since that may block.
space.lock.release();
const new_table_pa = mmp.phys.alloc_opts(.{ .policy = policy }) orelse {
const new_table_pa = mmp.phys.alloc_opts(
.{ .policy = policy },
) orelse
{
space.lock.acquire();
return mm.Error.OutOfMemory;
};
+7 -2
View File
@@ -88,7 +88,11 @@ pub inline fn make_table_pte(pa: r.PAddr) Pte {
};
}
pub inline fn make_leaf_pte(pa: r.PAddr, flags: mm.MapFlags, level: usize) Pte {
pub inline fn make_leaf_pte(
pa: r.PAddr,
flags: mm.MapFlags,
level: usize,
) Pte {
_ = level;
return Pte{
.present = true,
@@ -118,5 +122,6 @@ pub fn is_leaf_level_enabled(level: usize) bool {
}
pub fn init_kernel() void {
mmp.kernel_space.pmap.root_pa = (rv64.read_csr("satp") & ((@as(u64, 1) << 44) - 1)) << 12;
mmp.kernel_space.pmap.root_pa = (rv64.read_csr("satp") &
((@as(u64, 1) << 44) - 1)) << 12;
}
+5 -3
View File
@@ -35,7 +35,7 @@ comptime {
if (@sizeOf(Page) != r.BootInfo.page_struct_size) {
@compileError(
std.fmt.comptimePrint(
"'Page' size mismatch between loader and kernel: kernel={} loader={}",
"'Page' size mismatch between loader and kernel: k={} l={}",
.{ @sizeOf(Page), r.BootInfo.page_struct_size },
),
);
@@ -68,11 +68,13 @@ pub inline fn pfn_to_page(pfn: Pfn) usize {
}
pub inline fn struct_page_to_pfn(page: *Page) Pfn {
return @intCast((@intFromPtr(page) - private.impl.pfndb_base) / @sizeOf(Page));
return @intCast((@intFromPtr(page) - private.impl.pfndb_base) /
@sizeOf(Page));
}
pub inline fn pfn_to_struct_page(pfn: Pfn) *Page {
return @ptrFromInt(private.impl.pfndb_base + (@as(usize, @intCast(pfn)) * @sizeOf(Page)));
return @ptrFromInt(private.impl.pfndb_base +
(@as(usize, pfn) * @sizeOf(Page)));
}
pub var pfndb: [*]Page = @ptrFromInt(private.impl.pfndb_base);
+10 -8
View File
@@ -1,7 +1,6 @@
//! MM-level TLB shootdown support
const r = @import("root");
const std = @import("std");
const rtl = @import("rtl");
const ke = r.ke;
@@ -10,9 +9,12 @@ const mmp = mm.private;
const ps = r.ps;
const ex = r.ex;
pub var sync_shootdowns: std.atomic.Value(usize) = .init(0);
pub var async_shootdowns: std.atomic.Value(usize) = .init(0);
pub var sent_shootdowns: std.atomic.Value(usize) = .init(0);
const std = @import("std");
const assert = std.debug.assert;
pub var sync_shootdowns = std.atomic.Value(usize).init(0);
pub var async_shootdowns = std.atomic.Value(usize).init(0);
pub var sent_shootdowns = std.atomic.Value(usize).init(0);
var work_item: ex.WorkItem = undefined;
@@ -54,7 +56,7 @@ fn reap_pages(_: ?*anyopaque) void {
/// Unmap and flush a virtual range on the given space.
/// Space lock is held on entry and IPL is raised to Dispatch.
pub fn reclaim_range(space: *mm.Space, va: r.VAddr, size: usize) void {
std.debug.assert(std.mem.isAligned(va, mm.page_size));
assert(std.mem.isAligned(va, mm.page_size));
// Unmap the virtual addresses and get the backing physical pages.
const list = space.pmap.unmap(va, size) orelse {
@@ -67,7 +69,7 @@ pub fn reclaim_range(space: *mm.Space, va: r.VAddr, size: usize) void {
.base = va,
.npages = @truncate(size / mm.page_size),
.link = undefined,
.state = .init(0),
.state = std.atomic.Value(u16).init(0),
.payload = .{
@intFromPtr(space),
@as(*const usize, @ptrCast(@alignCast(&list))).*,
@@ -77,7 +79,7 @@ pub fn reclaim_range(space: *mm.Space, va: r.VAddr, size: usize) void {
space.lock.release();
const ipl = ke.ipl.raise(.Dispatch);
var mask: ke.CpuMask = .init(false);
var mask = ke.CpuMask.init(false);
for (0..ke.ncpus) |i| {
if (i != ke.cpu.current()) {
@@ -110,6 +112,6 @@ fn activation(_: *rtl.HandoffList) void {
}
pub fn init() void {
ke.shootdown.shootdowns.* = .init(activation);
ke.shootdown.shootdowns.* = rtl.HandoffList.init(activation);
work_item.init(.High, reap_pages, null);
}
+121 -85
View File
@@ -114,16 +114,18 @@
//! an object leaves the slab layer and destructors run when it returns to it,
//! slabs hold raw objects while magazines hold constructed ones.
const rtl = @import("rtl");
const r = @import("root");
const std = @import("std");
const config = @import("config");
const rtl = @import("rtl");
const r = @import("root");
const ke = r.ke;
const mm = r.mm;
const ex = r.ex;
const mmp = mm.private;
const std = @import("std");
const assert = std.debug.assert;
/// Interval at which we do housekeeping (working set update, reaping, etc.)
pub const update_interval_s = 15;
@@ -276,7 +278,7 @@ const Slab = struct {
const bm = self.bitmap();
const bit = @as(u64, 1) << @intCast(idx % 64);
std.debug.assert(bm[idx / 64] & (bit) == 0);
assert(bm[idx / 64] & (bit) == 0);
bm[idx / 64] |= bit;
}
@@ -558,8 +560,6 @@ pub const Zone = struct {
arg: ?*anyopaque,
const Self = @This();
const InitOptions = struct {
alignment: u16 = slab_align,
ctor: ?*const fn (*anyopaque) void = null,
@@ -586,7 +586,7 @@ pub const Zone = struct {
/// Initialize a zone.
pub fn init(
self: *Self,
self: *Zone,
name: []const u8,
size: usize,
options: InitOptions,
@@ -634,9 +634,9 @@ pub const Zone = struct {
self.smr_reclaim = options.smr_reclaim;
if (options.import) |i| {
std.debug.assert(options.release != null);
assert(options.release != null);
self.import = i;
self.release = options.release.?;
self.release = options.release orelse unreachable;
self.arg = options.arg;
} else {
self.import = zone_import;
@@ -669,7 +669,7 @@ pub const Zone = struct {
}
/// Allocate an object from the zone.
pub fn alloc(self: *Self, opts: AllocOpts) mm.Error!*anyopaque {
pub fn alloc(self: *Zone, opts: AllocOpts) mm.Error!*anyopaque {
// Try grabbing an object from the magazine layer.
if (magazines_initialized and self.use_magazines) {
@branchHint(.likely);
@@ -682,12 +682,13 @@ pub const Zone = struct {
while (true) {
if (cpu.alloc_rounds > 0) {
// Fast path: just pop from the alloc magazine.
const alloc_mag = cpu.alloc orelse unreachable;
cpu.alloc_rounds -= 1;
buf = cpu.alloc.?.rounds_ptr()[cpu.alloc_rounds];
buf = alloc_mag.rounds_ptr()[cpu.alloc_rounds];
if (is_poison_enabled and !self.dont_touch) {
if (!check_poison(buf, self.obj_size, free_poison)) {
@panic("slab poison mismatch: object corrupted after free");
@panic("poison mismatch: possible use-after-free");
}
fill_with_poison(buf, self.obj_size, alloc_poison);
@@ -702,7 +703,7 @@ pub const Zone = struct {
}
if (cpu.free_rounds > 0 and self.smr == null) {
std.debug.assert(cpu.free != null);
assert(cpu.free != null);
// Alloc magazine is empty. If the free magazine has items,
// swap them. The free magazine now becomes our alloc
@@ -715,7 +716,8 @@ pub const Zone = struct {
// Both magazines are empty.
// Try to get a full magazine from the CPU-local depot.
if (self.full_mags_ready(&cpu.depot.full_mags)) {
const full_mag = Depot.alloc(&cpu.depot.full_mags).?;
const full_mag = Depot.alloc(&cpu.depot.full_mags) orelse
unreachable;
// Discard our empty alloc magazine to the depot.
if (cpu.alloc) |empty_mag| {
@@ -741,7 +743,8 @@ pub const Zone = struct {
// Try getting a full magazine from the CPU-local depot again.
if (self.full_mags_ready(&cpu.depot.full_mags)) {
const full_mag = Depot.alloc(&cpu.depot.full_mags).?;
const full_mag = Depot.alloc(&cpu.depot.full_mags) orelse
unreachable;
// Discard our empty alloc magazine to the depot.
if (cpu.alloc) |empty_mag| {
@@ -758,14 +761,15 @@ pub const Zone = struct {
continue;
}
// Nothing was found. Allocate a new magazine and fill it from the
// import layer, then replace the CPU's alloc magazine with it.
// It is preferable (maybe even required?) to drop the CPU lock
// while we do this work; when we do re-acquire the lock we then
// might notice that someone has already done the work for us and
// put a magazine in the CPU's alloc storage. In that case, it would
// be better to take our fresh magazine and insert it into the depot
// instead, since there is a higher chance the impostor magazine
// Nothing was found. Allocate a new magazine and fill it from
// the import layer, then replace the CPU's alloc magazine with
// it. It is preferable (maybe even required?) to drop the CPU
// lock while we do this work; when we do re-acquire the lock
// we then might notice that someone has already done the work
// for us and put a magazine in the CPU's alloc storage.
// In that case, it would be better to take our fresh magazine
// and insert it into the depot instead,
// since there is a higher chance the impostor magazine
// contains objects that are hotter in cache.
cpu.lock.release(ipl);
@@ -783,7 +787,8 @@ pub const Zone = struct {
opts.policy,
);
// We dropped the lock and IPL, so we might've been migrated.
// We dropped the lock and IPL, so we might've been
// migrated.
ipl = ke.ipl.raise(.Dispatch);
cpu = &self.cpus[ke.cpu.current()].value;
cpu.lock.acquire_no_ipl();
@@ -793,14 +798,19 @@ pub const Zone = struct {
cpu.alloc_rounds = cnt;
continue;
} else {
// We lost the race. If the magazine we got was fully filled,
// then put it in the depot. Otherwise, dispose of it.
// We lost the race. If the magazine we got was fully
// filled, then put it in the depot.
// Otherwise, dispose of it.
if (cnt == magazine_size.load()) {
// Ensure this magazine isn't counted as a freed one,
// it is instantly reclaimable.
// Ensure this magazine isn't counted as a freed
// one, it is instantly reclaimable.
nm.seq = ke.smr.seq_invalid;
Depot.free(nm, &cpu.depot.full_mags, self.reuse_policy);
Depot.free(
nm,
&cpu.depot.full_mags,
self.reuse_policy,
);
continue;
}
@@ -828,8 +838,8 @@ pub const Zone = struct {
}
}
// Fallback in case this is a zone with magazines disabled or we got into trouble above,
// just fetch an object from the import layer.
// Fallback in case this is a zone with magazines disabled or we got
// into trouble above, just fetch an object from the import layer.
var elems: [1]*anyopaque = undefined;
_ = try self.import(
@@ -843,7 +853,7 @@ pub const Zone = struct {
}
/// Free an object back to the zone.
pub fn free(self: *Self, obj: *anyopaque) void {
pub fn free(self: *Zone, obj: *anyopaque) void {
if (magazines_initialized and self.use_magazines) {
@branchHint(.likely);
@@ -857,8 +867,12 @@ pub const Zone = struct {
{
// Fast path: push directly to free magazine.
// For SMR zones the dtor and poisoning are deferred until
// the magazine is reused, as readers may still hold the object.
if (is_poison_enabled and self.smr == null and !self.dont_touch) {
// the magazine is reused, as readers may still hold
// the object.
const free_mag = cpu.free orelse unreachable;
if (is_poison_enabled and self.smr == null and
!self.dont_touch)
{
if (self.dtor) |dtor| {
dtor(obj);
}
@@ -866,12 +880,16 @@ pub const Zone = struct {
fill_with_poison(obj, self.obj_size, free_poison);
}
cpu.free.?.rounds_ptr()[cpu.free_rounds] = obj;
free_mag.rounds_ptr()[cpu.free_rounds] = obj;
cpu.free_rounds += 1;
if (self.smr != null and cpu.free_rounds == magazine_size.load()) {
if (self.smr != null and
cpu.free_rounds == magazine_size.load())
{
const smr = self.smr orelse unreachable;
// Stamp the now-full magazine.
cpu.free.?.seq = ke.smr.deferred_advance(self.smr.?);
free_mag.seq = ke.smr.deferred_advance(smr);
}
break true;
@@ -929,8 +947,8 @@ pub const Zone = struct {
}
// No empty magazine in the depot, try to allocate a new one.
// We need to drop IPL and the CPU lock here because we need to acquire
// the magazine's allocator's blocking lock.
// We need to drop IPL and the CPU lock here because we need to
// acquire the magazine's allocator's blocking lock.
cpu.lock.release(ipl);
const raw_mag = magazine_zone.alloc(
@@ -970,7 +988,7 @@ pub const Zone = struct {
self.release(self.arg, &slice, self.chunk_size);
}
fn slab_free(self: *Self, obj: *anyopaque, poison: bool) void {
fn slab_free(self: *Zone, obj: *anyopaque, poison: bool) void {
if (!is_poison_enabled or poison and !self.dont_touch) {
if (self.dtor) |dtor| {
dtor(obj);
@@ -988,7 +1006,8 @@ pub const Zone = struct {
@intFromPtr(obj),
mm.page_size,
);
const phys = mmp.kernel_space.pmap.query(page_va).?;
const phys = mmp.kernel_space.pmap.query(page_va) orelse
unreachable;
const page = mm.pfn_to_struct_page(mm.page_to_pfn(phys));
break :blk page.alloced.slab_data.slab;
} else blk: {
@@ -1023,7 +1042,7 @@ pub const Zone = struct {
elem_size: usize,
policy: mm.WaitPolicy,
) mm.Error!usize {
const self: *Self = @ptrCast(@alignCast(arg));
const self: *Zone = @ptrCast(@alignCast(arg));
_ = elem_size;
@@ -1046,18 +1065,21 @@ pub const Zone = struct {
self.lock.acquire();
// Note: this is racy and could lead to two threads creating a slab
// at the same time. Worst case scenario, we have one slab too many,
// so we don't care; we already did the work required for slab
// construction and this ideally should not happen very often.
// Note: this is racy and could lead to two threads creating a
// slab at the same time. Worst case scenario, we have one slab
// too many, so we don't care; we already did the work
// required for slab construction and this ideally should not
// happen very often.
self.partial_slabs.insert_tail(&slab.link);
} else {
slab = @fieldParentPtr("link", self.partial_slabs.first());
}
while (slab.refcount != slab.capacity and i < requested) {
const idx = slab.bitmap_alloc().?;
const buf: *anyopaque = @ptrFromInt(slab.base + idx * self.chunk_size);
const idx = slab.bitmap_alloc() orelse unreachable;
const buf: *anyopaque = @ptrFromInt(
slab.base + idx * self.chunk_size,
);
slab.refcount += 1;
@@ -1068,7 +1090,7 @@ pub const Zone = struct {
if (is_poison_enabled) {
if (!check_poison(buf, self.obj_size, free_poison)) {
@panic("slab poison mismatch: object corrupted after free");
@panic("poison mismatch: possible use-after-free");
}
fill_with_poison(buf, self.obj_size, alloc_poison);
@@ -1087,8 +1109,12 @@ pub const Zone = struct {
return i;
}
fn zone_release(arg: ?*anyopaque, elems: []*anyopaque, elem_size: usize) void {
const self: *Self = @ptrCast(@alignCast(arg));
fn zone_release(
arg: ?*anyopaque,
elems: []*anyopaque,
elem_size: usize,
) void {
const self: *Zone = @ptrCast(@alignCast(arg));
_ = elem_size;
@@ -1112,7 +1138,7 @@ pub const Zone = struct {
/// Periodic updates on the zone.
/// Updates the magazine working set and trims or grows them if needed.
fn update(self: *Self) void {
fn update(self: *Zone) void {
const ipl = self.depot_lock.acquire();
// Update the working set size.
@@ -1187,7 +1213,7 @@ pub const Zone = struct {
}
/// Returns whether or not we should trim excess magazines.
fn trim_needed(self: *Self) bool {
fn trim_needed(self: *Zone) bool {
if (!self.use_magazines) return false;
if (self.trim_depot) return true;
@@ -1221,8 +1247,9 @@ pub const Zone = struct {
/// Called periodically to trim our magazines.
/// This frees the excess magazines.
fn trim(self: *Self, trim_depot: bool) void {
// Detach magazines under the locks, then release their objects unlocked.
fn trim(self: *Zone, trim_depot: bool) void {
// Detach magazines under the locks, then release their objects
// unlocked.
self.lock.acquire();
const empty_mags = self.trim_maglist(&self.depot.empty_mags);
@@ -1262,7 +1289,7 @@ pub const Zone = struct {
/// Called when memory is low and we need to make memory ASAP.
/// Drains all magazines from the depot.
fn drain(self: *Self) void {
fn drain(self: *Zone) void {
if (!self.use_magazines) return;
// Detach magazines before invoking release callbacks.
@@ -1304,7 +1331,7 @@ pub const Zone = struct {
}
/// Detach excess magazines for destruction.
fn trim_maglist(self: *Self, maglist: *MagazineList) ?*Magazine {
fn trim_maglist(self: *Zone, maglist: *MagazineList) ?*Magazine {
const ipl = self.depot_lock.acquire();
// Grab the magazines and WSS under the depot lock.
@@ -1345,7 +1372,7 @@ pub const Zone = struct {
}
/// Trim a CPU's depot to `target`.
fn trim_cpu(self: *Self, cpu: *Cpu, depot: *Depot, target: usize) void {
fn trim_cpu(self: *Zone, cpu: *Cpu, depot: *Depot, target: usize) void {
const ipl = cpu.lock.acquire();
// Split the target (cpu_depot_size) in 2 for each magazine type.
@@ -1361,7 +1388,7 @@ pub const Zone = struct {
// Move them to our depot.
for (0..n) |_| {
Depot.free(
Depot.alloc(&cpu.depot.full_mags).?,
Depot.alloc(&cpu.depot.full_mags) orelse unreachable,
&depot.full_mags,
self.reuse_policy,
);
@@ -1375,7 +1402,7 @@ pub const Zone = struct {
// Move them to our depot.
for (0..n) |_| {
Depot.free(
Depot.alloc(&cpu.depot.empty_mags).?,
Depot.alloc(&cpu.depot.empty_mags) orelse unreachable,
&depot.empty_mags,
self.reuse_policy,
);
@@ -1385,7 +1412,7 @@ pub const Zone = struct {
cpu.lock.release(ipl);
}
fn maglist_destroy(self: *Self, head: ?*Magazine, n: usize) void {
fn maglist_destroy(self: *Zone, head: ?*Magazine, n: usize) void {
var elems = head;
while (elems) |mag| {
elems = mag.next;
@@ -1394,7 +1421,7 @@ pub const Zone = struct {
}
/// Destroy a magazine.
fn magazine_destroy(self: *Self, magazine: *Magazine, rounds: usize) void {
fn magazine_destroy(self: *Zone, magazine: *Magazine, rounds: usize) void {
if (rounds != 0) {
if (self.smr) |smr| {
// The stamp may still be uncommitted if the magazine never
@@ -1439,7 +1466,7 @@ pub const Zone = struct {
mmp.phys.free(phys);
}
fn slab_create_small(self: *Self, policy: mm.WaitPolicy) mm.Error!*Slab {
fn slab_create_small(self: *Zone, policy: mm.WaitPolicy) mm.Error!*Slab {
const buf = try alloc_page(policy);
const capacity = Slab.calc_capacity(self.chunk_size, self.alignment);
@@ -1463,7 +1490,9 @@ pub const Zone = struct {
}
for (0..capacity) |i| {
const obj: *anyopaque = @ptrFromInt(slab.base + i * self.chunk_size);
const obj: *anyopaque = @ptrFromInt(
slab.base + i * self.chunk_size,
);
if (is_poison_enabled) {
fill_with_poison(obj, self.obj_size, free_poison);
}
@@ -1472,7 +1501,7 @@ pub const Zone = struct {
return slab;
}
fn slab_create_large(self: *Self, policy: mm.WaitPolicy) mm.Error!*Slab {
fn slab_create_large(self: *Zone, policy: mm.WaitPolicy) mm.Error!*Slab {
const capacity = (self.slab_size) / self.chunk_size;
const buf = try mmp.heap.alloc(self.slab_size, policy);
@@ -1515,7 +1544,7 @@ pub const Zone = struct {
return ret;
}
fn slab_create(self: *Self, policy: mm.WaitPolicy) mm.Error!*Slab {
fn slab_create(self: *Zone, policy: mm.WaitPolicy) mm.Error!*Slab {
if (self.obj_size <= small_slab_size) {
return self.slab_create_small(policy);
} else {
@@ -1523,7 +1552,7 @@ pub const Zone = struct {
}
}
fn slab_destroy(self: *Self, slab: *Slab) void {
fn slab_destroy(self: *Zone, slab: *Slab) void {
if (self.obj_size > small_slab_size) {
const base = slab.base;
const alloc_size = @sizeOf(Slab) + Slab.bitmap_bytes(slab.capacity);
@@ -1538,7 +1567,7 @@ pub const Zone = struct {
}
/// Check that a full magazine can be taken from the list head.
fn full_mags_ready(self: *Self, list: *MagazineList) bool {
fn full_mags_ready(self: *Zone, list: *MagazineList) bool {
const head = list.head orelse return false;
const smr = self.smr orelse return true;
@@ -1547,7 +1576,7 @@ pub const Zone = struct {
/// Deferred free-time work for SMR zones, ran when a full magazine
/// is reused after its grace period expired.
fn magazine_reuse(self: *Self, mag: *Magazine) void {
fn magazine_reuse(self: *Zone, mag: *Magazine) void {
if (self.smr == null) return;
for (0..magazine_size.load()) |i| {
@@ -1564,7 +1593,7 @@ pub const Zone = struct {
/// Move all local full magazines to the zone depot so their sequences
/// can age, committing their deferred advances. Depot lock is held.
fn smr_rotate_full(self: *Self, smr: *ke.smr.Domain, cpu: *Cpu) void {
fn smr_rotate_full(self: *Zone, smr: *ke.smr.Domain, cpu: *Cpu) void {
const src = &cpu.depot.full_mags;
const dst = &self.depot.full_mags;
const tail = src.tail orelse return;
@@ -1584,7 +1613,8 @@ pub const Zone = struct {
src.num = 0;
src.min = 0;
// The last magazine has the newest stamp, committing it covers them all.
// The last magazine has the newest stamp, committing it covers them
// all.
ke.smr.deferred_advance_commit(smr, tail.seq);
}
@@ -1592,7 +1622,7 @@ pub const Zone = struct {
/// after an allocation. The CPU depot moves its extra full magazines to the
/// zone depot, and tries to get full magazines from the zone depot up to
/// `target`. CPU lock is held.
fn alloc_depot_rebalance(self: *Self, target: usize, cpu: *Cpu) void {
fn alloc_depot_rebalance(self: *Zone, target: usize, cpu: *Cpu) void {
if (!self.depot_lock.try_acquire_no_ipl()) {
// Register the contention.
self.depot_lock.acquire_no_ipl();
@@ -1605,7 +1635,7 @@ pub const Zone = struct {
for (0..n) |_| {
Depot.free(
Depot.alloc(&cpu.depot.empty_mags).?,
Depot.alloc(&cpu.depot.empty_mags) orelse unreachable,
&self.depot.empty_mags,
self.reuse_policy,
);
@@ -1631,7 +1661,7 @@ pub const Zone = struct {
if (n != 0 and self.full_mags_ready(&self.depot.full_mags)) {
for (0..n) |_| {
Depot.free(
Depot.alloc(&self.depot.full_mags).?,
Depot.alloc(&self.depot.full_mags) orelse unreachable,
&cpu.depot.full_mags,
self.reuse_policy,
);
@@ -1645,7 +1675,7 @@ pub const Zone = struct {
/// after a free.The CPU depot moves its extra full magazines to the
/// zone depot, and tries to get empty magazines from the zone depot up to
/// `target`. CPU lock is held.
fn free_depot_rebalance(self: *Self, target: usize, cpu: *Cpu) void {
fn free_depot_rebalance(self: *Zone, target: usize, cpu: *Cpu) void {
if (!self.depot_lock.try_acquire_no_ipl()) {
// Register the contention.
self.depot_lock.acquire_no_ipl();
@@ -1663,7 +1693,8 @@ pub const Zone = struct {
if (n != 0 and self.full_mags_ready(&self.depot.full_mags)) {
for (0..n) |_| {
Depot.free(
Depot.alloc(&self.depot.full_mags).?,
Depot.alloc(&self.depot.full_mags) orelse
unreachable,
&cpu.depot.full_mags,
self.reuse_policy,
);
@@ -1675,7 +1706,7 @@ pub const Zone = struct {
for (0..n) |_| {
Depot.free(
Depot.alloc(&cpu.depot.full_mags).?,
Depot.alloc(&cpu.depot.full_mags) orelse unreachable,
&self.depot.full_mags,
self.reuse_policy,
);
@@ -1691,7 +1722,7 @@ pub const Zone = struct {
);
for (0..n) |_| {
Depot.free(
Depot.alloc(&self.depot.empty_mags).?,
Depot.alloc(&self.depot.empty_mags) orelse unreachable,
&cpu.depot.empty_mags,
self.reuse_policy,
);
@@ -1719,7 +1750,7 @@ pub fn TypedZone(comptime T: type) type {
return struct {
zone: Zone,
const Self = @This();
const TZone = @This();
pub const InitOptions = struct {
ctor: ?*const fn (*T) void = null,
@@ -1728,7 +1759,11 @@ pub fn TypedZone(comptime T: type) type {
smr_reclaim: ?*const fn (*T) void = null,
};
pub fn init(self: *@This(), name: []const u8, options: InitOptions) void {
pub fn init(
self: *TZone,
name: []const u8,
options: InitOptions,
) void {
self.zone.init(name, @sizeOf(T), .{
.alignment = @alignOf(T),
.ctor = @ptrCast(options.ctor),
@@ -1738,17 +1773,17 @@ pub fn TypedZone(comptime T: type) type {
});
}
pub fn create(self: *Self) mm.Error!*T {
pub fn create(self: *TZone) mm.Error!*T {
const ret = try self.zone.alloc(.{});
return @ptrCast(@alignCast(ret));
}
pub fn create_opts(self: *Self, opts: AllocOpts) mm.Error!*T {
pub fn create_opts(self: *TZone, opts: AllocOpts) mm.Error!*T {
const ret = try self.zone.alloc(opts);
return @ptrCast(@alignCast(ret));
}
pub fn destroy(self: *Self, obj: *T) void {
pub fn destroy(self: *TZone, obj: *T) void {
self.zone.free(obj);
}
};
@@ -1787,7 +1822,7 @@ pub fn late_init() linksection(r.init) void {
z.cpus = cpus_alloc();
for (0..ke.ncpus) |i| {
z.cpus[i] = .init(.{
z.cpus[i] = rtl.CachePadded(Cpu).init(.{
.lock = ke.SpinLock.init("Magazine"),
.alloc = null,
.free = null,
@@ -1889,7 +1924,8 @@ fn gpa_remap(
new_len: usize,
ret_addr: usize,
) ?[*]u8 {
if (gpa_resize(undefined, buf, alignment, new_len, ret_addr)) return buf.ptr;
if (gpa_resize(undefined, buf, alignment, new_len, ret_addr))
return buf.ptr;
const new_ptr = gpa_alloc(undefined, new_len, alignment, ret_addr) orelse
return null;
+8 -4
View File
@@ -276,7 +276,8 @@ pub var rsdt: ?*Rsdt = null;
pub fn find_table(signature: []const u8) ?*SdtHeader {
if (xsdt) |x| {
const entry_count = (x.header.length - @sizeOf(SdtHeader)) / @sizeOf(u64);
const entry_count = (x.header.length - @sizeOf(SdtHeader)) /
@sizeOf(u64);
const entries_ptr: [*]align(1) u64 = @ptrCast(&x.entries);
for (0..entry_count) |i| {
@@ -286,7 +287,8 @@ pub fn find_table(signature: []const u8) ?*SdtHeader {
}
}
} else if (rsdt) |rsdt_table| {
const entry_count = (rsdt_table.header.length - @sizeOf(SdtHeader)) / @sizeOf(u32);
const entry_count = (rsdt_table.header.length - @sizeOf(SdtHeader)) /
@sizeOf(u32);
const entries_ptr: [*]align(1) u32 = @ptrCast(&rsdt_table.entries);
for (0..entry_count) |i| {
@@ -316,7 +318,8 @@ fn format_table(hdr: *SdtHeader, phys_addr: u64) void {
fn enumerate_tables() void {
if (xsdt) |x| {
const entry_count = (x.header.length - @sizeOf(SdtHeader)) / @sizeOf(u64);
const entry_count = (x.header.length - @sizeOf(SdtHeader)) /
@sizeOf(u64);
const entries_ptr: [*]align(1) u64 = @ptrCast(&x.entries);
for (0..entry_count) |i| {
@@ -325,7 +328,8 @@ fn enumerate_tables() void {
format_table(hdr, phys);
}
} else if (rsdt) |rsdt_table| {
const entry_count = (rsdt_table.header.length - @sizeOf(SdtHeader)) / @sizeOf(u32);
const entry_count = (rsdt_table.header.length - @sizeOf(SdtHeader)) /
@sizeOf(u32);
const entries_ptr: [*]align(1) u32 = @ptrCast(&rsdt_table.entries);
for (0..entry_count) |i| {
+9 -5
View File
@@ -23,7 +23,7 @@ const start_stack = ke.ExportedCpuLocal(usize, 0, "ap_start_stack");
pub const start_thread = ke.CpuLocal(*ke.Thread, undefined);
var aps_booted: std.atomic.Value(usize) = .init(0);
var aps_booted = std.atomic.Value(usize).init(0);
const ApData = extern struct {
entry: usize align(1),
@@ -77,15 +77,18 @@ pub fn init() linksection(r.init) void {
.Unknown => false,
};
const init_delay: r.Nanoseconds = .init(
const init_delay = r.Nanoseconds.init(
if (skip_delay) 0 else std.time.ns_per_ms * 10,
);
const sipi_delay: r.Nanoseconds = .init(
const sipi_delay = r.Nanoseconds.init(
if (skip_delay) std.time.ns_per_us * 10 else std.time.ns_per_us * 300,
);
const page: [*]u8 = @ptrFromInt(mm.p2v(0x8000));
@memcpy(page[0..trampoline_size], @as([*]u8, @ptrFromInt(trampoline_start)));
@memcpy(
page[0..trampoline_size],
@as([*]u8, @ptrFromInt(trampoline_start)),
);
const data_offset = @intFromPtr(&AP_TRAMPOLINE_DATA) - trampoline_start;
const data_phys: *ApData = @ptrFromInt(mm.p2v(0x8000 + data_offset));
@@ -97,7 +100,8 @@ pub fn init() linksection(r.init) void {
// Allocate per-cpu offsets for CPU-local data.
kep.impl.cpu_offsets = @ptrCast(offsets);
const percpu_size = @intFromPtr(&__percpu_end) - @intFromPtr(&__percpu_start);
const percpu_size = @intFromPtr(&__percpu_end) -
@intFromPtr(&__percpu_start);
log.info("per-CPU data size: {} bytes", .{percpu_size});
+2 -1
View File
@@ -1,5 +1,6 @@
//! Platform interface: each port must implement these functions and variables.
//! To add a new platform, create a new file and implement all required functions.
//! To add a new platform, create a new file and implement all required
//! functions.
const config = @import("config");
const r = @import("root");
+1 -1
View File
@@ -15,7 +15,7 @@ fn turnstile_ctor(ts: *kep.turnstile.Turnstile) void {
.queues = undefined,
.waiters = 0,
.owners = .none,
.lock = .init("turnstile"),
.lock = ke.SpinLock.init("turnstile"),
};
ts.reset();
}
+4 -2
View File
@@ -51,7 +51,7 @@ pub fn stack_activation() void {
pub fn init() void {
thread_zone.init("thread", .{});
ke.thread.reaper_list.* = .init(reaper_activation);
ke.thread.reaper_list.* = rtl.HandoffList.init(reaper_activation);
reaper_item.init(.High, reaper_fn, null);
stack_item.init(.High, stack_fn, null);
}
@@ -68,7 +68,9 @@ pub fn create_kernel(
var td = try thread_zone.create();
const stack = if (start_with_stack)
@intFromPtr(try mm.heap.alloc(kernel_thread_stack_size, .WaitForMemory)) +
@intFromPtr(
try mm.heap.alloc(kernel_thread_stack_size, .WaitForMemory),
) +
kernel_thread_stack_size
else
0;
+41 -37
View File
@@ -3,14 +3,17 @@
//! the buckets for write operations.
//! The general design is inspired by XNU's smr_shash.
const std = @import("std");
const rtl = @import("rtl");
const r = @import("root");
const mm = r.mm;
const ex = r.ex;
const ke = r.ke;
const rcu = r.rcu;
const std = @import("std");
const assert = std.debug.assert;
/// Atomic state for the table
const State = packed struct(u32) {
/// The current index.
@@ -30,8 +33,6 @@ const State = packed struct(u32) {
const Bucket = struct {
word: std.atomic.Value(usize),
const Self = @This();
const lock_tag = 0b01;
const stop_tag = 0b10;
const mask = lock_tag | stop_tag;
@@ -46,12 +47,12 @@ const Bucket = struct {
return @intFromPtr(head) | stop_tag;
}
fn load(self: *const Self) usize {
fn load(self: *const Bucket) usize {
return self.word.load(.acquire) & ~@as(usize, lock_tag);
}
/// Lock the bucket and return the address.
fn lock(self: *Self) usize {
fn lock(self: *Bucket) usize {
while (true) {
const v = self.word.fetchOr(lock_tag, .acquire);
if (v & lock_tag == 0) return v;
@@ -62,17 +63,17 @@ const Bucket = struct {
}
/// Unlock the bucket and store `val`.
fn unlock(self: *Self, val: usize) void {
std.debug.assert(val & lock_tag == 0);
fn unlock(self: *Bucket, val: usize) void {
assert(val & lock_tag == 0);
self.word.store(val, .release);
}
fn store_locked(self: *Self, val: usize) void {
std.debug.assert(val & lock_tag == 0);
fn store_locked(self: *Bucket, val: usize) void {
assert(val & lock_tag == 0);
self.word.store(val | lock_tag, .release);
}
fn unlock_no_changes(self: *Self) void {
fn unlock_no_changes(self: *Bucket) void {
_ = self.word.fetchAnd(~@as(usize, lock_tag), .release);
}
};
@@ -145,10 +146,8 @@ const Core = struct {
smr: *ke.smr.Domain,
hash_link: *const fn (link: *rcu.SList.Entry, seed: u32) u32,
const Self = @This();
fn init(
self: *Self,
self: *Core,
smr: *ke.smr.Domain,
policy: ResizePolicy,
min_size: u32,
@@ -168,29 +167,29 @@ const Core = struct {
const arr = try mm.zone.gpa.alloc(Bucket, size);
for (arr) |*elem| {
elem.word = .init(Bucket.stop_for(elem));
elem.word = std.atomic.Value(usize).init(Bucket.stop_for(elem));
}
self.min_shift = @intCast(std.math.log2_int_ceil(usize, min));
self.state = .init(.{
self.state = std.atomic.Value(State).init(.{
.current_shift = shift,
.new_shift = shift,
.current_index = 0,
.new_index = 0,
});
self.rehashing_state = .init(rehashing_none);
self.elems = .init(0);
self.rehashing_state = std.atomic.Value(u8).init(rehashing_none);
self.elems = std.atomic.Value(u32).init(0);
self.smr = smr;
self.buckets[0] = .init(@ptrCast(arr));
self.seeds[0] = .init(0);
self.buckets[0] = std.atomic.Value([*]Bucket).init(@ptrCast(arr));
self.seeds[0] = std.atomic.Value(u32).init(0);
self.rehash_work.init(.Normal, rehash, self);
}
fn migrate_bucket(
self: *Self,
self: *Core,
old: *Bucket,
new_array: [*]Bucket,
new_seed: u32,
@@ -244,7 +243,7 @@ const Core = struct {
old.unlock(Bucket.moved);
}
fn enqueue_rehash(self: *Self, reason: u8) void {
fn enqueue_rehash(self: *Core, reason: u8) void {
const prev = self.rehashing_state.fetchOr(reason, .monotonic);
if (prev == rehashing_none) {
@@ -254,10 +253,14 @@ const Core = struct {
/// Called in worker thread context when the table needs rehashing.
fn rehash(ptr: ?*anyopaque) void {
const self: *Self = @ptrCast(@alignCast(ptr.?));
const ctx = ptr orelse unreachable;
const self: *Core = @ptrCast(@alignCast(ctx));
while (true) {
const reason = self.rehashing_state.swap(rehashing_running, .monotonic);
const reason = self.rehashing_state.swap(
rehashing_running,
.monotonic,
);
const elems = self.elems.load(.monotonic);
const state = self.state.load(.monotonic);
@@ -288,7 +291,7 @@ const Core = struct {
}
/// Resize (and re-seed) the hash table to a given target shift.
fn resize(self: *Self, new_shift: u5) void {
fn resize(self: *Core, new_shift: u5) void {
var state = self.state.load(.monotonic);
state.new_shift = new_shift;
@@ -307,7 +310,7 @@ const Core = struct {
// 1. Prepare the new array and publish it.
for (new_array) |*elem| {
elem.word = .init(Bucket.stop_for(elem));
elem.word = std.atomic.Value(usize).init(Bucket.stop_for(elem));
}
self.buckets[state.new_index].store(new_array.ptr, .monotonic);
@@ -338,7 +341,7 @@ const Core = struct {
}
/// Remove a given element from the table, SMR must be entered.
fn remove(self: *Self, link: *rcu.SList.Entry) void {
fn remove(self: *Core, link: *rcu.SList.Entry) void {
var state = self.state.load(.acquire);
var h = self.hash_link(
link,
@@ -394,7 +397,7 @@ const Core = struct {
}
}
fn bucket(self: *Self, state: State, which: Which, h: u32) *Bucket {
fn bucket(self: *Core, state: State, which: Which, h: u32) *Bucket {
const idx: usize, const shift: u5 = switch (which) {
.Cur => .{ state.current_index, @intCast(state.current_shift) },
.New => .{ state.new_index, @intCast(state.new_shift) },
@@ -408,12 +411,12 @@ const Core = struct {
return @as(u32, 1) << shift;
}
fn should_grow(self: *Self, elems: u32, size: u32) bool {
fn should_grow(self: *Core, elems: u32, size: u32) bool {
const target_depth = ResizePolicy.depth_for(self.policy);
return elems > target_depth * size;
}
fn should_shrink(self: *Self, elems: u32, size: u32) bool {
fn should_shrink(self: *Core, elems: u32, size: u32) bool {
const min_size = size_for_shift(self.min_shift);
return switch (self.policy) {
@@ -429,9 +432,9 @@ const Core = struct {
/// This avoids going through multiple steps when resizing a busy table,
/// i.e instead of doubling the size and doing 16 -> 32 -> 64,
/// we can just do 16 -> 64 in a single resize.
fn target_shift_for(self: *Self, elems: u32) u5 {
fn target_shift_for(self: *Core, elems: u32) u5 {
const depth = ResizePolicy.depth_for(self.policy);
const needed = std.math.divCeil(usize, elems, depth) catch unreachable;
const needed = @divCeil(elems, depth);
var shift = self.min_shift;
while (size_for_shift(shift) < needed) : (shift += 1) {}
@@ -470,7 +473,8 @@ pub fn Table(
return struct {
core: Core,
const Self = @This();
const Map = @This();
const Options = struct {
smr: *ke.smr.Domain,
@@ -481,13 +485,13 @@ pub fn Table(
/// Initialize a new table, `min_size` provides the minimum count of
/// buckets. If `min_size` is 0, it will get clamped to sane defaults
/// depending on the chosen policy.
pub fn init(self: *Self, opts: Options) !void {
pub fn init(self: *Map, opts: Options) !void {
try self.core.init(opts.smr, opts.policy, opts.min_size);
self.core.hash_link = hash_link;
}
/// Get an element given a key, SMR must be entered.
pub fn get(self: *Self, key: Context.Key) ?*T {
pub fn get(self: *Map, key: Context.Key) ?*T {
const state = self.core.state.load(.acquire);
const h = Context.hash(
key,
@@ -521,7 +525,7 @@ pub fn Table(
/// Find an element or insert it given a key, SMR must be entered.
pub fn get_or_insert(
self: *Self,
self: *Map,
key: Context.Key,
value: *rcu.SList.Entry,
) ?*T {
@@ -590,11 +594,11 @@ pub fn Table(
}
/// Remove a given element from the table, SMR must be entered.
pub fn remove(self: *Self, link: *rcu.SList.Entry) void {
pub fn remove(self: *Map, link: *rcu.SList.Entry) void {
self.core.remove(link);
}
fn get_slow(self: *Self, key: Context.Key, bucket: *Bucket) ?*T {
fn get_slow(self: *Map, key: Context.Key, bucket: *Bucket) ?*T {
// Wait until the bucket finishes moving.
while (bucket.word.load(.monotonic) != Bucket.moved) {
std.atomic.spinLoopHint();
+50 -29
View File
@@ -1,4 +1,4 @@
//! AVL tree implementation
//! AVL tree impLementation
//! Invariant: For every node, the difference in height between its left
//! and right subtrees is strictly limited to -1, 0 or 1.
const bst = @import("bst.zig");
@@ -12,26 +12,28 @@ const zero: u2 = 0;
const one: u2 = 2;
fn get_bf(node: *bst.Node) u2 {
return node.parent.get_tag();
return node.parent.tag();
}
fn set_bf(node: *bst.Node, bf: u2) void {
node.parent.set_tag(bf);
}
pub fn AVLTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Order) type {
pub fn AVLTree(
comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Order,
) type {
return struct {
tree: bst.BST(cmp),
const Self = @This();
const Tree = @This();
pub fn init() Self {
return Self{
pub fn init() Tree {
return Tree{
.tree = bst.BST(cmp).init(),
};
}
fn rotate_left(self: *Self, x: *bst.Node, z: *bst.Node) *bst.Node {
fn rotate_left(self: *Tree, x: *bst.Node, z: *bst.Node) *bst.Node {
x.right = z.left;
if (!self.tree.is_nil(z.left)) {
@@ -53,7 +55,7 @@ pub fn AVLTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Orde
return z;
}
fn rotate_right(self: *Self, x: *bst.Node, z: *bst.Node) *bst.Node {
fn rotate_right(self: *Tree, x: *bst.Node, z: *bst.Node) *bst.Node {
x.left = z.right;
if (!self.tree.is_nil(z.right)) {
@@ -75,7 +77,11 @@ pub fn AVLTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Orde
return z;
}
fn rotate_right_left(self: *Self, x: *bst.Node, z: *bst.Node) *bst.Node {
fn rotate_right_left(
self: *Tree,
x: *bst.Node,
z: *bst.Node,
) *bst.Node {
var y = z.left;
var t2 = y.right;
@@ -123,7 +129,11 @@ pub fn AVLTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Orde
return y;
}
fn rotate_left_right(self: *Self, x: *bst.Node, z: *bst.Node) *bst.Node {
fn rotate_left_right(
self: *Tree,
x: *bst.Node,
z: *bst.Node,
) *bst.Node {
var y = z.right;
var t2 = y.left;
@@ -172,9 +182,14 @@ pub fn AVLTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Orde
}
/// Insert the node `elem` into the tree.
pub fn insert(self: *Self, elem: *bst.Node) !void {
pub fn insert(self: *Tree, elem: *bst.Node) !void {
if (self.tree.is_empty()) {
self.tree.insert(elem) catch unreachable;
self.tree.insert(elem) catch {
// This can't ever error since the tree is empty, so the
// element can't already exist.
unreachable;
};
set_bf(elem, zero);
return;
}
@@ -188,17 +203,18 @@ pub fn AVLTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Orde
// Now the tree may be unbalanced, rebalance it.
var node = elem;
var parent = elem.parent.get_ptr();
var parent = elem.parent.ptr();
var n = &bst.nil;
while (!self.tree.is_nil(parent)) : (parent = parent.parent.get_ptr()) {
const orig_parent = parent.parent.get_ptr();
while (!self.tree.is_nil(parent)) : (parent = parent.parent.ptr()) {
const orig_parent = parent.parent.ptr();
if (node == parent.right) {
if (get_bf(parent) == one) {
// This node has been inserted on the right of the parent,
// and the parent already has a balance factor of 1 (right-heavy),
// the parent's BF would then become +2, which breaks the invariant.
// This node has been inserted on the right of the
// parent, and the parent already has a balance factor
// of 1 (right-heavy), the parent's BF would then
// become +2, which breaks the invariant.
// We need to rebalance.
if (get_bf(node) == minus_one) {
@@ -214,13 +230,16 @@ pub fn AVLTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Orde
// 2
// / \
// 1 3
// In the code, `parent` would be 1 and `node` would be 2
// In the code, `parent` would be 1 and `node` would
// be 2
n = self.rotate_left(parent, node);
}
} else {
// The balance factor is either 0 or -1, increase it.
// If it was -1, now it is 0 so the subtree is perfectly balanced, we can stop.
// If it was 0, now it is +1, so we may need to check higher up for imbalances.
// If it was -1, now it is 0 so the subtree is
// perfectly balanced, we can stop.
// If it was 0, now it is +1, so we may need to check
// higher up for imbalances.
if (get_bf(parent) == minus_one) {
set_bf(parent, zero);
@@ -236,9 +255,10 @@ pub fn AVLTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Orde
// The logic is the same as the other case but inverted.
else {
if (get_bf(parent) == minus_one) {
// This node has been inserted on the left of the parent,
// and the parent already has a balance factor of -1 (left-heavy),
// the parent's balance factor would then become -2, which breaks the invariant.
// This node has been inserted on the left of the
// parent, and the parent already has a balance factor
// of -1 (left-heavy), the parent's balance factor would
// then become -2, which breaks the invariant.
// We need to rebalance.
if (get_bf(node) == one) {
@@ -276,10 +296,10 @@ pub fn AVLTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Orde
}
/// Delete the node `elem` from the tree.
pub fn delete(self: *Self, elem: *bst.Node) void {
pub fn delete(self: *Tree, elem: *bst.Node) void {
var node = elem;
var original_parent = &bst.nil;
var parent = node.parent.get_ptr();
var parent = node.parent.ptr();
var was_on_left = !self.tree.is_nil(parent) and parent.left == node;
var b = zero;
@@ -289,7 +309,7 @@ pub fn AVLTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Orde
self.tree.transplant(elem, elem.left);
} else {
var succ = self.tree.successor(node);
parent = succ.parent.get_ptr();
parent = succ.parent.ptr();
was_on_left = !self.tree.is_nil(parent) and succ == parent.left;
if (parent != elem) {
@@ -297,7 +317,8 @@ pub fn AVLTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Orde
succ.right = elem.right;
succ.right.parent.set_ptr(succ);
} else {
// The successor is the direct child, so start rebalancing from it.
// The successor is the direct child, so start rebalancing
// from it.
parent = succ;
}
@@ -311,7 +332,7 @@ pub fn AVLTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Orde
// Rebalance the tree.
while (!self.tree.is_nil(parent)) {
original_parent = parent.parent.get_ptr();
original_parent = parent.parent.ptr();
if (node == parent.left or was_on_left) {
was_on_left = false;
+15 -5
View File
@@ -1,9 +1,11 @@
//! Atomic barrier implementations on various architectures.
//! This is needed since Zig removed @fence.
const std = @import("std");
const builtin = @import("builtin");
const std = @import("std");
const assert = std.debug.assert;
pub inline fn fence(comptime ordering: std.builtin.AtomicOrder) void {
switch (ordering) {
.seq_cst, .acq_rel => mb(),
@@ -67,7 +69,11 @@ inline fn rel() void {
/// Copy to dest from src with atomic loads.
/// Both addresses need to be aligned.
pub fn atomic_load_memcpy(dest: anytype, src: anytype, comptime ordering: std.builtin.AtomicOrder) void {
pub fn atomic_load_memcpy(
dest: anytype,
src: anytype,
comptime ordering: std.builtin.AtomicOrder,
) void {
const T = @TypeOf(dest.*);
const Word = switch (@alignOf(T)) {
1 => u8,
@@ -78,7 +84,7 @@ pub fn atomic_load_memcpy(dest: anytype, src: anytype, comptime ordering: std.bu
const word_size = @sizeOf(Word);
const n = @sizeOf(T);
comptime std.debug.assert(n % word_size == 0);
comptime assert(n % word_size == 0);
const src_words: [*]const volatile Word = @ptrCast(src);
const dest_words: [*]Word = @ptrCast(dest);
@@ -91,7 +97,11 @@ pub fn atomic_load_memcpy(dest: anytype, src: anytype, comptime ordering: std.bu
/// Copy to dest from src with atomic stores.
/// Both addresses need to be aligned.
pub fn atomic_store_memcpy(dest: anytype, src: anytype, comptime ordering: std.builtin.AtomicOrder) void {
pub fn atomic_store_memcpy(
dest: anytype,
src: anytype,
comptime ordering: std.builtin.AtomicOrder,
) void {
const T = @TypeOf(dest.*);
const Word = switch (@alignOf(T)) {
1 => u8,
@@ -102,7 +112,7 @@ pub fn atomic_store_memcpy(dest: anytype, src: anytype, comptime ordering: std.b
const word_size = @sizeOf(Word);
const n = @sizeOf(T);
comptime std.debug.assert(n % word_size == 0);
comptime assert(n % word_size == 0);
const src_words: [*]const volatile Word = @ptrCast(src);
const dest_words: [*]Word = @ptrCast(dest);
+149 -102
View File
@@ -1,7 +1,13 @@
const std = @import("std");
const AtomicOrder = std.builtin.AtomicOrder;
const assert = std.debug.assert;
fn ClosestType(comptime N: usize) type {
const bits = std.math.ceilPowerOfTwo(usize, N) catch {
// Shouldn't happen.
unreachable;
};
fn get_closest_type(comptime N: usize) type {
const bits = std.math.ceilPowerOfTwo(usize, N) catch unreachable;
return @Int(.unsigned, bits);
}
@@ -12,15 +18,15 @@ fn bit_mask(comptime Word: type, bit_index: usize) Word {
/// Static bitmap that uses the most efficient underlying word size for N < 64
pub fn BitMap(comptime N: usize) type {
return struct {
pub const Word = if (N > 32) u64 else get_closest_type(N);
const Self = @This();
pub const Word = if (N > 32) u64 else ClosestType(N);
const Bitmap = @This();
const bits_per_word = @bitSizeOf(Word);
const arr_len = (N + bits_per_word - 1) / bits_per_word;
storage: [arr_len]Word,
fn valid_word_mask(word_index: usize) Word {
std.debug.assert(word_index < arr_len);
assert(word_index < arr_len);
if (word_index != arr_len - 1) return std.math.maxInt(Word);
@@ -30,39 +36,39 @@ pub fn BitMap(comptime N: usize) type {
}
/// Initialize a bitmap, setting all bits to `val`.
pub fn init(val: bool) Self {
pub fn init(val: bool) Bitmap {
return .{
.storage = @splat(if (val) std.math.maxInt(Self.Word) else 0),
.storage = @splat(if (val) std.math.maxInt(Bitmap.Word) else 0),
};
}
/// Set a given bit.
pub fn set(self: *Self, bit: usize) void {
std.debug.assert(bit < N);
const word_index = bit / Self.bits_per_word;
const bit_index = bit % Self.bits_per_word;
pub fn set(self: *Bitmap, bit: usize) void {
assert(bit < N);
const word_index = bit / Bitmap.bits_per_word;
const bit_index = bit % Bitmap.bits_per_word;
self.storage[word_index] |= bit_mask(Word, bit_index);
}
/// Clear a given bit.
pub fn clear(self: *Self, bit: usize) void {
std.debug.assert(bit < N);
const word_index = bit / Self.bits_per_word;
const bit_index = bit % Self.bits_per_word;
pub fn clear(self: *Bitmap, bit: usize) void {
assert(bit < N);
const word_index = bit / Bitmap.bits_per_word;
const bit_index = bit % Bitmap.bits_per_word;
self.storage[word_index] &= ~bit_mask(Word, bit_index);
}
/// Get a given bit.
pub fn get(self: *const Self, bit: usize) bool {
std.debug.assert(bit < N);
const word_index = bit / Self.bits_per_word;
const bit_index = bit % Self.bits_per_word;
pub fn get(self: *const Bitmap, bit: usize) bool {
assert(bit < N);
const word_index = bit / Bitmap.bits_per_word;
const bit_index = bit % Bitmap.bits_per_word;
return self.storage[word_index] & bit_mask(Word, bit_index) != 0;
}
/// Count the number of set bits in the bitmap.
pub fn count(self: *const Self) usize {
pub fn count(self: *const Bitmap) usize {
var total: usize = 0;
for (self.storage, 0..) |word, i| {
total += @intCast(@popCount(word & valid_word_mask(i)));
@@ -71,7 +77,7 @@ pub fn BitMap(comptime N: usize) type {
}
pub const Iterator = struct {
bitmap: *const Self,
bitmap: *const Bitmap,
word_index: usize = 0,
word: Word = 0,
@@ -81,25 +87,29 @@ pub fn BitMap(comptime N: usize) type {
if (self.word != 0) {
const bit_index: usize = @intCast(@ctz(self.word));
self.word &= self.word - 1;
return ((self.word_index - 1) * bits_per_word) + bit_index;
return ((self.word_index - 1) * bits_per_word) +
bit_index;
}
if (self.word_index >= arr_len) return null;
self.word = self.bitmap.storage[self.word_index] & valid_word_mask(self.word_index);
self.word = self.bitmap.storage[self.word_index] &
valid_word_mask(self.word_index);
self.word_index += 1;
}
}
};
/// Iterate over the indexes of set bits in ascending order.
pub fn iter(self: *const Self) Iterator {
pub fn iter(self: *const Bitmap) Iterator {
return .{ .bitmap = self };
}
/// Check whether all bits in the bitmap are equal to a given value.
pub fn is_all(self: *const Self, value: bool) bool {
const v: Self.Word = if (value) std.math.maxInt(Self.Word) else 0;
pub fn is_all(self: *const Bitmap, value: bool) bool {
const v: Bitmap.Word = if (value) std.math.maxInt(Bitmap.Word)
else
0;
for (self.storage) |word| {
if (word != v) return false;
}
@@ -108,18 +118,19 @@ pub fn BitMap(comptime N: usize) type {
};
}
/// Atomic static bitmap with the same API as `BitMap`, but each operation accepts a memory ordering.
/// Atomic static bitmap with the same API as `BitMap`, but each operation
/// accepts a memory ordering.
pub fn AtomicBitMap(comptime N: usize) type {
return struct {
pub const Word = if (N > 32) u64 else get_closest_type(N);
const Self = @This();
pub const Word = if (N > 32) u64 else ClosestType(N);
const Bitmap = @This();
const bits_per_word = @bitSizeOf(Word);
const arr_len = (N + bits_per_word - 1) / bits_per_word;
storage: [arr_len]std.atomic.Value(Word),
fn valid_word_mask(word_index: usize) Word {
std.debug.assert(word_index < arr_len);
assert(word_index < arr_len);
if (word_index != arr_len - 1) return std.math.maxInt(Word);
@@ -129,9 +140,9 @@ pub fn AtomicBitMap(comptime N: usize) type {
}
/// Initialize a bitmap, setting all bits to `val`.
pub fn init(val: bool) Self {
pub fn init(val: bool) Bitmap {
var storage: [arr_len]std.atomic.Value(Word) = undefined;
const word: Word = if (val) std.math.maxInt(Self.Word) else 0;
const word: Word = if (val) std.math.maxInt(Bitmap.Word) else 0;
inline for (&storage) |*elem| {
elem.* = std.atomic.Value(Word).init(word);
}
@@ -139,58 +150,86 @@ pub fn AtomicBitMap(comptime N: usize) type {
}
/// Set a given bit.
pub fn set(self: *Self, bit: usize, comptime ordering: std.builtin.AtomicOrder) void {
std.debug.assert(bit < N);
const word_index = bit / Self.bits_per_word;
const bit_index = bit % Self.bits_per_word;
pub fn set(
self: *Bitmap,
bit: usize,
comptime order: AtomicOrder,
) void {
assert(bit < N);
const word_index = bit / Bitmap.bits_per_word;
const bit_index = bit % Bitmap.bits_per_word;
_ = self.storage[word_index].fetchOr(bit_mask(Word, bit_index), ordering);
_ = self.storage[word_index].fetchOr(
bit_mask(Word, bit_index),
order,
);
}
/// Clear a given bit.
pub fn clear(self: *Self, bit: usize, comptime ordering: std.builtin.AtomicOrder) void {
std.debug.assert(bit < N);
const word_index = bit / Self.bits_per_word;
const bit_index = bit % Self.bits_per_word;
pub fn clear(
self: *Bitmap,
bit: usize,
comptime order: AtomicOrder,
) void {
assert(bit < N);
const word_index = bit / Bitmap.bits_per_word;
const bit_index = bit % Bitmap.bits_per_word;
_ = self.storage[word_index].fetchAnd(~bit_mask(Word, bit_index), ordering);
_ = self.storage[word_index].fetchAnd(
~bit_mask(Word, bit_index),
order,
);
}
/// Get a given bit.
pub fn get(self: *const Self, bit: usize, comptime ordering: std.builtin.AtomicOrder) bool {
std.debug.assert(bit < N);
const word_index = bit / Self.bits_per_word;
const bit_index = bit % Self.bits_per_word;
return self.storage[word_index].load(ordering) & bit_mask(Word, bit_index) != 0;
pub fn get(
self: *const Bitmap,
bit: usize,
comptime order: AtomicOrder,
) bool {
assert(bit < N);
const word_index = bit / Bitmap.bits_per_word;
const bit_index = bit % Bitmap.bits_per_word;
return self.storage[word_index].load(order) &
bit_mask(Word, bit_index) != 0;
}
/// Count the number of set bits in the bitmap.
pub fn count(self: *const Self, comptime ordering: std.builtin.AtomicOrder) usize {
pub fn count(
self: *const Bitmap,
comptime order: AtomicOrder,
) usize {
var total: usize = 0;
for (self.storage, 0..) |word, i| {
total += @intCast(@popCount(word.load(ordering) & valid_word_mask(i)));
total += @intCast(@popCount(word.load(order) &
valid_word_mask(i)));
}
return total;
}
pub fn Iterator(comptime ordering: std.builtin.AtomicOrder) type {
pub fn Iterator(comptime order: AtomicOrder) type {
return struct {
bitmap: *const Self,
bitmap: *const Bitmap,
word_index: usize = 0,
word: Word = 0,
/// Return the index of the next set bit, or null when exhausted.
/// Return the index of the next set bit, or null when
/// exhausted.
pub fn next(self: *@This()) ?usize {
while (true) {
if (self.word != 0) {
const bit_index: usize = @intCast(@ctz(self.word));
self.word &= self.word - 1;
return ((self.word_index - 1) * bits_per_word) + bit_index;
return ((self.word_index - 1) * bits_per_word) +
bit_index;
}
if (self.word_index >= arr_len) return null;
self.word = self.bitmap.storage[self.word_index].load(ordering) & valid_word_mask(self.word_index);
const word = &self.bitmap.storage[self.word_index];
self.word = word.load(order) &
valid_word_mask(self.word_index);
self.word_index += 1;
}
}
@@ -198,15 +237,23 @@ pub fn AtomicBitMap(comptime N: usize) type {
}
/// Iterate over the indexes of set bits in ascending order.
pub fn iter(self: *const Self, comptime ordering: std.builtin.AtomicOrder) Iterator(ordering) {
pub fn iter(
self: *const Bitmap,
comptime order: AtomicOrder,
) Iterator(order) {
return .{ .bitmap = self };
}
/// Check whether all bits in the bitmap are equal to a given value.
pub fn is_all(self: *const Self, value: bool, comptime ordering: std.builtin.AtomicOrder) bool {
const v: Self.Word = if (value) std.math.maxInt(Self.Word) else 0;
pub fn is_all(
self: *const Bitmap,
value: bool,
comptime order: AtomicOrder,
) bool {
const v: Bitmap.Word = if (value) std.math.maxInt(Bitmap.Word) else
0;
for (self.storage) |word| {
if (word.load(ordering) != v) return false;
if (word.load(order) != v) return false;
}
return true;
}
@@ -214,37 +261,37 @@ pub fn AtomicBitMap(comptime N: usize) type {
}
test "BitMap has proper underlying type" {
std.debug.assert(BitMap(8).Word == u8);
std.debug.assert(BitMap(10).Word == u16);
std.debug.assert(BitMap(16).Word == u16);
std.debug.assert(BitMap(21).Word == u32);
std.debug.assert(BitMap(32).Word == u32);
std.debug.assert(BitMap(64).Word == u64);
std.debug.assert(BitMap(128).Word == u64);
std.debug.assert(BitMap(1024).Word == u64);
assert(BitMap(8).Word == u8);
assert(BitMap(10).Word == u16);
assert(BitMap(16).Word == u16);
assert(BitMap(21).Word == u32);
assert(BitMap(32).Word == u32);
assert(BitMap(64).Word == u64);
assert(BitMap(128).Word == u64);
assert(BitMap(1024).Word == u64);
}
test BitMap {
var bitmap = BitMap(10).init(false);
std.debug.assert(@TypeOf(bitmap).Word == u16);
std.debug.assert(bitmap.get(0) == false);
assert(@TypeOf(bitmap).Word == u16);
assert(bitmap.get(0) == false);
bitmap.set(0);
std.debug.assert(bitmap.get(0) == true);
assert(bitmap.get(0) == true);
bitmap.clear(0);
std.debug.assert(bitmap.get(0) == false);
assert(bitmap.get(0) == false);
}
test "BitMap count and iter" {
var bitmap = BitMap(70).init(false);
std.debug.assert(bitmap.count() == 0);
assert(bitmap.count() == 0);
{
var it = bitmap.iter();
std.debug.assert(it.next() == null);
assert(it.next() == null);
}
bitmap.set(0);
@@ -253,54 +300,54 @@ test "BitMap count and iter" {
bitmap.set(64);
bitmap.set(69);
std.debug.assert(bitmap.count() == 5);
assert(bitmap.count() == 5);
var it = bitmap.iter();
std.debug.assert(it.next().? == 0);
std.debug.assert(it.next().? == 3);
std.debug.assert(it.next().? == 63);
std.debug.assert(it.next().? == 64);
std.debug.assert(it.next().? == 69);
std.debug.assert(it.next() == null);
assert(it.next().? == 0);
assert(it.next().? == 3);
assert(it.next().? == 63);
assert(it.next().? == 64);
assert(it.next().? == 69);
assert(it.next() == null);
bitmap.clear(63);
std.debug.assert(bitmap.count() == 4);
assert(bitmap.count() == 4);
var all = BitMap(70).init(true);
std.debug.assert(all.count() == 70);
assert(all.count() == 70);
var all_it = all.iter();
var expected: usize = 0;
while (all_it.next()) |bit| : (expected += 1) {
std.debug.assert(bit == expected);
assert(bit == expected);
}
std.debug.assert(expected == 70);
assert(expected == 70);
}
test AtomicBitMap {
var bitmap = AtomicBitMap(10).init(false);
std.debug.assert(@TypeOf(bitmap).Word == u16);
std.debug.assert(bitmap.get(0, .seq_cst) == false);
assert(@TypeOf(bitmap).Word == u16);
assert(bitmap.get(0, .seq_cst) == false);
bitmap.set(0, .seq_cst);
std.debug.assert(bitmap.get(0, .seq_cst) == true);
std.debug.assert(bitmap.is_all(false, .seq_cst) == false);
assert(bitmap.get(0, .seq_cst) == true);
assert(bitmap.is_all(false, .seq_cst) == false);
bitmap.clear(0, .seq_cst);
std.debug.assert(bitmap.get(0, .seq_cst) == false);
std.debug.assert(bitmap.is_all(false, .seq_cst) == true);
assert(bitmap.get(0, .seq_cst) == false);
assert(bitmap.is_all(false, .seq_cst) == true);
}
test "AtomicBitMap count and iter" {
var bitmap = AtomicBitMap(70).init(false);
std.debug.assert(bitmap.count(.seq_cst) == 0);
assert(bitmap.count(.seq_cst) == 0);
{
var it = bitmap.iter(.seq_cst);
std.debug.assert(it.next() == null);
assert(it.next() == null);
}
bitmap.set(0, .seq_cst);
@@ -309,27 +356,27 @@ test "AtomicBitMap count and iter" {
bitmap.set(64, .seq_cst);
bitmap.set(69, .seq_cst);
std.debug.assert(bitmap.count(.seq_cst) == 5);
assert(bitmap.count(.seq_cst) == 5);
var it = bitmap.iter(.seq_cst);
std.debug.assert(it.next().? == 0);
std.debug.assert(it.next().? == 3);
std.debug.assert(it.next().? == 63);
std.debug.assert(it.next().? == 64);
std.debug.assert(it.next().? == 69);
std.debug.assert(it.next() == null);
assert(it.next().? == 0);
assert(it.next().? == 3);
assert(it.next().? == 63);
assert(it.next().? == 64);
assert(it.next().? == 69);
assert(it.next() == null);
bitmap.clear(63, .seq_cst);
std.debug.assert(bitmap.count(.seq_cst) == 4);
assert(bitmap.count(.seq_cst) == 4);
var all = AtomicBitMap(70).init(true);
std.debug.assert(all.count(.seq_cst) == 70);
assert(all.count(.seq_cst) == 70);
var all_it = all.iter(.seq_cst);
var expected: usize = 0;
while (all_it.next()) |bit| : (expected += 1) {
std.debug.assert(bit == expected);
assert(bit == expected);
}
std.debug.assert(expected == 70);
assert(expected == 70);
}
+32 -22
View File
@@ -14,27 +14,29 @@ pub var nil: Node = .{
};
/// Base struct for binary search trees.
/// This should be used as a field in other types that are based on binary search trees, such as red-black trees or AVL trees.
/// This should be used as a field in other types that are based on
/// binary search trees, such as red-black trees or AVL trees.
pub fn BST(comptime cmp: fn (*const Node, *const Node) std.math.Order) type {
return struct {
const Self = @This();
const Tree = @This();
root: *Node = &nil,
pub fn init() Self {
return Self{
pub fn init() Tree {
return Tree{
.root = &nil,
};
}
/// Return whether or not the given node is the sentinel nil node.
pub fn is_nil(self: *Self, node: *Node) bool {
pub fn is_nil(self: *Tree, node: *Node) bool {
_ = self;
return node == &nil;
}
/// Search for a node in the BST that matches `elem`, and return a pointer to it if found.
pub fn search(self: *Self, elem: *Node) ?*Node {
/// Search for a node in the BST that matches `elem`.
/// Return a pointer to it if found.
pub fn search(self: *Tree, elem: *Node) ?*Node {
var node = self.root;
while (!self.is_nil(node)) {
const order = cmp(elem, node);
@@ -49,8 +51,9 @@ pub fn BST(comptime cmp: fn (*const Node, *const Node) std.math.Order) type {
return null;
}
/// Return a pointer to the minimum node in the subtree rooted at `node`.
pub fn minimum(self: *Self, node: *Node) *Node {
/// Return a pointer to the minimum node in the subtree
/// rooted at `node`.
pub fn minimum(self: *Tree, node: *Node) *Node {
var current = node;
while (!self.is_nil(current.left)) {
current = current.left;
@@ -58,8 +61,9 @@ pub fn BST(comptime cmp: fn (*const Node, *const Node) std.math.Order) type {
return current;
}
/// Return a pointer to the maximum node in the subtree rooted at `node`.
pub fn maximum(self: *Self, node: *Node) *Node {
/// Return a pointer to the maximum node in the subtree
/// rooted at `node`.
pub fn maximum(self: *Tree, node: *Node) *Node {
var current = node;
while (!self.is_nil(current.right)) {
current = current.right;
@@ -67,8 +71,9 @@ pub fn BST(comptime cmp: fn (*const Node, *const Node) std.math.Order) type {
return current;
}
/// Return a pointer to the successor of `node`, or nil if `node` is the maximum element in the tree.
pub fn successor(self: *Self, node: *Node) *Node {
/// Return a pointer to the successor of `node`, or nil if `node`
/// is the maximum element in the tree.
pub fn successor(self: *Tree, node: *Node) *Node {
if (!self.is_nil(node.right)) {
// Minimum of right subtree
return self.minimum(node.right);
@@ -76,10 +81,10 @@ pub fn BST(comptime cmp: fn (*const Node, *const Node) std.math.Order) type {
// Go up and find it.
var current = node;
var parent = current.parent.get_ptr();
var parent = current.parent.ptr();
while (!self.is_nil(parent) and current == parent.right) {
current = parent;
parent = parent.parent.get_ptr();
parent = parent.parent.ptr();
}
if (parent == self.root) {
@@ -89,9 +94,9 @@ pub fn BST(comptime cmp: fn (*const Node, *const Node) std.math.Order) type {
return parent;
}
/// Transplant the subtree rooted at `u` with the subtree rooted at `v`. This is used as a helper function.
pub fn transplant(self: *Self, u: *Node, v: *Node) void {
const u_parent = u.parent.get_ptr();
/// Transplant the subtree rooted at `u` with the subtree rooted at `v`.
pub fn transplant(self: *Tree, u: *Node, v: *Node) void {
const u_parent = u.parent.ptr();
if (self.is_nil(u_parent)) {
self.root = v;
} else if (u == u_parent.left) {
@@ -104,7 +109,7 @@ pub fn BST(comptime cmp: fn (*const Node, *const Node) std.math.Order) type {
// Insert `elem` into the BST, if already inserted, do nothing.
// This does not perform any balancing.
pub fn insert(self: *Self, elem: *Node) !void {
pub fn insert(self: *Tree, elem: *Node) !void {
var current = self.root;
if (self.is_nil(current)) {
@@ -142,7 +147,7 @@ pub fn BST(comptime cmp: fn (*const Node, *const Node) std.math.Order) type {
}
/// Return whether or not the tree is empty.
pub fn is_empty(self: *Self) bool {
pub fn is_empty(self: *Tree) bool {
return self.is_nil(self.root);
}
};
@@ -162,7 +167,7 @@ fn node_cmp(a: *const Node, b: *const Node) std.math.Order {
}
test BST {
var tree: BST(node_cmp) = .init();
var tree = BST(node_cmp).init();
var nodes = [_]MyNode{
.{ .value = 5, .node = undefined },
@@ -181,6 +186,11 @@ test BST {
for (&nodes) |*node| {
const found = tree.search(&node.node);
try std.testing.expect(found != null);
try std.testing.expectEqual(node.value, @as(*MyNode, @fieldParentPtr("node", found.?)).value);
const f = found orelse unreachable;
try std.testing.expectEqual(
node.value,
@as(*MyNode, @fieldParentPtr("node", f)).value,
);
}
}
+1 -1
View File
@@ -51,7 +51,7 @@ test "get_number() should return the integer value of a key" {
try std.testing.expectEqual(100, get_number(cmdline, "baz").?);
}
test "get_number() should return null if the key is not found or value is not an integer" {
test "get_number() returns null if the key not found or value not an integer" {
const cmdline = "foo=42 baz=qux";
try std.testing.expect(get_number(cmdline, "quux") == null);
try std.testing.expect(get_number(cmdline, "baz") == null);
+39 -19
View File
@@ -1,15 +1,15 @@
//! Lock-free singly linked list with an activation callback when items are first inserted.
//! This is meant for use where there is a set of objects being worked upon in an
//! asynchronous context.
//! Lock-free singly linked list with an activation callback invoked when items
//! are first inserted.
//! This is meant for use where there is a set of objects being worked upon in
//! an asynchronous context.
//! Data structure generalized from the NT kernel reaper lists, and inspired by
//! MINTIA's custody list (https://github.com/xrarch/mintia2/blob/main/OS/Executive/Ke/KeCustodyList.jkl)
//! MINTIA's custody list (https://github.com/xrarch/mintia2).
const std = @import("std");
pub const HandoffList = struct {
const ActivationFn = *const fn (*Self) void;
const Self = @This();
const ActivationFn = *const fn (*HandoffList) void;
const Callback = *const fn (obj: *anyopaque, ctx: ?*anyopaque) void;
/// Non-null value indicating processing is in progress.
const processing_tag: *anyopaque = @ptrFromInt(1);
@@ -17,21 +17,26 @@ pub const HandoffList = struct {
activation: ActivationFn,
head: std.atomic.Value(?*anyopaque),
pub fn init(activation: ActivationFn) Self {
pub fn init(activation: ActivationFn) HandoffList {
return .{
.activation = activation,
.head = .init(null),
.head = std.atomic.Value(?*anyopaque).init(null),
};
}
/// Insert an element at the head of the list.
/// If the list was previously empty, the activation routine will be called.
pub fn insert(self: *Self, link: *?*anyopaque) void {
pub fn insert(self: *HandoffList, link: *?*anyopaque) void {
var head = self.head.load(.monotonic);
while (true) {
link.* = head;
head = self.head.cmpxchgWeak(head, @ptrCast(link), .release, .monotonic) orelse break;
head = self.head.cmpxchgWeak(
head,
@ptrCast(link),
.release,
.monotonic,
) orelse break;
}
if (head == null) {
@@ -43,17 +48,27 @@ pub const HandoffList = struct {
/// Pop the entire list and process it.
/// The callback takes the object pointer as its first parameter
/// and ctx as its second.
pub fn process(self: *Self, callback: *const fn (*anyopaque, ?*anyopaque) void, ctx: ?*anyopaque) void {
pub fn process(
self: *HandoffList,
callback: Callback,
ctx: ?*anyopaque,
) void {
while (true) {
const head = self.head.swap(processing_tag, .acquire);
// Someone else is already processing it (or we set it previously and got nothing).
// Someone else is already processing it, or we set it previously
// and got nothing.
if (head == processing_tag) return;
// List is empty; this shouldn't happen, but it'll just return on the next
// iteration if it is truly empty.
// List is empty; this shouldn't happen, but it'll just return
// on the next iteration if it is truly empty.
if (head == null) {
_ = self.head.cmpxchgStrong(processing_tag, null, .release, .monotonic) orelse return;
_ = self.head.cmpxchgStrong(
processing_tag,
null,
.release,
.monotonic,
) orelse return;
continue;
}
@@ -68,7 +83,12 @@ pub const HandoffList = struct {
}
// If the cmpxchg fails, a new item was added; also process it.
_ = self.head.cmpxchgStrong(processing_tag, null, .release, .monotonic) orelse return;
_ = self.head.cmpxchgStrong(
processing_tag,
null,
.release,
.monotonic,
) orelse return;
}
}
};
@@ -99,7 +119,7 @@ fn test_activation(list: *HandoffList) void {
fn record_node(node_ptr: *anyopaque, ctx_ptr: ?*anyopaque) void {
const node: *TestNode = @ptrCast(@alignCast(node_ptr));
const ctx: *TestContext = @ptrCast(@alignCast(ctx_ptr.?));
const ctx: *TestContext = @ptrCast(@alignCast(ctx_ptr orelse unreachable));
ctx.processed[ctx.processed_len] = node.value;
ctx.processed_len += 1;
@@ -107,7 +127,7 @@ fn record_node(node_ptr: *anyopaque, ctx_ptr: ?*anyopaque) void {
fn record_node_and_insert(node_ptr: *anyopaque, ctx_ptr: ?*anyopaque) void {
const node: *TestNode = @ptrCast(@alignCast(node_ptr));
const ctx: *TestContext = @ptrCast(@alignCast(ctx_ptr.?));
const ctx: *TestContext = @ptrCast(@alignCast(ctx_ptr orelse unreachable));
ctx.processed[ctx.processed_len] = node.value;
ctx.processed_len += 1;
+4 -3
View File
@@ -1,3 +1,5 @@
//! Linker sets, inspired by FreeBSD.
const std = @import("std");
/// Used for declaring global objects that get collected by the linker
@@ -13,8 +15,6 @@ pub fn LinkerSet(name: []const u8, comptime T: type) type {
const start = @extern(*u8, .{ .name = "__start_set_" ++ name });
const end = @extern(*u8, .{ .name = "__stop_set_" ++ name });
const Self = @This();
pub fn count() usize {
return (@intFromPtr(end) - @intFromPtr(start)) / @sizeOf(T);
}
@@ -41,7 +41,8 @@ pub fn LinkerSet(name: []const u8, comptime T: type) type {
@export(
@as(*const usize, @ptrCast(&storage)),
.{
.name = "_set_elem_" ++ name ++ "_" ++ @typeName(Tag(elem)),
.name = "_set_elem_" ++ name ++ "_" ++
@typeName(Tag(elem)),
.section = "set_" ++ name,
},
);
+16 -12
View File
@@ -1,5 +1,6 @@
//! Intrusive Pairing Heap implementation.
const std = @import("std");
const assert = std.debug.assert;
pub const Node = struct {
child: ?*Node = null,
@@ -7,9 +8,12 @@ pub const Node = struct {
prev: ?*Node = null,
};
pub fn PairingHeap(comptime order: enum { min, max }, comptime cmp: fn (*Node, *Node) std.math.Order) type {
pub fn PairingHeap(
comptime order: enum { Min, Max },
comptime cmp: fn (*Node, *Node) std.math.Order,
) type {
return struct {
const Self = @This();
const Heap = @This();
/// Number of elements in the heap
size: usize = 0,
@@ -19,8 +23,8 @@ pub fn PairingHeap(comptime order: enum { min, max }, comptime cmp: fn (*Node, *
fn meld(a: *Node, b: *Node) *Node {
const a_wins = switch (order) {
.min => cmp(a, b) != .gt, // a <= b
.max => cmp(a, b) == .gt, // a > b
.Min => cmp(a, b) != .gt, // a <= b
.Max => cmp(a, b) == .gt, // a > b
};
if (a_wins) {
@@ -70,7 +74,7 @@ pub fn PairingHeap(comptime order: enum { min, max }, comptime cmp: fn (*Node, *
}
// Second pass: fold pairs right to left
var result = pairs.?;
var result = pairs orelse unreachable;
var rest = result.next;
result.next = null;
while (rest) |p| {
@@ -81,7 +85,7 @@ pub fn PairingHeap(comptime order: enum { min, max }, comptime cmp: fn (*Node, *
return result;
}
pub fn init() Self {
pub fn init() Heap {
return .{
.size = 0,
.root = null,
@@ -89,7 +93,7 @@ pub fn PairingHeap(comptime order: enum { min, max }, comptime cmp: fn (*Node, *
}
/// Insert `node` into the heap.
pub fn insert(self: *Self, node: *Node) void {
pub fn insert(self: *Heap, node: *Node) void {
node.* = .{
.child = null,
.next = null,
@@ -107,7 +111,7 @@ pub fn PairingHeap(comptime order: enum { min, max }, comptime cmp: fn (*Node, *
}
/// Return and remove the topmost element.
pub fn pop(self: *Self) ?*Node {
pub fn pop(self: *Heap) ?*Node {
const root = self.root orelse return null;
const child = root.child;
@@ -125,13 +129,13 @@ pub fn PairingHeap(comptime order: enum { min, max }, comptime cmp: fn (*Node, *
}
/// Remove `node` from the heap.
pub fn remove(self: *Self, node: *Node) void {
pub fn remove(self: *Heap, node: *Node) void {
if (self.root == node) {
_ = self.pop();
return;
}
std.debug.assert(self.root != null);
assert(self.root != null);
self.size -= 1;
@@ -174,8 +178,8 @@ fn my_cmp(a: *Node, b: *Node) std.math.Order {
return std.math.order(ta.value, tb.value);
}
const MinHeap = PairingHeap(.min, my_cmp);
const MaxHeap = PairingHeap(.max, my_cmp);
const MinHeap = PairingHeap(.Min, my_cmp);
const MaxHeap = PairingHeap(.Max, my_cmp);
test "empty heap" {
var heap = MinHeap.init();
+42 -32
View File
@@ -1,5 +1,6 @@
//! Red-Black Tree implementation mostly based on pseudocode from CLRS.
//! Refer to the book for details on the algorithms and properties of red-black trees.
//! Refer to the book for details on the algorithms and properties of red-black
//! trees.
const bst = @import("bst.zig");
const std = @import("std");
@@ -7,22 +8,24 @@ const black: u2 = 0;
const red: u2 = 1;
fn get_color(node: *bst.Node) u2 {
return node.parent.get_tag();
return node.parent.tag();
}
fn set_color(node: *bst.Node, color: u2) void {
node.parent.set_tag(color);
}
pub fn RBTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Order) type {
pub fn RBTree(
comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Order,
) type {
return struct {
tree: bst.BST(cmp),
const Self = @This();
const Tree = @This();
fn rotate_right(self: *Self, node: *bst.Node) void {
fn rotate_right(self: *Tree, node: *bst.Node) void {
var y = node.left;
const parent = node.parent.get_ptr();
const parent = node.parent.ptr();
node.left = y.right;
@@ -44,9 +47,9 @@ pub fn RBTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Order
node.parent.set_ptr(y);
}
fn rotate_left(self: *Self, node: *bst.Node) void {
fn rotate_left(self: *Tree, node: *bst.Node) void {
var y = node.right;
const parent = node.parent.get_ptr();
const parent = node.parent.ptr();
node.right = y.left;
@@ -68,16 +71,19 @@ pub fn RBTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Order
node.parent.set_ptr(y);
}
pub fn init() Self {
return Self{
pub fn init() Tree {
return Tree{
.tree = bst.BST(cmp).init(),
};
}
/// Insert the node `elem` into the tree.
pub fn insert(self: *Self, elem: *bst.Node) !void {
pub fn insert(self: *Tree, elem: *bst.Node) !void {
if (self.tree.is_empty()) {
self.tree.insert(elem) catch unreachable;
self.tree.insert(elem) catch {
// This can't happen as the tree is empty.
unreachable;
};
set_color(elem, black);
return;
}
@@ -90,11 +96,11 @@ pub fn RBTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Order
set_color(elem, red);
var node = elem;
var parent = node.parent.get_ptr();
var parent = node.parent.ptr();
// The tree might be unbalanced, rebalance it.
while (get_color(parent) == red) {
var grandparent = parent.parent.get_ptr();
var grandparent = parent.parent.ptr();
// The parent of the node is a left child
if (parent == grandparent.left) {
@@ -108,19 +114,19 @@ pub fn RBTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Order
set_color(uncle, black);
set_color(grandparent, red);
node = grandparent;
parent = node.parent.get_ptr();
parent = node.parent.ptr();
} else {
// Otherwise, if the node is a right child, rotate left
// to make it a left child.
if (node == parent.right) {
node = parent;
self.rotate_left(node);
parent = node.parent.get_ptr();
grandparent = parent.parent.get_ptr();
parent = node.parent.ptr();
grandparent = parent.parent.ptr();
}
// Then, recolor the parent and grandparent and rotate right
// to maintain the properties of the red-black tree.
// Then, recolor the parent and grandparent and rotate
// right to maintain the invariants.
set_color(parent, black);
set_color(grandparent, red);
self.rotate_right(grandparent);
@@ -134,13 +140,13 @@ pub fn RBTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Order
set_color(uncle, black);
set_color(grandparent, red);
node = grandparent;
parent = node.parent.get_ptr();
parent = node.parent.ptr();
} else {
if (node == parent.left) {
node = parent;
self.rotate_right(node);
parent = node.parent.get_ptr();
grandparent = parent.parent.get_ptr();
parent = node.parent.ptr();
grandparent = parent.parent.ptr();
}
set_color(parent, black);
@@ -148,7 +154,7 @@ pub fn RBTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Order
self.rotate_left(grandparent);
}
parent = node.parent.get_ptr();
parent = node.parent.ptr();
}
}
@@ -156,7 +162,7 @@ pub fn RBTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Order
}
/// Delete the node `elem` from the tree.
pub fn delete(self: *Self, elem: *bst.Node) void {
pub fn delete(self: *Tree, elem: *bst.Node) void {
var node = elem;
var child = &bst.nil;
var orig_color = get_color(node);
@@ -175,7 +181,7 @@ pub fn RBTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Order
orig_color = get_color(node);
child = node.right;
if (node.parent.get_ptr() == elem) {
if (node.parent.ptr() == elem) {
child.parent.set_ptr(node);
} else {
self.tree.transplant(node, node.right);
@@ -198,7 +204,7 @@ pub fn RBTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Order
// Fix the tree
while (node != self.tree.root and get_color(node) == black) {
var parent = node.parent.get_ptr();
var parent = node.parent.ptr();
if (node == parent.left) {
var sibling = parent.right;
@@ -208,11 +214,13 @@ pub fn RBTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Order
set_color(parent, red);
self.rotate_left(parent);
parent = node.parent.get_ptr();
parent = node.parent.ptr();
sibling = parent.right;
}
if (get_color(sibling.left) == black and get_color(sibling.right) == black) {
if (get_color(sibling.left) == black and
get_color(sibling.right) == black)
{
set_color(sibling, red);
node = parent;
} else {
@@ -220,7 +228,7 @@ pub fn RBTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Order
set_color(sibling.left, black);
set_color(sibling, red);
self.rotate_right(sibling);
parent = node.parent.get_ptr();
parent = node.parent.ptr();
sibling = parent.right;
}
@@ -238,11 +246,13 @@ pub fn RBTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Order
set_color(parent, red);
self.rotate_right(parent);
parent = node.parent.get_ptr();
parent = node.parent.ptr();
sibling = parent.left;
}
if (get_color(sibling.left) == black and get_color(sibling.right) == black) {
if (get_color(sibling.left) == black and
get_color(sibling.right) == black)
{
set_color(sibling, red);
node = parent;
} else {
@@ -250,7 +260,7 @@ pub fn RBTree(comptime cmp: fn (*const bst.Node, *const bst.Node) std.math.Order
set_color(sibling.right, black);
set_color(sibling, red);
self.rotate_left(sibling);
parent = node.parent.get_ptr();
parent = node.parent.ptr();
sibling = parent.left;
}
+65 -51
View File
@@ -22,6 +22,45 @@ pub fn comptime_error(comptime msg: []const u8, args: anytype) void {
@compileError(std.fmt.comptimePrint(msg, args));
}
const InterfaceError = enum {
MissingField,
FieldType,
MissingMethod,
ParameterCount,
ReturnType,
ParameterType,
MissingType,
TypeDeclaration,
MissingVariable,
VariableType,
};
fn interface_error(comptime err: InterfaceError, args: anytype) void {
const msg = switch (err) {
.MissingField => "Expected field '{s}' of type '{s}' in type '{s}'" ++
"required by '{s}'",
.FieldType => "Expected field '{s}' of type '{s}' in type '{s}'" ++
"required by '{s}', got '{s}'",
.MissingMethod => "Expected method '{s}' in type '{s}' " ++
"required by '{s}'",
.ParameterCount => "Parameter count mismatch in method '{s}' " ++
"in type '{s}'" ++ "required by '{s}'",
.ReturnType => "Return type mismatch in method '{s}' in type '{s}'" ++
"required by '{s}'",
.ParameterType => "Parameter type mismatch in method '{s}' in " ++
"type '{s}' required by '{s}'",
.MissingType => "Expected type declaration '{s}' in type '{s}'" ++
"required by '{s}'",
.TypeDeclaration => "Declaration '{s}' in type '{s}' " ++
"is required to be a" ++ "type by '{s}', but got '{s}'",
.MissingVariable => "Expected variable declaration '{s}' " ++
"in type '{s}'" ++ "required by '{s}'",
.VariableType => "Declaration '{s}' in type '{s}' is required to be" ++
"a variable of type '{s}' by '{s}', but got '{s}'",
};
comptime_error(msg, args);
}
/// Asserts that a given type `T` matches the schema declared by `I`.
/// This includes public methods and fields.
pub fn assert_interface(T: type, I: type) void {
@@ -31,19 +70,14 @@ pub fn assert_interface(T: type, I: type) void {
inline for (info.field_names, info.field_types) |name, t| {
if (!@hasField(T, name)) {
comptime_error(
"Expected field '{s}' of type '{s}' in type '{s}' required by '{s}'",
.{
name,
@typeName(t),
@typeName(T),
@typeName(I),
},
interface_error(
.MissingField,
.{ name, @typeName(t), @typeName(T), @typeName(I) },
);
} else {
if (@FieldType(T, name) != t) {
comptime_error(
"Expected field '{s}' of type '{s}' in type '{s}' required by '{s}', got '{s}'",
interface_error(
.FieldType,
.{
name,
@typeName(t),
@@ -61,8 +95,8 @@ pub fn assert_interface(T: type, I: type) void {
if (@typeInfo(@TypeOf(member)) == .@"fn") {
if (!@hasDecl(T, decl)) {
comptime_error(
"Expected method '{s}' in type '{s}' required by '{s}'",
interface_error(
.MissingMethod,
.{
decl,
@typeName(T),
@@ -80,55 +114,39 @@ pub fn assert_interface(T: type, I: type) void {
const impl_fn = @typeInfo(ImplFnType).@"fn";
if (iface_fn.param_types.len != impl_fn.param_types.len) {
comptime_error(
"Parameter count mismatch in method '{s}' in type '{s}' required by '{s}'",
.{
decl,
@typeName(T),
@typeName(I),
},
interface_error(
.ParameterCount,
.{ decl, @typeName(T), @typeName(I) },
);
}
if (iface_fn.return_type != impl_fn.return_type) {
comptime_error(
"Return type mismatch in method '{s}' in type '{s}' required by '{s}'",
.{
decl,
@typeName(T),
@typeName(I),
},
interface_error(
.ReturnType,
.{ decl, @typeName(T), @typeName(I) },
);
}
for (iface_fn.param_types, impl_fn.param_types) |a, b| {
if (a != b) {
comptime_error(
"Parameter type mismatch in method '{s}' in type '{s}' required by '{s}'",
.{
decl,
@typeName(T),
@typeName(I),
},
interface_error(
.ParameterType,
.{ decl, @typeName(T), @typeName(I) },
);
}
}
}
} else if (@TypeOf(member) == type) {
if (!@hasDecl(T, decl)) {
comptime_error(
"Expected type declaration '{s}' in type '{s}' required by '{s}'",
.{
decl,
@typeName(T),
@typeName(I),
},
interface_error(
.MissingType,
.{ decl, @typeName(T), @typeName(I) },
);
}
const impl_member = @field(T, decl);
if (@TypeOf(impl_member) != type) {
comptime_error(
"Declaration '{s}' in type '{s}' is required to be a type by '{s}', but got '{s}'",
interface_error(
.TypeDeclaration,
.{
decl,
@typeName(T),
@@ -139,20 +157,16 @@ pub fn assert_interface(T: type, I: type) void {
}
} else {
if (!@hasDecl(T, decl)) {
comptime_error(
"Expected variable declaration '{s}' in type '{s}' required by '{s}'",
.{
decl,
@typeName(T),
@typeName(I),
},
interface_error(
.MissingVariable,
.{ decl, @typeName(T), @typeName(I) },
);
}
const impl_member = @field(T, decl);
if (@TypeOf(impl_member) != @TypeOf(member)) {
comptime_error(
"Declaration '{s}' in type '{s}' is required to be a variable of type '{s}' by '{s}', but got '{s}'",
interface_error(
.VariableType,
.{
decl,
@typeName(T),
+11 -7
View File
@@ -1,18 +1,20 @@
//! Sequence locks.
const std = @import("std");
const rtl = @import("rtl");
pub fn SeqLock(comptime T: type) type {
return struct {
const Self = @This();
const Lock = @This();
raw: T,
sequence: std.atomic.Value(usize),
pub fn init(val: T) Self {
return .{ .raw = val, .sequence = .init(0) };
pub fn init(val: T) Lock {
return .{ .raw = val, .sequence = std.atomic.Value(usize).init(0) };
}
pub fn load(self: *Self) T {
pub fn load(self: *Lock) T {
var ret: T = undefined;
while (true) {
@@ -25,7 +27,8 @@ pub fn SeqLock(comptime T: type) type {
// Relaxed load memcpy is needed here since data races are UB.
rtl.barrier.atomic_load_memcpy(&ret, &self.raw, .monotonic);
// Ensure the sequence read happens *after* the data is fully loaded.
// Ensure the sequence read happens *after* the data is fully
// loaded.
rtl.barrier.fence(.acquire);
if (self.sequence.load(.monotonic) == seq)
@@ -37,12 +40,13 @@ pub fn SeqLock(comptime T: type) type {
return ret;
}
pub fn store(self: *Self, data: T) void {
pub fn store(self: *Lock, data: T) void {
const seq = self.sequence.load(.monotonic);
self.sequence.store(seq + 1, .monotonic);
// Ensure the data write happens *after* the sequence is incremented.
// Ensure the data write happens *after* the sequence
// is incremented.
rtl.barrier.fence(.release);
// Relaxed store memcpy is needed here since data races are UB.
+16 -13
View File
@@ -4,32 +4,35 @@ pub fn TaggedPtr(comptime T: type) type {
return struct {
value: usize,
const Self = @This();
const Ptr = @This();
const mask: usize = 0x3;
pub fn init(ptr: *T, tag: u2) Self {
return Self{ .value = (@intFromPtr(ptr) & ~mask) | (@as(usize, tag) & mask) };
pub fn init(ptr_val: *T, tag_val: u2) Ptr {
return Ptr{
.value = (@intFromPtr(ptr_val) & ~mask) |
(@as(usize, tag_val) & mask),
};
}
/// Return the pointer with the tag bits masked out.
pub fn get_ptr(self: *const Self) *T {
pub fn ptr(self: *const Ptr) *T {
return @ptrFromInt(self.value & ~mask);
}
/// Set the tag bits to `tag`, while preserving the pointer.
pub fn set_tag(self: *Self, tag: u2) void {
self.value = (self.value & ~mask) | (@as(usize, tag) & mask);
pub fn set_tag(self: *Ptr, val: u2) void {
self.value = (self.value & ~mask) | (@as(usize, val) & mask);
}
/// Return the tag bits.
pub fn get_tag(self: *const Self) u2 {
pub fn tag(self: *const Ptr) u2 {
return @as(u2, @truncate(self.value & mask));
}
/// Set the pointer to `ptr`, while preserving the tag bits.
pub fn set_ptr(self: *Self, ptr: *T) void {
self.value = (@intFromPtr(ptr) & ~mask) | (self.value & mask);
pub fn set_ptr(self: *Ptr, val: *T) void {
self.value = (@intFromPtr(val) & ~mask) | (self.value & mask);
}
};
}
@@ -40,13 +43,13 @@ test TaggedPtr {
var x: u32 = 0;
var tagged = TaggedPtr(u32).init(&x, 2);
try std.testing.expectEqual(&x, tagged.get_ptr());
try std.testing.expectEqual(2, tagged.get_tag());
try std.testing.expectEqual(&x, tagged.ptr());
try std.testing.expectEqual(2, tagged.tag());
tagged.set_tag(1);
try std.testing.expectEqual(1, tagged.get_tag());
try std.testing.expectEqual(1, tagged.tag());
var y: u32 = 0;
tagged.set_ptr(&y);
try std.testing.expectEqual(&y, tagged.get_ptr());
try std.testing.expectEqual(&y, tagged.ptr());
}
+15 -8
View File
@@ -3,7 +3,11 @@ const std = @import("std");
const Dimension = enum { Time };
fn scale_value(comptime from_scale: u64, comptime to_scale: u64, value: u64) u64 {
fn scale_value(
comptime from_scale: u64,
comptime to_scale: u64,
value: u64,
) u64 {
if (comptime from_scale >= to_scale) {
return value * comptime (from_scale / to_scale);
} else {
@@ -17,7 +21,7 @@ pub fn Unit(
comptime unit_suffix: []const u8,
) type {
return struct {
const Self = @This();
const UnitType = @This();
pub const dimension = dim;
pub const base_per_unit = scale;
@@ -28,27 +32,30 @@ pub fn Unit(
if (!(@typeInfo(T) == .@"struct" and @hasDecl(T, "dimension")))
@compileError(@typeName(T) ++ " is not a unit");
if (T.dimension != dim)
@compileError("cannot convert between " ++ @tagName(T.dimension) ++
@compileError("cannot convert between " ++
@tagName(T.dimension) ++
" and " ++ @tagName(dim));
}
}
pub fn init(value: u64) Self {
pub fn init(value: u64) UnitType {
return .{ .value = value };
}
pub fn to(self: Self, comptime T: type) T {
pub fn to(self: UnitType, comptime T: type) T {
check(T);
return T.init(scale_value(scale, T.base_per_unit, self.value));
}
pub fn from(other: anytype) Self {
pub fn from(other: anytype) UnitType {
const From = @TypeOf(other);
check(From);
return Self.init(scale_value(From.base_per_unit, scale, other.value));
return UnitType.init(
scale_value(From.base_per_unit, scale, other.value),
);
}
pub inline fn format(self: Self, writer: *std.Io.Writer) !void {
pub inline fn format(self: UnitType, writer: *std.Io.Writer) !void {
try writer.print("{} {s}", .{ self.value, unit_suffix });
}
};
+9 -28
View File
@@ -117,17 +117,6 @@ const Errors = struct {
);
}
pub fn add_orelse_unreachable(
errors: *Errors,
file: SourceFile,
line_num: usize,
) void {
errors.emit(
"{s}:{d}: error: use 'orelse unreachable' instead of .?\n",
.{ file.path, line_num },
);
}
pub fn add_function_too_long(
errors: *Errors,
name: []const u8,
@@ -226,6 +215,12 @@ const SourceFile = struct {
fn tidy_file(gpa: Allocator, file: SourceFile, errors: *Errors) !void {
if (!file.has_extension(".zig")) return;
if (std.mem.eql(u8, file.path, "build.zig")) return;
if (std.mem.eql(u8, file.path, "build/config.zig")) return;
if (std.mem.eql(u8, file.path, "build/image.zig")) return;
if (std.mem.eql(u8, file.path, "build/run.zig")) return;
if (std.mem.eql(u8, file.path, "build/ksyms.zig")) return;
if (std.mem.eql(u8, file.path, "src/tidy.zig")) return;
// Tidy lines.
var line_iterator = std.mem.splitScalar(u8, file.text, '\n');
@@ -264,7 +259,6 @@ fn tidy_line(
fn tidy_ast(file: SourceFile, tree: std.zig.Ast, errors: *Errors) void {
tidy_defer(file, tree, errors);
tidy_optional_unwrap(file, tree, errors);
for (tree.rootDecls()) |decl| {
if (tree.nodeTag(decl) != .fn_decl) continue;
@@ -358,27 +352,14 @@ fn function_has_reference(
return false;
}
fn tidy_optional_unwrap(file: SourceFile, tree: std.zig.Ast, errors: *Errors) void {
for (0..tree.nodes.len) |index| {
const node: std.zig.Ast.Node.Index = @enumFromInt(index);
if (tree.nodeTag(node) != .@"orelse") continue;
const rhs = tree.nodeData(node).node_and_node[1];
if (tree.nodeTag(rhs) != .unreachable_literal) continue;
const line_num = tree.tokenLocation(0, tree.nodeMainToken(node)).line +
1;
errors.add_orelse_unreachable(file, line_num);
}
}
fn tidy_banned(file: SourceFile, errors: *Errors) void {
const ban_list: []const struct { []const u8, []const u8 } = &.{
.{ "debug.assert(", "unqualified assert" },
.{ "Self = @This()", "proper type name" },
.{ "catch unreachable", "proper error handling or documentation" },
.{ "catch {}", "proper error handling or documentation" },
.{ "= .init", "full type name" },
.{ "= .init(", "full type name" },
.{ ".?", "orelse unreachable" },
};
for (ban_list) |ban_item| {
@@ -392,7 +373,7 @@ fn tidy_banned(file: SourceFile, errors: *Errors) void {
fn tidy_defer(file: SourceFile, tree: std.zig.Ast, errors: *Errors) void {
for (0..tree.nodes.len) |index| {
const node: std.zig.Ast.Node.Index = @enumFromInt(index);
const node: std.zig.Ast.Node.Index = @fromBackingInt(@intCast(index));
if (tree.nodeTag(node) != .@"defer" and
tree.nodeTag(node) != .@"errdefer") continue;