lib/sys/src/capabilities.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const builtin = @import("builtin");
3
4 pub const current = Capabilities.fromBuiltin();
5
6 pub const JitDebugSupport = struct {
7 supported: bool,
8 reason: ?[]const u8 = null,
9 note: ?[]const u8 = null,
10 };
11
12 pub const CapabilityTier = enum {
13 pure,
14 no_libc,
15 host,
16 };
17
18 pub const Capability = enum {
19 allocator,
20 descriptors,
21 dynamic_loading,
22 environment,
23 filesystem,
24 libc_interop,
25 memory_mapping,
26 networking,
27 process,
28 random,
29 signal,
30 standard_streams,
31 terminal,
32 threads,
33 time,
34 audio,
35 event_loop,
36 performance_counters,
37 platform_frameworks,
38 tls,
39 virtualization,
40 };
41
42 pub const capability_count = @typeInfo(Capability).@"enum".field_names.len;
43
44 pub const CapabilitySet = struct {
45 bits: [capability_count]bool = @as([capability_count]bool, @splat(false)),
46
47 pub const empty: CapabilitySet = .{};
48
49 pub fn of(comptime values: []const Capability) CapabilitySet {
50 var set: CapabilitySet = .{};
51 inline for (values) |value| set.add(value);
52 return set;
53 }
54
55 pub fn add(self: *CapabilitySet, capability: Capability) void {
56 self.bits[indexOf(capability)] = true;
57 }
58
59 pub fn with(self: CapabilitySet, capability: Capability) CapabilitySet {
60 var next = self;
61 next.add(capability);
62 return next;
63 }
64
65 pub fn contains(self: CapabilitySet, capability: Capability) bool {
66 return self.bits[indexOf(capability)];
67 }
68
69 pub fn containsAll(self: CapabilitySet, required: CapabilitySet) bool {
70 inline for (
71 @typeInfo(Capability).@"enum".field_names,
72 @typeInfo(Capability).@"enum".field_values,
73 ) |field_name, field_name_value| {
74 const field = .{ .name = field_name, .value = field_name_value };
75 const capability: Capability = @fromBackingInt(@intCast(field.value));
76 if (required.contains(capability) and !self.contains(capability)) return false;
77 }
78 return true;
79 }
80
81 pub fn count(self: CapabilitySet) usize {
82 var result: usize = 0;
83 inline for (
84 @typeInfo(Capability).@"enum".field_names,
85 @typeInfo(Capability).@"enum".field_values,
86 ) |field_name, field_name_value| {
87 const field = .{ .name = field_name, .value = field_name_value };
88 const capability: Capability = @fromBackingInt(@intCast(field.value));
89 if (self.contains(capability)) result += 1;
90 }
91 return result;
92 }
93
94 pub fn isEmpty(self: CapabilitySet) bool {
95 return self.count() == 0;
96 }
97
98 pub fn names(self: CapabilitySet, storage: *[capability_count][]const u8) []const []const u8 {
99 var len: usize = 0;
100 inline for (
101 @typeInfo(Capability).@"enum".field_names,
102 @typeInfo(Capability).@"enum".field_values,
103 ) |field_name, field_name_value| {
104 const field = .{ .name = field_name, .value = field_name_value };
105 const capability: Capability = @fromBackingInt(@intCast(field.value));
106 if (self.contains(capability)) {
107 storage[len] = field.name;
108 len += 1;
109 }
110 }
111 return storage[0..len];
112 }
113
114 fn indexOf(capability: Capability) usize {
115 return @backingInt(capability);
116 }
117 };
118
119 pub const Requirement = struct {
120 tier: CapabilityTier = .pure,
121 capabilities: CapabilitySet = .{},
122
123 pub fn requires(self: Requirement, capability: Capability) bool {
124 return self.capabilities.contains(capability);
125 }
126
127 pub fn containsAll(self: Requirement, required: Requirement) bool {
128 if (@backingInt(self.tier) < @backingInt(required.tier)) return false;
129 return self.capabilities.containsAll(required.capabilities);
130 }
131
132 pub fn requiresPlatformEnvironment(self: Requirement) bool {
133 return self.tier != .pure or !self.capabilities.isEmpty();
134 }
135 };
136
137 pub fn pure() Requirement {
138 return .{};
139 }
140
141 pub fn noLibc(comptime values: []const Capability) Requirement {
142 return .{ .tier = .no_libc, .capabilities = CapabilitySet.of(values) };
143 }
144
145 pub fn host(comptime values: []const Capability) Requirement {
146 return .{ .tier = .host, .capabilities = CapabilitySet.of(values) };
147 }
148
149 pub fn require(comptime tier: CapabilityTier, comptime values: []const Capability) Requirement {
150 return .{ .tier = tier, .capabilities = CapabilitySet.of(values) };
151 }
152
153 pub const Capabilities = struct {
154 os: std.Target.Os.Tag,
155 arch: std.Target.Cpu.Arch,
156 abi: std.Target.Abi = .none,
157 single_threaded: bool = false,
158
159 pub fn fromBuiltin() Capabilities {
160 return .{
161 .os = builtin.os.tag,
162 .arch = builtin.cpu.arch,
163 .abi = builtin.abi,
164 .single_threaded = builtin.single_threaded,
165 };
166 }
167
168 pub fn isWasm(self: Capabilities) bool {
169 return self.arch.isWasm();
170 }
171
172 pub fn archName(self: Capabilities) []const u8 {
173 return @tagName(self.arch);
174 }
175
176 pub fn endianName(self: Capabilities) []const u8 {
177 return @tagName(self.arch.endian());
178 }
179
180 pub fn pointerWidthBits(self: Capabilities) u16 {
181 return std.Target.ptrBitWidth_arch_abi(self.arch, self.abi);
182 }
183
184 pub fn isFreestanding(self: Capabilities) bool {
185 return self.os == .freestanding;
186 }
187
188 pub fn isLinux(self: Capabilities) bool {
189 return self.os == .linux;
190 }
191
192 pub fn isBrowserish(self: Capabilities) bool {
193 return self.isWasm() and self.isFreestanding();
194 }
195
196 pub fn supportsThreads(self: Capabilities) bool {
197 return !self.single_threaded;
198 }
199
200 pub fn supportsX86_64Execution(self: Capabilities) bool {
201 return self.arch == .x86_64;
202 }
203
204 pub fn supportsAarch64Execution(self: Capabilities) bool {
205 return self.arch == .aarch64;
206 }
207
208 pub fn supportsAppleMetal(self: Capabilities) bool {
209 return self.os == .macos;
210 }
211
212 pub fn supportsMoltenVk(self: Capabilities) bool {
213 return self.os == .macos;
214 }
215
216 pub fn jitDebugSupport(self: Capabilities) JitDebugSupport {
217 if (self.os != .linux and self.os != .macos) {
218 return .{
219 .supported = false,
220 .reason = "JIT debug registration is only available on Linux/macOS",
221 };
222 }
223 if (self.arch != .x86_64 and self.arch != .aarch64) {
224 return .{
225 .supported = false,
226 .reason = "JIT debug registration is only available on x86_64/aarch64",
227 };
228 }
229 const note: ?[]const u8 = if (self.os == .macos)
230 "lldb requires `settings set plugin.jit-loader.gdb.enable on` to load JIT debug objects"
231 else
232 null;
233 return .{ .supported = true, .note = note };
234 }
235
236 pub fn supportsJitDebugInfo(self: Capabilities) bool {
237 return self.jitDebugSupport().supported;
238 }
239 };
240
241 test "execution capability predicates use architecture" {
242 try std.testing.expect((Capabilities{ .os = .linux, .arch = .x86_64 }).supportsX86_64Execution());
243 try std.testing.expect(!(Capabilities{ .os = .linux, .arch = .aarch64 }).supportsX86_64Execution());
244 try std.testing.expect((Capabilities{ .os = .linux, .arch = .aarch64 }).supportsAarch64Execution());
245 try std.testing.expectEqualStrings("x86_64", (Capabilities{ .os = .linux, .arch = .x86_64 }).archName());
246 try std.testing.expectEqualStrings("little", (Capabilities{ .os = .linux, .arch = .x86_64 }).endianName());
247 try std.testing.expectEqual(@as(u16, 64), (Capabilities{ .os = .linux, .arch = .x86_64 }).pointerWidthBits());
248 }
249
250 test "host shape predicates derive from target fields" {
251 const browser = Capabilities{ .os = .freestanding, .arch = .wasm32, .single_threaded = true };
252 try std.testing.expect(browser.isWasm());
253 try std.testing.expect(browser.isFreestanding());
254 try std.testing.expect(browser.isBrowserish());
255 try std.testing.expect((Capabilities{ .os = .linux, .arch = .x86_64 }).isLinux());
256 try std.testing.expect(!browser.supportsThreads());
257 }
258
259 test "JIT debug support is constrained by OS and architecture" {
260 try std.testing.expect((Capabilities{ .os = .linux, .arch = .x86_64 }).supportsJitDebugInfo());
261 try std.testing.expect((Capabilities{ .os = .macos, .arch = .aarch64 }).supportsJitDebugInfo());
262
263 const windows_support = (Capabilities{ .os = .windows, .arch = .x86_64 }).jitDebugSupport();
264 try std.testing.expect(!windows_support.supported);
265 try std.testing.expect(windows_support.reason != null);
266
267 const unsupported_arch = (Capabilities{ .os = .linux, .arch = .riscv64 }).jitDebugSupport();
268 try std.testing.expect(!unsupported_arch.supported);
269 try std.testing.expect(unsupported_arch.reason != null);
270 }
271
272 test "Apple platform predicates are explicit" {
273 try std.testing.expect((Capabilities{ .os = .macos, .arch = .aarch64 }).supportsAppleMetal());
274 try std.testing.expect(!(Capabilities{ .os = .linux, .arch = .x86_64 }).supportsAppleMetal());
275 try std.testing.expect((Capabilities{ .os = .macos, .arch = .x86_64 }).supportsMoltenVk());
276 }
277
278 test "capability sets are comptime service labels" {
279 const io = CapabilitySet.of(&.{ .filesystem, .standard_streams });
280 try std.testing.expect(io.contains(.filesystem));
281 try std.testing.expect(io.contains(.standard_streams));
282 try std.testing.expect(!io.contains(.process));
283 try std.testing.expectEqual(@as(usize, 2), io.count());
284
285 var storage: [capability_count][]const u8 = undefined;
286 const names = io.names(&storage);
287 try std.testing.expectEqual(@as(usize, 2), names.len);
288 try std.testing.expectEqualStrings("filesystem", names[0]);
289 try std.testing.expectEqualStrings("standard_streams", names[1]);
290 }
291
292 test "capability requirements record tiers and contained services" {
293 const host_files = host(&.{ .filesystem, .dynamic_loading });
294 const no_libc_streams = noLibc(&.{.standard_streams});
295 const empty = pure();
296
297 try std.testing.expectEqual(CapabilityTier.host, host_files.tier);
298 try std.testing.expect(host_files.requires(.filesystem));
299 try std.testing.expect(host_files.requires(.dynamic_loading));
300 try std.testing.expect(!host_files.requires(.networking));
301 try std.testing.expect(host_files.requiresPlatformEnvironment());
302
303 try std.testing.expectEqual(CapabilityTier.no_libc, no_libc_streams.tier);
304 try std.testing.expect(no_libc_streams.requires(.standard_streams));
305 try std.testing.expect(host_files.containsAll(no_libc_streams) == false);
306 try std.testing.expect(!empty.requiresPlatformEnvironment());
307 }