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