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