core/fibre/stacks
skip to the library navigationShared: available on every enabled target.
The switch between stacks, and a round of fixed turns built on it.
| Call | What it does |
|---|---|
stacks.round_over | Fill the caller's stacks.round_space and return a stacks.round, a provider of fibre.turns over the caller's table of records. |
stacks.prepare | Lay out a fibre's first frame on a stack, for a provider of its own. |
stacks.switch | Save the leaving fibre's registers and resume another's. |
A round's entry 0 is the stack it was made on; its other entries are the fibres it started, in the order they were started. start prepares and returns, and the fibre runs at its first turn. The table holds one entry more than the fibres that may be unfinished at once.
When every unfinished fibre has yielded since anything last started or ended, the round calls its stacks.idler before it goes on: a device waits for an interrupt there, and stacks.spin returns at once.
In a program that prepares a stack, every routine checks its frame against the stack it runs on before it reserves the frame, and traps stack_exhausted at its own site when the frame would go below the stack's limit, so a task that goes too deep stops at the call that would have overflowed, between switches as at them. The limit stands a margin above the stack's lowest byte, room for a failed check's handler: 640 bytes on x86-64 and arm64, 768 on RV64 and 384 on Cortex-M0, which --stack-report gives as margin_bytes. A switch carries each stack's limit with the registers it saves. A prepared stack is at least 128 words, beside the few words its first frame keeps at its top; size it from the report's depth of the task's routine and the margin.
Every target with a native backend admits this module, and synthetic-32 does not; a program over fibre.whole or the blocking host does not import it, and runs on every target.
Items
Concepts 1
Types 3
Functions 3
Also on this page
public idler: type = concept (provider: type)
Wait until something a fibre waits for may have changed.
idle: (self: provider) -> none
end idler
What a round does when every fibre has yielded since anything last started or ended: wait for something to change. A device waits for an interrupt; an event-driven Io waits for its events; spin returns at once.
public round: type = round_value
A round of fixed turns, as a handle: copies share one round. Entry 0 is the stack the round was made on; the others are the fibres start prepared, each the record it was given, in the order they were started. start prepares and returns, and the fibre runs at its first turn. yield moves to the next unfinished entry after the current one, round the table, and after a whole round in which every unfinished entry yielded and nothing started or ended it calls the idler first. A provider of fibre.turns. Every entry may switch except finished.
public round_space: type = round_state
Caller-owned state of a round: the record of the stack the round was made on, which entry runs, and its counts. Declare it zeroed and give it to round_over, which fills it; it must outlive the round.
public spin: type = struct
reserved: u32
end spin
An idler that returns at once, for a round whose fibres yield only while they have work to do, as a test's do. Make one zeroed.
public prepare: (escaping record: ptr mut fibre.space,
escaping stack: []mut u8, escaping work: any fibre.task,
escaping after: ptr mut fibre.space)
-> none ! mem.out_of_memory
Lay out a first frame on stack that, when a switch resumes record, runs work to its end, makes the record's status zero and switches to after, which the provider keeps unfinished for as long as a fibre can end into it. A routine of the task, or of anything it calls, whose frame would go below the stack's limit traps stack_exhausted in its prologue (D451, D471), before it has written there. It fails mem.out_of_memory when stack is smaller than 128 words and the few words the first frame keeps at its top.
public round_over: (inout state: round_space, entries: []mut usize,
when_idle: any idler)
-> (handle: round from state, entries, when_idle)
! mem.out_of_memory
Fill state for a round over entries, the caller's table of the records it runs, and return its handle. The table holds entry 0 and one entry for each fibre that may be unfinished at once, so a group of count stacks needs count + 1; start fails mem.out_of_memory when it is full. It fails mem.out_of_memory when entries holds fewer than two. Keep state, entries and when_idle alive while the round is used.
public switch: (leaving: ptr mut fibre.space, entering: ptr mut fibre.space)
-> none
Save the callee-saved registers, the frame pointer and the leaving stack's limit on the leaving stack, keep its stack pointer in leaving, and resume entering, returning when something switches back to leaving (D472). A switch is a full compiler memory boundary (D446). Pointers rather than two inout records, because both commonly sit in one table.
Example program
This complete program is one of the compiler's runtime tests. View source.
-- D444: a group over a round of turns runs two tasks that yield in turn, -- each letting the other run before it resumes, so their letters -- interleave. The round's home stack makes the group and goes on past -- both admissions before either task has run, and its join takes turns -- until both have ended. import core/failure import core/fibre import core/fibre/stacks import core/io import hosted/system mut order: [16]u8 = zeroed mut steps: usize = 0 note: type = struct runs: any fibre.turns letter: u8 end note note_of: (runs: any fibre.turns, letter: u8) -> (made: note from runs) = made = (runs: runs, letter: letter) end note_of mark: (letter: u8) -> none = order[steps] = letter steps += 1 end mark -- Three letters, with a turn after each of the first two. thrice: (inout state: note) -> none ! failure.reason = mark(state.letter) state.runs.yield() mark(state.letter) state.runs.yield() mark(state.letter) end thrice both: (runs: any fibre.turns) -> none ! failure.reason = mut stacks: [2 * 8192]u8 = zeroed mut tasks := try fibre.turns_over(runs, stacks, 2) defer fibre.join(tasks) else none a := note_of(runs, u8('a')) try fibre.admit(tasks, thrice, a) b := note_of(runs, u8('b')) try fibre.admit(tasks, thrice, b) mark(u8('h')) try fibre.join(tasks) mark(u8('j')) end both public main: () -> (code: i32) = code = 1 host := system.new() mut state: stacks.round_space = zeroed mut entries: [3]usize = zeroed idler: stacks.spin = zeroed round := stacks.round_over(state, entries, any(addr idler)) else (problem) _ = problem return end both(any(addr round)) else (problem) _ = problem return end mut storage: [64]u8 = zeroed mut out := io.buffered_over(host, io.out(host), storage) io.put(out, order[0..<steps]) else none io.put(out, "\n") else none io.flush(out) else none code = 0 end main
$ refine compile main.ldn -o fibre-turns-interleave && ./fibre-turns-interleave habababj