nmbrs_runtime/fiber_engine.rs
1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! The engine nmbrs runs its kernels on.
5//!
6//! Scope synthesis reads program structure on the interpreter
7//! (`PolydatProgram`), and pull plans resolve their indices against the
8//! interpreter's program. Every kernel that runs — each scope of the
9//! tree ([`crate::scope_kernel::ScopeKernel`]), each fiber's main kernel,
10//! its per-op kernels, the kernels adapters hold — runs on a compiled
11//! engine instead, bound under its parent through the engine-neutral
12//! `Kernel` surface (polydat's native_scope_trees.md).
13//!
14//! A compiled image stands in for an interpreter program only where it
15//! reports the same inputs and outputs in the same order, which is what
16//! lets an index resolved on the interpreter's program drive the image.
17//! polydat guarantees that for a scope module's images; an image
18//! compiled separately from the same source is checked here, once, and
19//! an image that disagrees is not used.
20
21use std::sync::Arc;
22
23use polydat::kernel::subcontext::{Child, RootMarker, ScopeModule};
24use polydat::kernel::{KernelProgram, PolydatProgram};
25
26/// The module an op-template scope finalizes to: its interpreter
27/// program plus the settings and write-throughs every other engine's
28/// image is built from.
29pub type OpTemplateModule = ScopeModule<Child<RootMarker>>;
30
31/// The engine scope, fiber and per-op kernels run on: polydat's default, the
32/// most native form the build has.
33pub fn fiber_engine() -> polydat::Engine {
34 polydat::Engine::default()
35}
36
37/// Whether `image` has `program`'s inputs and outputs in the same
38/// order, coordinates first — the condition for driving it with indices
39/// resolved on `program`.
40pub fn agrees(program: &PolydatProgram, image: &Arc<dyn KernelProgram>) -> bool {
41 let kernel = image.clone().create_kernel();
42 let outputs: Vec<String> = program
43 .output_names()
44 .iter()
45 .map(|n| n.to_string())
46 .collect();
47 kernel.input_names() == program.input_names()
48 && kernel.output_names() == outputs
49 && kernel.coord_count() == program.coord_count()
50}
51
52/// The fiber-engine image of a scope module, or `None` when the
53/// engine refuses it or it disagrees with the module's program; the
54/// module's kernels then stay on the interpreter.
55pub fn module_image(module: &OpTemplateModule, context: &str) -> Option<Arc<dyn KernelProgram>> {
56 match module.program_on(fiber_engine()) {
57 Ok(image) if agrees(module.program(), &image) => Some(image),
58 Ok(_) => {
59 warn_interpreter(
60 context,
61 "its compiled image lists inputs or outputs differently",
62 );
63 None
64 }
65 Err(e) => {
66 warn_interpreter(context, &e.to_string());
67 None
68 }
69 }
70}
71
72/// The fiber-engine image of a scope compiled from `source` under
73/// `options`, the same inputs that produced `program`; `None`, with a
74/// warning, when it cannot stand in for `program`.
75pub fn source_image(
76 program: &PolydatProgram,
77 source: &str,
78 options: &polydat::dsl::compile::CompileOptions,
79 context: &str,
80) -> Option<Arc<dyn KernelProgram>> {
81 let options = polydat::dsl::compile::CompileOptions {
82 engine: fiber_engine(),
83 ..options.clone()
84 };
85 match polydat::dsl::compile::compile_polydat_kernel_with_options(source, &options, None) {
86 Ok(kernel) => {
87 let image = kernel.into_program();
88 if agrees(program, &image) {
89 Some(image)
90 } else {
91 warn_interpreter(
92 context,
93 "its compiled image lists inputs or outputs differently",
94 );
95 None
96 }
97 }
98 Err(e) => {
99 warn_interpreter(context, &e.to_string());
100 None
101 }
102 }
103}
104
105fn warn_interpreter(context: &str, reason: &str) {
106 crate::diag!(
107 crate::observer::LogLevel::Warn,
108 "{context}: kernels stay on the interpreter ({} unavailable: {reason})",
109 fiber_engine()
110 );
111}