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 }