Commit f83834993e

Veikka Tuominen <git@vexu.eu>
2023-01-03 18:37:11
std: collect all options under one namespace
1 parent fe2bd9d
lib/std/crypto/tlcsprng.zig
@@ -5,7 +5,6 @@
 
 const std = @import("std");
 const builtin = @import("builtin");
-const root = @import("root");
 const mem = std.mem;
 const os = std.os;
 
@@ -67,8 +66,8 @@ fn tlsCsprngFill(_: *anyopaque, buffer: []u8) void {
     // Allow applications to decide they would prefer to have every call to
     // std.crypto.random always make an OS syscall, rather than rely on an
     // application implementation of a CSPRNG.
-    if (comptime std.meta.globalOption("crypto_always_getrandom", bool) orelse false) {
-        return fillWithOsEntropy(buffer);
+    if (std.options.crypto_always_getrandom) {
+        return defaultRandomSeed(buffer);
     }
 
     if (wipe_mem.len == 0) {
@@ -86,7 +85,7 @@ fn tlsCsprngFill(_: *anyopaque, buffer: []u8) void {
             ) catch {
                 // Could not allocate memory for the local state, fall back to
                 // the OS syscall.
-                return fillWithOsEntropy(buffer);
+                return std.options.cryptoRandomSeed(buffer);
             };
             // The memory is already zero-initialized.
         } else {
@@ -128,14 +127,14 @@ fn tlsCsprngFill(_: *anyopaque, buffer: []u8) void {
             // Since we failed to set up fork safety, we fall back to always
             // calling getrandom every time.
             ctx.init_state = .failed;
-            return fillWithOsEntropy(buffer);
+            return std.options.cryptoRandomSeed(buffer);
         },
         .initialized => {
             return fillWithCsprng(buffer);
         },
         .failed => {
             if (want_fork_safety) {
-                return fillWithOsEntropy(buffer);
+                return std.options.cryptoRandomSeed(buffer);
             } else {
                 unreachable;
             }
@@ -165,7 +164,7 @@ fn fillWithCsprng(buffer: []u8) void {
     mem.set(u8, ctx.gimli.toSlice()[0..std.crypto.core.Gimli.RATE], 0);
 }
 
-fn fillWithOsEntropy(buffer: []u8) void {
+pub fn defaultRandomSeed(buffer: []u8) void {
     os.getrandom(buffer) catch @panic("getrandom() failed to provide entropy");
 }
 
@@ -174,12 +173,8 @@ fn initAndFill(buffer: []u8) void {
     // Because we panic on getrandom() failing, we provide the opportunity
     // to override the default seed function. This also makes
     // `std.crypto.random` available on freestanding targets, provided that
-    // the `cryptoRandomSeed` function is provided.
-    if (@hasDecl(root, "cryptoRandomSeed")) {
-        root.cryptoRandomSeed(&seed);
-    } else {
-        fillWithOsEntropy(&seed);
-    }
+    // the `std.options.cryptoRandomSeed` function is provided.
+    std.options.cryptoRandomSeed(&seed);
 
     const ctx = @ptrCast(*Context, wipe_mem.ptr);
     ctx.gimli = std.crypto.core.Gimli.init(seed);
lib/std/event/batch.zig
@@ -17,7 +17,7 @@ pub fn Batch(
     comptime async_behavior: enum {
         /// Observe the value of `std.io.is_async` to decide whether `add`
         /// and `wait` will be async functions. Asserts that the jobs do not suspend when
-        /// `std.io.mode == .blocking`. This is a generally safe assumption, and the
+        /// `std.options.io_mode == .blocking`. This is a generally safe assumption, and the
         /// usual recommended option for this parameter.
         auto_async,
 
lib/std/event/loop.zig
@@ -1,6 +1,5 @@
 const std = @import("../std.zig");
 const builtin = @import("builtin");
-const root = @import("root");
 const assert = std.debug.assert;
 const testing = std.testing;
 const mem = std.mem;
@@ -104,25 +103,29 @@ pub const Loop = struct {
         };
     };
 
-    const LoopOrVoid = switch (std.io.mode) {
-        .blocking => void,
-        .evented => Loop,
+    pub const Instance = switch (std.options.io_mode) {
+        .blocking => @TypeOf(null),
+        .evented => ?*Loop,
     };
+    pub const instance = std.options.event_loop;
 
-    var global_instance_state: LoopOrVoid = undefined;
-    const default_instance: ?*LoopOrVoid = switch (std.io.mode) {
+    var global_instance_state: Loop = undefined;
+    pub const default_instance = switch (std.options.io_mode) {
         .blocking => null,
         .evented => &global_instance_state,
     };
-    pub const instance: ?*LoopOrVoid = if (@hasDecl(root, "event_loop")) root.event_loop else default_instance;
+
+    pub const Mode = enum {
+        single_threaded,
+        multi_threaded,
+    };
+    pub const default_mode = .multi_threaded;
 
     /// TODO copy elision / named return values so that the threads referencing *Loop
     /// have the correct pointer value.
     /// https://github.com/ziglang/zig/issues/2761 and https://github.com/ziglang/zig/issues/2765
     pub fn init(self: *Loop) !void {
-        if (builtin.single_threaded or
-            (@hasDecl(root, "event_loop_mode") and root.event_loop_mode == .single_threaded))
-        {
+        if (builtin.single_threaded or std.options.event_loop_mode == .single_threaded) {
             return self.initSingleThreaded();
         } else {
             return self.initMultiThreaded();
lib/std/fs/file.zig
@@ -21,7 +21,7 @@ pub const File = struct {
     /// blocking.
     capable_io_mode: io.ModeOverride = io.default_mode,
 
-    /// Furthermore, even when `std.io.mode` is async, it is still sometimes desirable
+    /// Furthermore, even when `std.options.io_mode` is async, it is still sometimes desirable
     /// to perform blocking I/O, although not by default. For example, when printing a
     /// stack trace to stderr. This field tracks both by acting as an overriding I/O mode.
     /// When not building in async I/O mode, the type only has the `.blocking` tag, making
lib/std/debug.zig
@@ -1861,10 +1861,9 @@ pub const have_segfault_handling_support = switch (native_os) {
     .freebsd, .openbsd => @hasDecl(os.system, "ucontext_t"),
     else => false,
 };
-pub const enable_segfault_handler: bool = if (@hasDecl(root, "enable_segfault_handler"))
-    root.enable_segfault_handler
-else
-    runtime_safety and have_segfault_handling_support;
+
+const enable_segfault_handler = std.options.enable_segfault_handler;
+pub const default_enable_segfault_handler = runtime_safety and have_segfault_handling_support;
 
 pub fn maybeEnableSegfaultHandler() void {
     if (enable_segfault_handler) {
lib/std/fs.zig
@@ -2661,17 +2661,17 @@ pub fn cwd() Dir {
     if (builtin.os.tag == .windows) {
         return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle };
     } else if (builtin.os.tag == .wasi) {
-        if (@hasDecl(root, "wasi_cwd")) {
-            return root.wasi_cwd();
-        } else {
-            // Expect the first preopen to be current working directory.
-            return .{ .fd = 3 };
-        }
+        return std.options.wasiCwd();
     } else {
         return Dir{ .fd = os.AT.FDCWD };
     }
 }
 
+pub fn defaultWasiCwd() Dir {
+    // Expect the first preopen to be current working directory.
+    return .{ .fd = 3 };
+}
+
 /// Opens a directory at the given path. The directory is a system resource that remains
 /// open until `close` is called on the result.
 /// See `openDirAbsoluteZ` for a function that accepts a null-terminated path.
lib/std/io.zig
@@ -19,14 +19,7 @@ pub const Mode = enum {
     evented,
 };
 
-/// The application's chosen I/O mode. This defaults to `Mode.blocking` but can be overridden
-/// by `root.event_loop`.
-pub const mode: Mode = if (@hasDecl(root, "io_mode"))
-    root.io_mode
-else if (@hasDecl(root, "event_loop"))
-    Mode.evented
-else
-    Mode.blocking;
+const mode = std.options.io_mode;
 pub const is_async = mode != .blocking;
 
 /// This is an enum value to use for I/O mode at runtime, since it takes up zero bytes at runtime,
lib/std/log.zig
@@ -1,6 +1,6 @@
 //! std.log is a standardized interface for logging which allows for the logging
 //! of programs and libraries using this interface to be formatted and filtered
-//! by the implementer of the root.log function.
+//! by the implementer of the `std.options.logFn` function.
 //!
 //! Each log message has an associated scope enum, which can be used to give
 //! context to the logging. The logging functions in std.log implicitly use a
@@ -13,16 +13,20 @@
 //! `const log = std.log.scoped(.libfoo);` to use .libfoo as the scope of its
 //! log messages.
 //!
-//! An example root.log might look something like this:
+//! An example `logFn` might look something like this:
 //!
 //! ```
 //! const std = @import("std");
 //!
-//! // Set the log level to info
-//! pub const log_level: std.log.Level = .info;
+//! pub const std_options = struct {
+//!     // Set the log level to info
+//!     pub const log_level = .info;
 //!
-//! // Define root.log to override the std implementation
-//! pub fn log(
+//!     // Define logFn to override the std implementation
+//!     pub const logFn = myLogFn;
+//! };
+//!
+//! pub fn myLogFn(
 //!     comptime level: std.log.Level,
 //!     comptime scope: @TypeOf(.EnumLiteral),
 //!     comptime format: []const u8,
@@ -70,7 +74,6 @@
 
 const std = @import("std.zig");
 const builtin = @import("builtin");
-const root = @import("root");
 
 pub const Level = enum {
     /// Error: something has gone wrong. This might be recoverable or might
@@ -102,22 +105,14 @@ pub const default_level: Level = switch (builtin.mode) {
     .ReleaseFast, .ReleaseSmall => .err,
 };
 
-/// The current log level. This is set to root.log_level if present, otherwise
-/// log.default_level.
-pub const level: Level = if (@hasDecl(root, "log_level"))
-    root.log_level
-else
-    default_level;
+const level = std.options.log_level;
 
 pub const ScopeLevel = struct {
     scope: @Type(.EnumLiteral),
     level: Level,
 };
 
-const scope_levels = if (@hasDecl(root, "scope_levels"))
-    root.scope_levels
-else
-    [0]ScopeLevel{};
+const scope_levels = std.options.log_scope_levels;
 
 fn log(
     comptime message_level: Level,
@@ -127,13 +122,7 @@ fn log(
 ) void {
     if (comptime !logEnabled(message_level, scope)) return;
 
-    if (@hasDecl(root, "log")) {
-        if (@typeInfo(@TypeOf(root.log)) != .Fn)
-            @compileError("Expected root.log to be a function");
-        root.log(message_level, scope, format, args);
-    } else {
-        defaultLog(message_level, scope, format, args);
-    }
+    std.options.logFn(message_level, scope, format, args);
 }
 
 /// Determine if a specific log message level and scope combination are enabled for logging.
@@ -149,8 +138,8 @@ pub fn defaultLogEnabled(comptime message_level: Level) bool {
     return comptime logEnabled(message_level, default_log_scope);
 }
 
-/// The default implementation for root.log.  root.log may forward log messages
-/// to this function.
+/// The default implementation for the log function, custom log functions may
+/// forward log messages to this function.
 pub fn defaultLog(
     comptime message_level: Level,
     comptime scope: @Type(.EnumLiteral),
lib/std/start.zig
@@ -527,7 +527,7 @@ const bad_main_ret = "expected return type of main to be 'void', '!void', 'noret
 // and we want fewer call frames in stack traces.
 inline fn initEventLoopAndCallMain() u8 {
     if (std.event.Loop.instance) |loop| {
-        if (!@hasDecl(root, "event_loop")) {
+        if (loop == std.event.Loop.default_instance) {
             loop.init() catch |err| {
                 std.log.err("{s}", .{@errorName(err)});
                 if (@errorReturnTrace()) |trace| {
@@ -556,7 +556,7 @@ inline fn initEventLoopAndCallMain() u8 {
 // because it is working around stage1 compiler bugs.
 inline fn initEventLoopAndCallWinMain() std.os.windows.INT {
     if (std.event.Loop.instance) |loop| {
-        if (!@hasDecl(root, "event_loop")) {
+        if (loop == std.event.Loop.default_instance) {
             loop.init() catch |err| {
                 std.log.err("{s}", .{@errorName(err)});
                 if (@errorReturnTrace()) |trace| {
lib/std/std.zig
@@ -94,10 +94,79 @@ pub const wasm = @import("wasm.zig");
 pub const zig = @import("zig.zig");
 pub const start = @import("start.zig");
 
+const root = @import("root");
+const options_override = if (@hasDecl(root, "std_options")) root.std_options else struct {};
+
+pub const options = struct {
+    pub const enable_segfault_handler: bool = if (@hasDecl(options_override, "enable_segfault_handler"))
+        options_override.enable_segfault_handler
+    else
+        debug.default_enable_segfault_handler;
+
+    /// Function used to implement std.fs.cwd for wasi.
+    pub const wasiCwd: fn () fs.Dir = if (@hasDecl(options_override, "wasiCwd"))
+        options_override.wasiCwd
+    else
+        fs.defaultWasiCwd;
+
+    /// The application's chosen I/O mode.
+    pub const io_mode: io.Mode = if (@hasDecl(options_override, "io_mode"))
+        options_override.io_mode
+    else if (@hasDecl(options_override, "event_loop"))
+        .evented
+    else
+        .blocking;
+
+    pub const event_loop: event.Loop.Instance = if (@hasDecl(options_override, "event_loop"))
+        options_override.event_loop
+    else
+        event.Loop.default_instance;
+
+    pub const event_loop_mode: event.Loop.Mode = if (@hasDecl(options_override, "event_loop_mode"))
+        options_override.event_loop_mode
+    else
+        event.Loop.default_mode;
+
+    /// The current log level.
+    pub const log_level: log.Level = if (@hasDecl(options_override, "log_level"))
+        options_override.log_level
+    else
+        log.default_level;
+
+    pub const log_scope_levels: []const log.ScopeLevel = if (@hasDecl(options_override, "log_scope_levels"))
+        options_override.log_scope_levels
+    else
+        &.{};
+
+    pub const logFn: fn (
+        comptime message_level: log.Level,
+        comptime scope: @TypeOf(.enum_literal),
+        comptime format: []const u8,
+        args: anytype,
+    ) void = if (@hasDecl(options_override, "logFn"))
+        options_override.logFn
+    else
+        log.defaultLog;
+
+    pub const cryptoRandomSeed: fn (buffer: []u8) void = if (@hasDecl(options_override, "cryptoRandomSeed"))
+        options_override.cryptoRandomSeed
+    else
+        @import("crypto/tlcsprng.zig").defaultRandomSeed;
+
+    pub const crypto_always_getrandom: bool = if (@hasDecl(options_override, "crypto_always_getrandom"))
+        options_override.crypto_always_getrandom
+    else
+        false;
+};
+
 // This forces the start.zig file to be imported, and the comptime logic inside that
 // file decides whether to export any appropriate start symbols, and call main.
 comptime {
     _ = start;
+
+    for (@typeInfo(options_override).Struct.decls) |decl| {
+        if (!@hasDecl(options, decl.name)) @compileError("no option named " ++ decl.name);
+    }
 }
 
 test {
lib/test_runner.zig
@@ -2,7 +2,10 @@ const std = @import("std");
 const io = std.io;
 const builtin = @import("builtin");
 
-pub const io_mode: io.Mode = builtin.test_io_mode;
+pub const std_options = struct {
+    pub const io_mode: io.Mode = builtin.test_io_mode;
+    pub const logFn = log;
+};
 
 var log_err_count: usize = 0;
 
@@ -45,7 +48,7 @@ pub fn main() void {
         if (!have_tty) {
             std.debug.print("{d}/{d} {s}... ", .{ i + 1, test_fn_list.len, test_fn.name });
         }
-        const result = if (test_fn.async_frame_size) |size| switch (io_mode) {
+        const result = if (test_fn.async_frame_size) |size| switch (std.options.io_mode) {
             .evented => blk: {
                 if (async_frame_buffer.len < size) {
                     std.heap.page_allocator.free(async_frame_buffer);
src/crash_report.zig
@@ -13,12 +13,10 @@ const Decl = Module.Decl;
 
 pub const is_enabled = builtin.mode == .Debug;
 
-/// To use these crash report diagnostics, publish these symbols in your main file.
+/// To use these crash report diagnostics, publish this panic in your main file
+/// and add `pub const enable_segfault_handler = false;` to your `std_options`.
 /// You will also need to call initialize() on startup, preferably as the very first operation in your program.
-pub const root_decls = struct {
-    pub const panic = if (is_enabled) compilerPanic else std.builtin.default_panic;
-    pub const enable_segfault_handler = false;
-};
+pub const panic = if (is_enabled) compilerPanic else std.builtin.default_panic;
 
 /// Install signal handlers to identify crashes and report diagnostics.
 pub fn initialize() void {
src/main.zig
@@ -25,11 +25,23 @@ const target_util = @import("target.zig");
 const ThreadPool = @import("ThreadPool.zig");
 const crash_report = @import("crash_report.zig");
 
-// Crash report needs to override the panic handler and other root decls
-pub usingnamespace crash_report.root_decls;
+pub const std_options = struct {
+    pub const wasiCwd = wasi_cwd;
+    pub const logFn = log;
+    pub const enable_segfault_handler = false;
+
+    pub const log_level: std.log.Level = switch (builtin.mode) {
+        .Debug => .debug,
+        .ReleaseSafe, .ReleaseFast => .info,
+        .ReleaseSmall => .err,
+    };
+};
+
+// Crash report needs to override the panic handler
+pub const panic = crash_report.panic;
 
 var wasi_preopens: fs.wasi.Preopens = undefined;
-pub inline fn wasi_cwd() fs.Dir {
+pub fn wasi_cwd() fs.Dir {
     // Expect the first preopen to be current working directory.
     const cwd_fd: std.os.fd_t = 3;
     assert(mem.eql(u8, wasi_preopens.names[cwd_fd], "."));
@@ -111,12 +123,6 @@ const debug_usage = normal_usage ++
 
 const usage = if (debug_extensions_enabled) debug_usage else normal_usage;
 
-pub const log_level: std.log.Level = switch (builtin.mode) {
-    .Debug => .debug,
-    .ReleaseSafe, .ReleaseFast => .info,
-    .ReleaseSmall => .err,
-};
-
 var log_scopes: std.ArrayListUnmanaged([]const u8) = .{};
 
 pub fn log(
@@ -128,7 +134,7 @@ pub fn log(
     // Hide debug messages unless:
     // * logging enabled with `-Dlog`.
     // * the --debug-log arg for the scope has been provided
-    if (@enumToInt(level) > @enumToInt(std.log.level) or
+    if (@enumToInt(level) > @enumToInt(std.options.log_level) or
         @enumToInt(level) > @enumToInt(std.log.Level.info))
     {
         if (!build_options.enable_logging) return;
test/behavior/pointers.zig
@@ -509,8 +509,8 @@ test "ptrCast comptime known slice to C pointer" {
 test "ptrToInt on a generic function" {
     if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_aarch64 and builtin.os.tag != .linux) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_x86_64 and builtin.os.tag != .linux) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
 
     const S = struct {
         fn generic(i: anytype) @TypeOf(i) {
test/standalone/issue_7030/main.zig
@@ -1,5 +1,9 @@
 const std = @import("std");
 
+pub const std_options = struct {
+    pub const logFn = log;
+};
+
 pub fn log(
     comptime message_level: std.log.Level,
     comptime scope: @Type(.EnumLiteral),
test/standalone/issue_9693/main.zig
@@ -1,2 +1,4 @@
-pub const io_mode = .evented;
+pub const std_options = struct {
+    pub const io_mode = .evented;
+};
 pub fn main() void {}
test/compare_output.zig
@@ -440,11 +440,14 @@ pub fn addCases(cases: *tests.CompareOutputContext) void {
     cases.add("std.log per scope log level override",
         \\const std = @import("std");
         \\
-        \\pub const log_level: std.log.Level = .debug;
-        \\
-        \\pub const scope_levels = [_]std.log.ScopeLevel{
-        \\    .{ .scope = .a, .level = .warn },
-        \\    .{ .scope = .c, .level = .err },
+        \\pub const std_options = struct {
+        \\    pub const log_level: std.log.Level = .debug;
+        \\    
+        \\    pub const log_scope_levels = &[_]std.log.ScopeLevel{
+        \\        .{ .scope = .a, .level = .warn },
+        \\        .{ .scope = .c, .level = .err },
+        \\    };
+        \\    pub const logFn = log;
         \\};
         \\
         \\const loga = std.log.scoped(.a);
@@ -494,7 +497,10 @@ pub fn addCases(cases: *tests.CompareOutputContext) void {
     cases.add("std.heap.LoggingAllocator logs to std.log",
         \\const std = @import("std");
         \\
-        \\pub const log_level: std.log.Level = .debug;
+        \\pub const std_options = struct {
+        \\    pub const log_level: std.log.Level = .debug;
+        \\    pub const logFn = log;
+        \\};
         \\
         \\pub fn main() !void {
         \\    var allocator_buf: [10]u8 = undefined;