Skip to documentation
SLOP

tiny.mprompt.zig_api

Reference tiny.mprompt zig_api

Defined in tiny.mprompt.

A typed Zig layer over the package's stack-switching runtime runs a function on a stack of its own and returns the function's result as a Zig value.

API (9)

Actions

Public operations.

Types and contracts

Public types and contracts.

No direct callersNo direct callstiny.mpromptzig api
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Source: lib/mprompt/src/api.zig

zig
//! A typed Zig layer over the package's stack-switching runtime runs a function on a stack of its//! own and returns the function's result as a Zig value. A body run this way can pause with a value//! of a type it names and continue with a value of another type it names.//!//! The runtime underneath passes one untyped pointer each way when it switches stacks, and Zig//! callers want their own types, error unions included, checked by the compiler. A body pauses in//! one of two styles: a handler answers the pause at once on the caller's side, as a generator//! hands out each value, or the pause goes back to the caller, which continues it later, as a//! scheduler does with waiting workers.//!//! A value that crosses from one stack to the other has to stay in memory that both sides can reach//! until the receiving side has read it. When a pause goes back to the caller, the caller gets one//! pointer back and has to tell from it whether the body finished or paused. A handler that returns//! without continuing the body leaves the call that started the body with no result to return.//!//! Each value travels by address: the sender stores it in a small record on its own stack and//! passes the record's address, and the record stays valid because the sender waits inside its//! resume or pause call until the receiver has read it. `SuspendedRun` keeps the body's result and//! a one-byte marker (*done marker*) in the caller's own memory, and the body's wrapper returns the//! marker's address when the body finishes, so that address means the run finished and any other//! pointer is a handle to the paused body. `yieldWith` hands its handler a typed handle to the//! paused rest of the body, a `Continuation`. The program stops with a panic when the handler//! returns without continuing the body. Every context passes by pointer, and the compiler rejects//! any other context type. The stack switch underneath exists for x86_64 and aarch64 targets other//! than Windows, on 64-bit targets only.//!//! - *one-shot handle*: a handle resumable at most once, continuing in place with no copy//! - *multi-shot handle*: a reference-counted handle resumable more than once//! - *one-shot continuation*: a continuation resumable at most once, continuing in place with no//!   copy//! - *multi-shot continuation*: a reference-counted continuation resumable more than once//! - *tail resume*: a resume made as a function's last act, returning straight to the call that//!   entered the promptconst std = @import("std");const raw = @import("root.zig");pub const Prompt = raw.Prompt;const VoidSlot = struct {};fn Slot(comptime T: type) type {    return if (T == void) VoidSlot else SlotValueType(T);}fn SlotValueType(comptime T: type) type {    return struct {        value: T = undefined,    };}/// Returns a tagged union type with two cases: the body returned, or the body paused. A caller/// switches on it after starting or continuing a paused run, to learn whether the body finished or/// paused again. `SuspendedRun.start` and `SuspendedPrompt.continueWith` return it. `ResumeValue`/// is the type a caller passes to continue the body. `Result` is the type the body returns. An/// error union passes through `Result` unchanged, so a failing body comes back as an error inside/// the returned case.pub fn PromptOutcome(comptime ResumeValue: type, comptime Result: type) type {    return union(enum) {        /// Holds the value the body returned. By the time this case comes back, the runtime has        /// released the body's stacklet, so no handle remains to drop.        returned: Result,        /// Holds the handle to the paused body. The caller either continues the handle with a value        /// or drops it, and a one-shot handle is used once.        suspended: SuspendedPrompt(ResumeValue, Result),    };}/// Returns the typed handle type for a body that paused inside a `SuspendedRun`. A caller keeps/// this handle from the moment a run pauses until it continues or abandons the run, for example a/// scheduler that holds one handle per waiting worker. The handle points into the `SuspendedRun`/// that started the body, so that `SuspendedRun` must stay at the same address while the handle is/// in use. A one-shot handle is continued once or dropped once, because continuing it again, or/// after a drop, uses a stacklet the runtime has already freed or reused. `asMulti` turns it into a/// multi-shot handle that the caller can continue once per reference.pub fn SuspendedPrompt(comptime ResumeValue: type, comptime Result: type) type {    return struct {        /// Holds the runtime's resumption for the paused body. The methods pass it to the runtime's        /// `resumePrompt`, `resumeDrop`, `resumeMulti`, `resumeDup` and `resumeResumeCount`.        raw_resume: *raw.Resume,        /// Points to the done marker inside the `SuspendedRun` that started the body.        /// `continueWith` compares the runtime's result with this address to tell a finished body        /// from a new pause.        done_marker: *u8,        /// Points to the storage inside the `SuspendedRun` where the body's wrapper writes the        /// body's result. `continueWith` reads the result from it when the body finishes.        result: *Slot(Result),        const Self = @This();        /// Names the `PromptOutcome` type for this handle's `ResumeValue` and `Result`, which        /// `continueWith` returns.        pub const Outcome: type = PromptOutcome(ResumeValue, Result);        /// Resumes the paused body so that its `suspendPrompt` call returns `value`, for a caller        /// continuing the body with the value the pause is waiting for. The call runs the body on        /// its stacklet until it finishes or pauses again. The call returns `.returned` with the        /// body's result when the body finished, or `.suspended` with a new handle when it paused        /// again. The call uses up a one-shot handle, and a later pause comes back as a new handle        /// in `.suspended`. The call gives up one reference of a multi-shot handle. When other        /// references of a multi-shot handle remain, the call first copies the paused stack to the        /// heap, and a later resume copies it back.        pub fn continueWith(self: Self, value: ResumeValue) Outcome {            var slot: Slot(ResumeValue) = .{};            writeSlot(ResumeValue, &slot, value);            const raw_result = raw.resumePrompt(self.raw_resume, slotPtr(ResumeValue, &slot));            return outcomeFromRaw(ResumeValue, Result, raw_result, self.done_marker, self.result);        }        /// Continues the paused body with no value, for a `ResumeValue` of `void`. Any other        /// `ResumeValue` is a compile error.        pub fn continueWithoutValue(self: Self) Outcome {            if (ResumeValue != void) {                @compileError("continueWithoutValue requires ResumeValue to be void");            }            return self.continueWith({});        }        /// Returns a multi-shot handle for the same pause, for a caller that converts the handle        /// before continuing the same pause more than once. The caller uses the returned handle in        /// place of the old one. Converting a one-shot handle allocates a record from the process        /// allocator, and the record starts with one reference. The program stops with a panic if        /// that allocation fails. A handle that is already multi-shot comes back unchanged.        pub fn asMulti(self: Self) Self {            return .{                .raw_resume = raw.resumeMulti(self.raw_resume),                .done_marker = self.done_marker,                .result = self.result,            };        }        /// A caller takes one extra reference for each extra time it will continue the pause. The        /// call returns a second handle for the same pause and adds one reference, or returns null        /// for a one-shot handle. The call copies no stack memory. The runtime copies the paused        /// stack only when a resume finds other references still held.        pub fn dup(self: Self) ?Self {            const duplicated = raw.resumeDup(self.raw_resume) orelse return null;            return .{                .raw_resume = duplicated,                .done_marker = self.done_marker,                .result = self.result,            };        }        /// Gives up this handle without continuing the body, for a caller abandoning a paused body        /// it will never continue, as the Chic host does when it tears down a turn. For a one-shot        /// handle, and for the last reference of a multi-shot handle, the runtime frees the paused        /// stacklets, and the rest of the body never runs, so its `defer` statements never run        /// either.        pub fn drop(self: Self) void {            raw.resumeDrop(self.raw_resume);        }        /// Returns the number of resumes of a multi-shot handle, or 0 for a one-shot handle, for a        /// caller reading how many times the pause has been continued so far.        pub fn resumeCount(self: Self) c_long {            return raw.resumeResumeCount(self.raw_resume);        }    };}fn SuspendedRunStartCallbackType(    comptime RunState: type,    comptime Result: type,    comptime body: anytype,) type {    return struct {        fn run(prompt: *Prompt, arg: ?*anyopaque) callconv(.c) ?*anyopaque {            const run_state: *RunState = @ptrCast(@alignCast(arg.?));            writeSlot(Result, &run_state.result, body(prompt, run_state.context));            return @ptrCast(&run_state.done_marker);        }    };}/// Returns a struct type that holds a context pointer, storage for the body's result, and a done/// marker, for a caller that starts a body that may pause and hand control back, then continues it/// later from its own loop, as a scheduler does with workers or the Chic host does with an/// interpreter turn. `Context` must be a pointer type, and any other type is a compile error./// `init(context)` builds the value, and `start(body)` runs `body(prompt, context)` on a new/// stacklet and returns a `PromptOutcome`. The body pauses with `suspendPrompt`, and the pause/// comes back to the caller as `.suspended`. The value must stay at one address from `start` until/// the body finishes or its last handle is dropped, because every handle points to its result/// storage and done marker. A caller may call `start` again after the previous run finished, as the/// scheduler test does with one value per worker.pub fn SuspendedRun(    comptime ResumeValue: type,    comptime Result: type,    comptime Context: type,) type {    requirePointer(Context, "SuspendedRun context");    return struct {        /// The pointer passed to `init`, which `start` hands to the body.        context: Context,        /// Storage where the body's wrapper writes the body's result when the body finishes. A        /// handle from this run reads the result from it. The default is empty storage, whose value        /// stays undefined until the body returns.        result: Slot(Result) = .{},        /// The done marker: a byte whose address the body's wrapper returns when the body finishes.        /// `start` and each handle compare the runtime's result with this address to tell a        /// finished body from a pause. Only its address is used, and its value stays at the        /// default 0.        done_marker: u8 = 0,        const Self = @This();        /// Names the `PromptOutcome` type for this run's `ResumeValue` and `Result`, which `start`        /// returns.        pub const Outcome: type = PromptOutcome(ResumeValue, Result);        /// Returns a run holding `context`, with empty result storage and the done marker at 0, for        /// a caller that builds the run once before starting the body. The call allocates nothing        /// and starts nothing.        pub fn init(context: Context) Self {            return .{ .context = context };        }        /// Creates a prompt with a new stacklet and runs `body(prompt, context)` on it, for a        /// caller that begins the body and learns whether it finished at once or paused. The call        /// returns `.returned` with the body's result when the body finishes without pausing, or        /// `.suspended` with a handle when it pauses through `suspendPrompt`. `self` must stay at        /// the same address while any handle from this run is in use.        pub fn start(            self: *Self,            comptime body: *const fn (*Prompt, Context) Result,        ) Outcome {            const Callback: type = SuspendedRunStartCallbackType(Self, Result, body);            const raw_result = raw.prompt(Callback.run, self);            return outcomeFromRaw(                ResumeValue,                Result,                raw_result,                &self.done_marker,                &self.result,            );        }    };}fn suspend_prompt_callback(raw_resume: *raw.Resume, _: ?*anyopaque) callconv(.c) ?*anyopaque {    return @ptrCast(raw_resume);}/// Suspends the body at `prompt`, so the pending `start` or `continueWith` call returns/// `.suspended` with a handle, for a body started by `SuspendedRun` that pauses and hands control/// back to the code that started or last continued it. The call returns the value the caller later/// passes to `continueWith`. `prompt` must be the prompt the body received from `start`, and the/// body must still be running on it, which assertions check in safe builds. Only a body started by/// `SuspendedRun` can call it, because under `run` the handle would be read as the body's result.pub fn suspendPrompt(comptime ResumeValue: type, prompt: *Prompt) ResumeValue {    return readSlot(ResumeValue, raw.yieldPrompt(prompt, suspend_prompt_callback, null));}/// Returns the typed handle type that a `yieldWith` handler gets for the paused rest of the body,/// for a handler that uses it to continue the paused body with its answer. `ResumeValue` is the/// type the paused `yieldWith` call returns. `PromptResult` is the type the body's `run` returns./// `PromptResult` must be the `Result` of the enclosing `run`, and the compiler does not check that/// the two match. The handler must call one of the continue methods before it returns, or the/// program stops with a panic.pub fn Continuation(comptime ResumeValue: type, comptime PromptResult: type) type {    return struct {        /// Holds the runtime's resumption for the paused body.        raw_resume: *raw.Resume,        /// Points to a flag in `yieldWith` that a continue method sets once the body hands control        /// back. `yieldWith` stops the program with a panic when the handler returns and the flag        /// is still false.        continued: *bool,        /// Points to where a continue method stores the runtime's result pointer. `yieldWith`        /// returns that pointer to the call that entered the prompt.        raw_result: *?*anyopaque,        const Self = @This();        /// Resumes the paused body so that its `yieldWith` call returns `value`, for a handler        /// continuing the paused body with its answer and getting the body's result back. The call        /// returns the body's result once the body runs to its end, through any later pauses that        /// their own handlers continue. The call marks the continuation as used. A one-shot        /// continuation is continued once. When other references of a multi-shot continuation        /// remain, the call first copies the paused stack to the heap, and a later resume copies it        /// back.        pub fn continueWith(self: Self, value: ResumeValue) PromptResult {            return self.continueInternal(false, value);        }        /// Resumes the paused body with `value` as a tail resume, for a handler whose last act is        /// to continue the body, so repeated pauses do not pile up handler frames. For a one-shot        /// continuation, control never comes back to the handler, and the body's result goes        /// straight to the call that entered the prompt. The call must be the handler's last        /// action. For a multi-shot continuation, only the first tail resume works this way, and        /// later ones behave as `continueWith` does.        pub fn continueTailWith(self: Self, value: ResumeValue) PromptResult {            return self.continueInternal(true, value);        }        /// Continues the paused body with no value, for a `ResumeValue` of `void`. Any other        /// `ResumeValue` is a compile error.        pub fn continueWithoutValue(self: Self) PromptResult {            if (ResumeValue != void) {                @compileError("continueWithoutValue requires ResumeValue to be void");            }            return self.continueWith({});        }        /// Continues the paused body with no value as a tail resume, for a `ResumeValue` of `void`.        /// Any other `ResumeValue` is a compile error.        pub fn continueTailWithoutValue(self: Self) PromptResult {            if (ResumeValue != void) {                @compileError("continueTailWithoutValue requires ResumeValue to be void");            }            return self.continueTailWith({});        }        /// Returns a multi-shot continuation for the same pause, for a handler that converts the        /// continuation before continuing the same pause more than once, as a search that tries        /// each branch does. The handler uses the returned continuation in place of the old one.        /// Converting a one-shot continuation allocates a record from the process allocator, and        /// the program stops with a panic if that allocation fails. A continuation that is already        /// multi-shot comes back unchanged. Each continue call gives up one reference, so        /// continuing twice takes a `dup` first.        pub fn asMulti(self: Self) Self {            return .{                .raw_resume = raw.resumeMulti(self.raw_resume),                .continued = self.continued,                .raw_result = self.raw_result,            };        }        /// A handler takes one extra reference for each extra time it will continue the pause. The        /// call returns a second continuation for the same pause and adds one reference, or returns        /// null for a one-shot continuation. The call copies no stack memory.        pub fn dup(self: Self) ?Self {            const duplicated = raw.resumeDup(self.raw_resume) orelse return null;            return .{                .raw_resume = duplicated,                .continued = self.continued,                .raw_result = self.raw_result,            };        }        /// Gives up one reference without continuing the body, for a handler giving back an extra        /// reference it took and will not use. The handler must still continue the body through        /// another reference before it returns, because a handler that drops its only reference and        /// returns stops the program with a panic.        pub fn drop(self: Self) void {            raw.resumeDrop(self.raw_resume);        }        /// Returns the number of resumes of a multi-shot continuation, or 0 for a one-shot        /// continuation, for a handler reading how many times the pause has been continued so far.        pub fn resumeCount(self: Self) c_long {            return raw.resumeResumeCount(self.raw_resume);        }        fn continueInternal(self: Self, comptime tail: bool, value: ResumeValue) PromptResult {            var slot: Slot(ResumeValue) = .{};            writeSlot(ResumeValue, &slot, value);            const result = if (tail)                raw.resumeTailPrompt(self.raw_resume, slotPtr(ResumeValue, &slot))            else                raw.resumePrompt(self.raw_resume, slotPtr(ResumeValue, &slot));            self.continued.* = true;            self.raw_result.* = result;            return readSlot(PromptResult, result);        }    };}fn RunEnvironmentType(    comptime Result: type,    comptime Context: type,    comptime body: anytype,) type {    return struct {        context: Context,        result: Slot(Result) = .{},        fn start(prompt: *Prompt, arg: ?*anyopaque) callconv(.c) ?*anyopaque {            const environment: *@This() = @ptrCast(@alignCast(arg.?));            writeSlot(Result, &environment.result, body(prompt, environment.context));            return slotPtr(Result, &environment.result);        }    };}/// A caller runs the body to its end in one call, and a handler answers each pause before the body/// goes on. The call creates a prompt with a new stacklet, runs `body(prompt, context)` on it, and/// returns what the body returns. The prompt marks where the body entered its stacklet, and the/// body passes it to `yieldWith` to pause back to that point. `context` must be a pointer, and any/// other type is a compile error. An error union passes through: a body that returns an error makes/// `run` return that error. The stacklet comes from the calling thread's cache or from a new/// reservation. The first prompt in a process initializes the runtime with the default/// configuration when `init` has not run. The runtime releases the stacklet when the body returns./// The body pauses with `yieldWith`. `suspendPrompt` is only for bodies started by `SuspendedRun`.pub fn run(    comptime Result: type,    context: anytype,    comptime body: *const fn (*Prompt, @TypeOf(context)) Result,) Result {    const Context = @TypeOf(context);    requirePointer(Context, "run context");    const Environment: type = RunEnvironmentType(Result, Context, body);    var environment: Environment = .{ .context = context };    return readSlot(Result, raw.prompt(Environment.start, &environment));}fn RunWithoutContextType(comptime Result: type, comptime body: anytype) type {    return struct {        fn start(prompt: *Prompt, _: *@This()) Result {            return body(prompt);        }    };}/// Runs `body(prompt)` the way `run` does, with no context argument, so a caller whose body needs/// no context skips the pointer.pub fn runWithoutContext(    comptime Result: type,    comptime body: *const fn (*Prompt) Result,) Result {    const Context: type = RunWithoutContextType(Result, body);    var context: Context = .{};    return run(Result, &context, Context.start);}fn YieldWithEnvironmentType(    comptime ResumeValue: type,    comptime PromptResult: type,    comptime YieldValue: type,    comptime HandlerContext: type,    comptime handler: anytype,) type {    return struct {        value: YieldValue,        handler_context: HandlerContext,        fn onYield(raw_resume: *raw.Resume, arg: ?*anyopaque) callconv(.c) ?*anyopaque {            const environment: *@This() = @ptrCast(@alignCast(arg.?));            var continued = false;            var raw_result: ?*anyopaque = null;            const continuation: Continuation(ResumeValue, PromptResult) = .{                .raw_resume = raw_resume,                .continued = &continued,                .raw_result = &raw_result,            };            handler(continuation, environment.handler_context, environment.value);            if (!continued) {                std.debug.panic("mprompt.yieldWith handlers must continue the prompt before returning", .{});            }            return raw_result;        }    };}/// Suspends the body at `prompt` and runs `handler(continuation, handler_context, value)` on the/// stack of the code that called `run`, so that a handler outside the body computes its next value,/// as a generator hands out each value or a worker asks for input. The call returns the value the/// handler continues the body with. The handler receives `value` by copy. `handler_context` must be/// a pointer, and any other type is a compile error. The handler must continue the body before it/// returns, and a handler that returns without continuing stops the program with a panic. `prompt`/// must be the prompt the body received from `run`, and the body must still be running on it, which/// assertions check in safe builds. `PromptResult` must be the `Result` type of the enclosing/// `run`.pub fn yieldWith(    comptime ResumeValue: type,    comptime PromptResult: type,    prompt: *Prompt,    value: anytype,    handler_context: anytype,    comptime handler: *const fn (Continuation(ResumeValue, PromptResult), @TypeOf(handler_context), @TypeOf(value)) void,) ResumeValue {    const YieldValue = @TypeOf(value);    const HandlerContext = @TypeOf(handler_context);    requirePointer(HandlerContext, "yieldWith handler context");    const Environment: type = YieldWithEnvironmentType(        ResumeValue,        PromptResult,        YieldValue,        HandlerContext,        handler,    );    var environment: Environment = .{        .value = value,        .handler_context = handler_context,    };    return readSlot(ResumeValue, raw.yieldPrompt(prompt, Environment.onYield, &environment));}fn writeSlot(comptime T: type, slot: *Slot(T), value: T) void {    if (comptime T != void) {        slot.value = value;    }}fn slotPtr(comptime T: type, slot: *Slot(T)) ?*anyopaque {    if (comptime T != void) {        return @ptrCast(slot);    }    return null;}fn readSlot(comptime T: type, ptr: ?*anyopaque) T {    if (comptime T != void) {        const slot: *Slot(T) = @ptrCast(@alignCast(ptr.?));        return slot.value;    }    return {};}fn outcomeFromRaw(    comptime ResumeValue: type,    comptime Result: type,    raw_result: ?*anyopaque,    done_marker: *u8,    result: *Slot(Result),) PromptOutcome(ResumeValue, Result) {    const done_ptr: ?*anyopaque = @ptrCast(done_marker);    if (raw_result == done_ptr) {        return .{ .returned = readSlot(Result, slotPtr(Result, result)) };    }    return .{        .suspended = .{            .raw_resume = @ptrCast(@alignCast(raw_result.?)),            .done_marker = done_marker,            .result = result,        },    };}fn requirePointer(comptime T: type, comptime name: []const u8) void {    switch (@typeInfo(T)) {        .pointer => {},        else => @compileError(name ++ " must be a pointer"),    }}const Counter = struct {    seen: usize = 0,};fn returnCount(_: *raw.Prompt, counter: *Counter) usize {    counter.seen += 1;    return counter.seen + 40;}test "typed prompt run returns a Zig value" {    var counter: Counter = .{};    try std.testing.expectEqual(@as(usize, 41), run(usize, &counter, returnCount));    try std.testing.expectEqual(@as(usize, 1), counter.seen);}fn fallibleBody(_: *raw.Prompt, counter: *Counter) error{Done}!usize {    counter.seen += 1;    return error.Done;}test "typed prompt run preserves error unions" {    var counter: Counter = .{};    try std.testing.expectError(error.Done, run(error{Done}!usize, &counter, fallibleBody));    try std.testing.expectEqual(@as(usize, 1), counter.seen);}fn noContextBody(_: *raw.Prompt) usize {    return 42;}test "typed prompt run supports no-context bodies" {    try std.testing.expectEqual(@as(usize, 42), runWithoutContext(usize, noContextBody));}const SuspendedContext = struct {    seen: usize = 0,};fn suspendOnce(prompt: *raw.Prompt, context: *SuspendedContext) usize {    const resumed = suspendPrompt(usize, prompt);    context.seen = resumed;    return resumed + 1;}test "typed suspended run resumes an escaped prompt" {    var context: SuspendedContext = .{};    var prompt_run = SuspendedRun(usize, usize, *SuspendedContext).init(&context);    const first = prompt_run.start(suspendOnce);    const suspended = switch (first) {        .suspended => |continuation| continuation,        .returned => return error.ExpectedSuspension,    };    const second = suspended.continueWith(41);    const result = switch (second) {        .returned => |value| value,        .suspended => return error.UnexpectedSuspension,    };    try std.testing.expectEqual(@as(usize, 42), result);    try std.testing.expectEqual(@as(usize, 41), context.seen);}fn suspendTwice(prompt: *raw.Prompt, context: *SuspendedContext) usize {    context.seen += suspendPrompt(usize, prompt);    context.seen += suspendPrompt(usize, prompt);    return context.seen;}test "typed suspended run can suspend again after resume" {    var context: SuspendedContext = .{};    var prompt_run = SuspendedRun(usize, usize, *SuspendedContext).init(&context);    const first = prompt_run.start(suspendTwice);    const first_suspended = switch (first) {        .suspended => |continuation| continuation,        .returned => return error.ExpectedSuspension,    };    const second = first_suspended.continueWith(13);    const second_suspended = switch (second) {        .suspended => |continuation| continuation,        .returned => return error.ExpectedSuspension,    };    const third = second_suspended.continueWith(29);    const result = switch (third) {        .returned => |value| value,        .suspended => return error.UnexpectedSuspension,    };    try std.testing.expectEqual(@as(usize, 42), result);    try std.testing.expectEqual(@as(usize, 42), context.seen);}fn useStackPages(kb: usize) void {    var top: u8 = 0;    const sp = @intFromPtr(&top);    const page_size = 4096;    const page_count = (kb * 1024 + page_size - 1) / page_size;    var checksum: u8 = 0;    var page: usize = 0;    while (page < page_count) : (page += 1) {        const address: *volatile u8 = @ptrFromInt(sp - page * page_size);        checksum +%= address.*;    }    std.mem.doNotOptimizeAway(checksum);}const StackWorkerEnv = struct {    completed: usize = 0,};fn stackUsingAsyncWorker(prompt: *raw.Prompt, env: *StackWorkerEnv) usize {    const stack_kb = suspendPrompt(usize, prompt);    useStackPages(stack_kb);    env.completed += 1;    return 1;}test "scheduler-style async prompts resume active workers" {    const worker_count = 16;    const request_count = 256;    const stack_kb = 8;    const WorkerRun = SuspendedRun(usize, usize, *StackWorkerEnv);    const Worker = SuspendedPrompt(usize, usize);    var envs: [worker_count]StackWorkerEnv = @as([worker_count]StackWorkerEnv, @splat(.{}));    var runs: [worker_count]WorkerRun = undefined;    for (&runs, &envs) |*worker_run, *env| {        worker_run.* = WorkerRun.init(env);    }    var workers: [worker_count]?Worker = @splat(null);    var completed: usize = 0;    var i: usize = 0;    while (i < request_count + worker_count) : (i += 1) {        const slot = i % worker_count;        if (workers[slot]) |continuation| {            const outcome = continuation.continueWith(stack_kb);            completed += switch (outcome) {                .returned => |value| value,                .suspended => return error.UnexpectedSuspension,            };            workers[slot] = null;        }        if (i < request_count) {            const outcome = runs[slot].start(stackUsingAsyncWorker);            workers[slot] = switch (outcome) {                .suspended => |continuation| continuation,                .returned => return error.ExpectedSuspension,            };        }    }    try std.testing.expectEqual(@as(usize, request_count), completed);    var observed: usize = 0;    for (envs) |env| {        observed += env.completed;    }    try std.testing.expectEqual(@as(usize, request_count), observed);    for (workers) |worker| {        try std.testing.expect(worker == null);    }}const YieldContext = struct {    yielded: usize = 0,};fn continueSuspended(    continuation: Continuation(usize, usize),    context: *YieldContext,    value: usize,) void {    context.yielded = value;    _ = continuation.continueWith(value + 1);}fn addAfterSuspend(prompt: *raw.Prompt, context: *YieldContext) usize {    const resumed = yieldWith(        usize,        usize,        prompt,        @as(usize, 40),        context,        continueSuspended,    );    return resumed + 1;}test "typed yield passes values through a continuation" {    var context: YieldContext = .{};    try std.testing.expectEqual(@as(usize, 42), run(usize, &context, addAfterSuspend));    try std.testing.expectEqual(@as(usize, 40), context.yielded);}const Collector = struct {    values: [8]usize = @splat(0),    count: usize = 0,};fn collectAndContinueTail(    continuation: Continuation(void, void),    collector: *Collector,    value: usize,) void {    collector.values[collector.count] = value;    collector.count += 1;    continuation.continueTailWithoutValue();}fn produceValues(prompt: *raw.Prompt, collector: *Collector) void {    var i: usize = 0;    while (i < 4) : (i += 1) {        yieldWith(            void,            void,            prompt,            i,            collector,            collectAndContinueTail,        );    }}test "typed yield supports tail continuation without payloads" {    var collector: Collector = .{};    run(void, &collector, produceValues);    try std.testing.expectEqual(@as(usize, 4), collector.count);    for (collector.values[0..collector.count], 0..) |value, index| {        try std.testing.expectEqual(index, value);    }}

Source: lib/mprompt/src/root.zig:56

zig
pub const zig_api = @import("api.zig");

Audit

Definitions1
Public names1
Members0
Version26.7.0
Revisiondaab053ee433