forked from katzen-cafe/iowo
chore: rename executor -> evaluator and integrate into app
This commit is contained in:
parent
de9ca81b65
commit
3c529c3a1a
16 changed files with 229 additions and 115 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -63,6 +63,8 @@ dependencies = [
|
|||
"ariadne",
|
||||
"clap",
|
||||
"dirs",
|
||||
"eval",
|
||||
"ir",
|
||||
"owo-colors",
|
||||
"ron",
|
||||
"serde",
|
||||
|
@ -272,6 +274,7 @@ dependencies = [
|
|||
"clap",
|
||||
"image",
|
||||
"ir",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -6,14 +6,16 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = { workspace = true, features = [ "derive", "env" ] }
|
||||
serde = { workspace = true, features = [ "derive" ] }
|
||||
ron = "0.8"
|
||||
serde_json = "1.0"
|
||||
ariadne = "0.4"
|
||||
time = { version = "0.3", features = [ "local-offset" ] }
|
||||
clap = { workspace = true, features = [ "derive", "env" ] }
|
||||
dirs = "5"
|
||||
eval = { path = "../eval" }
|
||||
ir = { path = "../ir" }
|
||||
owo-colors = "4"
|
||||
ron = "0.8"
|
||||
serde = { workspace = true, features = [ "derive" ] }
|
||||
serde_json = "1.0"
|
||||
time = { version = "0.3", features = [ "local-offset" ] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use self::{
|
||||
|
@ -10,6 +12,9 @@ mod config_file;
|
|||
|
||||
/// this struct may hold all configuration
|
||||
pub struct Config {
|
||||
pub source: PathBuf,
|
||||
pub evaluator: eval::Available,
|
||||
|
||||
pub startup_msg: bool,
|
||||
}
|
||||
|
||||
|
@ -17,37 +22,37 @@ impl Config {
|
|||
/// Get the configs from all possible places (args, file, env...)
|
||||
pub fn read() -> Self {
|
||||
let args = Args::parse();
|
||||
let config_path = if let Some(config_path) = args.config_path {
|
||||
Ok(config_path)
|
||||
let config = if let Some(config) = args.config_path {
|
||||
Ok(config)
|
||||
} else {
|
||||
find_config_file()
|
||||
};
|
||||
|
||||
// try to read a maybe existing config file
|
||||
let file_config = if let Ok(config_path) = config_path {
|
||||
let file_config = Configs::read(config_path);
|
||||
|
||||
match file_config {
|
||||
Ok(c) => Some(c),
|
||||
Err(e) => {
|
||||
let config = config.ok().and_then(|path| {
|
||||
Configs::read(path).map_or_else(
|
||||
|e| {
|
||||
eprintln!("Config error: {e:?}");
|
||||
eprintln!("Proceeding with defaults or cli args...");
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
},
|
||||
Some,
|
||||
)
|
||||
});
|
||||
|
||||
if let Some(file_config) = file_config {
|
||||
if let Some(file) = config {
|
||||
Self {
|
||||
source: args.source,
|
||||
evaluator: args.evaluator.and(file.evaluator).unwrap_or_default(),
|
||||
// this is negated because to an outward api, the negative is more intuitive,
|
||||
// while in the source the other way around is more intuitive
|
||||
startup_msg: !(args.no_startup_message || file_config.no_startup_message),
|
||||
startup_msg: !(args.no_startup_message || file.no_startup_message),
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
source: args.source,
|
||||
startup_msg: !args.no_startup_message,
|
||||
evaluator: args.evaluator.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +61,7 @@ impl Config {
|
|||
pub mod error {
|
||||
/// Errors that can occur when reading configs
|
||||
#[derive(Debug)]
|
||||
pub enum ConfigError {
|
||||
pub enum Config {
|
||||
/// The config dir doesn't exist
|
||||
NoConfigDir,
|
||||
/// We didn't find a config file in the config dir
|
||||
|
@ -73,19 +78,19 @@ pub mod error {
|
|||
SerdeRonError(ron::error::SpannedError),
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for ConfigError {
|
||||
impl From<std::io::Error> for Config {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Self::IoError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for ConfigError {
|
||||
impl From<serde_json::Error> for Config {
|
||||
fn from(value: serde_json::Error) -> Self {
|
||||
Self::SerdeJsonError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ron::error::SpannedError> for ConfigError {
|
||||
impl From<ron::error::SpannedError> for Config {
|
||||
fn from(value: ron::error::SpannedError) -> Self {
|
||||
Self::SerdeRonError(value)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,14 @@ use clap::{builder::BoolishValueParser, ArgAction, Parser};
|
|||
|
||||
#[derive(Parser)]
|
||||
pub(crate) struct Args {
|
||||
/// What file contains the pipeline to evaluate.
|
||||
pub source: PathBuf,
|
||||
|
||||
/// How to actually run the pipeline.
|
||||
/// Overrides the config file. Defaults to the debug evaluator.
|
||||
#[arg(short, long)]
|
||||
pub evaluator: Option<eval::Available>,
|
||||
|
||||
/// Read this config file.
|
||||
#[arg(short, long)]
|
||||
pub config_path: Option<PathBuf>,
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::{
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::error::ConfigError;
|
||||
use super::error::Config;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Configs {
|
||||
|
@ -13,6 +13,7 @@ pub struct Configs {
|
|||
pub example_value: i32,
|
||||
#[serde(default)]
|
||||
pub no_startup_message: bool,
|
||||
pub evaluator: Option<eval::Available>,
|
||||
}
|
||||
|
||||
/// what the fuck serde why do i need this
|
||||
|
@ -21,9 +22,9 @@ fn default_example_value() -> i32 {
|
|||
}
|
||||
|
||||
/// Find the location of a config file and check if there is, in fact, a file
|
||||
pub(super) fn find_config_file() -> Result<PathBuf, ConfigError> {
|
||||
pub(super) fn find_config_file() -> Result<PathBuf, Config> {
|
||||
let Some(config_path) = dirs::config_dir() else {
|
||||
return Err(ConfigError::NoConfigDir);
|
||||
return Err(Config::NoConfigDir);
|
||||
};
|
||||
|
||||
let ron_path = config_path.with_file_name("config.ron");
|
||||
|
@ -34,19 +35,19 @@ pub(super) fn find_config_file() -> Result<PathBuf, ConfigError> {
|
|||
} else if Path::new(&json_path).exists() {
|
||||
Ok(json_path)
|
||||
} else {
|
||||
Err(ConfigError::NoConfigFileFound)
|
||||
Err(Config::NoConfigFileFound)
|
||||
}
|
||||
}
|
||||
|
||||
impl Configs {
|
||||
pub fn read(p: PathBuf) -> Result<Self, ConfigError> {
|
||||
pub fn read(p: PathBuf) -> Result<Self, Config> {
|
||||
match p
|
||||
.extension()
|
||||
.map(|v| v.to_str().expect("config path to be UTF-8"))
|
||||
{
|
||||
Some("ron") => Ok(serde_json::from_str(&fs::read_to_string(p)?)?),
|
||||
Some("json") => Ok(ron::from_str(&fs::read_to_string(p)?)?),
|
||||
e => Err(ConfigError::UnknownExtension(e.map(str::to_string))),
|
||||
e => Err(Config::UnknownExtension(e.map(str::to_string))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::fs;
|
||||
|
||||
use config::Config;
|
||||
use welcome_msg::print_startup_msg;
|
||||
|
||||
|
@ -14,4 +16,14 @@ fn main() {
|
|||
if cfg.startup_msg {
|
||||
print_startup_msg();
|
||||
}
|
||||
|
||||
let source =
|
||||
fs::read_to_string(cfg.source).expect("can't find source file lol handle me better please");
|
||||
|
||||
let ir =
|
||||
ir::from_ron(&source).expect("aww failed to parse source to graph ir handle me better");
|
||||
|
||||
let mut machine = cfg.evaluator.pick();
|
||||
machine.feed(ir);
|
||||
machine.eval_full();
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ edition = "2021"
|
|||
clap = { workspace = true, features = [ "derive" ] }
|
||||
image = "0.24"
|
||||
ir = { path = "../ir" }
|
||||
serde = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
pub(crate) struct CpuExecutor;
|
|
@ -1,37 +0,0 @@
|
|||
use ir::instruction::{Filter, Kind};
|
||||
|
||||
use crate::value::Dynamic;
|
||||
mod instructions;
|
||||
|
||||
pub struct Executor;
|
||||
|
||||
impl crate::Executor for Executor {
|
||||
fn execute(instruction: Kind, input: Option<Dynamic>) -> Option<Dynamic> {
|
||||
match instruction {
|
||||
Kind::Read(read_instruction) => {
|
||||
Some(Dynamic::Image(instructions::read::read(read_instruction)))
|
||||
}
|
||||
Kind::Write(write_instruction) => {
|
||||
instructions::write::write(
|
||||
write_instruction,
|
||||
match input {
|
||||
Some(Dynamic::Image(ref img)) => img,
|
||||
_ => panic!("awawwawwa"),
|
||||
},
|
||||
);
|
||||
None
|
||||
}
|
||||
Kind::Math(_) => todo!(),
|
||||
Kind::Blend(_) => todo!(),
|
||||
Kind::Noise(_) => todo!(),
|
||||
Kind::Filter(filter_instruction) => match filter_instruction {
|
||||
Filter::Invert => Some(Dynamic::Image(instructions::filters::invert::invert(
|
||||
match input {
|
||||
Some(Dynamic::Image(img)) => img,
|
||||
_ => panic!("invalid value type for invert"),
|
||||
},
|
||||
))),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
57
crates/eval/src/kind/debug/mod.rs
Normal file
57
crates/eval/src/kind/debug/mod.rs
Normal file
|
@ -0,0 +1,57 @@
|
|||
use std::mem;
|
||||
|
||||
use ir::{
|
||||
instruction::{Filter, Kind},
|
||||
GraphIr, Instruction, InstructionRef,
|
||||
};
|
||||
|
||||
use crate::value::Dynamic;
|
||||
mod instr;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Evaluator {
|
||||
ir: GraphIr,
|
||||
}
|
||||
|
||||
impl crate::Evaluator for Evaluator {
|
||||
fn feed(&mut self, ir: GraphIr) {
|
||||
// TODO: should add instead of replace, see note in Evaluator trait above this method
|
||||
self.ir = ir;
|
||||
}
|
||||
|
||||
fn eval_full(&mut self) {
|
||||
let queue: Vec<Instruction> = self
|
||||
.ir
|
||||
.topological_sort()
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
for instr in queue {
|
||||
self.step(instr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Evaluator {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn step(&mut self, instr: Instruction) {
|
||||
let _ = match instr.kind {
|
||||
Kind::Read(details) => Some(Dynamic::Image(instr::read::read(details))),
|
||||
Kind::Write(details) => {
|
||||
instr::write::write(details, todo!());
|
||||
None
|
||||
}
|
||||
Kind::Math(_) => todo!(),
|
||||
Kind::Blend(_) => todo!(),
|
||||
Kind::Noise(_) => todo!(),
|
||||
Kind::Filter(filter_instruction) => match filter_instruction {
|
||||
Filter::Invert => Some(Dynamic::Image(instr::filters::invert::invert(
|
||||
match todo!() {
|
||||
Some(Dynamic::Image(img)) => img,
|
||||
_ => panic!("invalid value type for invert"),
|
||||
},
|
||||
))),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
1
crates/eval/src/kind/mod.rs
Normal file
1
crates/eval/src/kind/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod debug;
|
|
@ -1,34 +1,43 @@
|
|||
use ir::instruction::Kind;
|
||||
use value::Dynamic;
|
||||
use ir::GraphIr;
|
||||
|
||||
mod debug;
|
||||
mod kind;
|
||||
mod value;
|
||||
|
||||
/// The available executors
|
||||
/// unused in early dev.
|
||||
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
||||
pub enum RegisteredExecutor {
|
||||
/// the debug executor is single threaded and really, *really* slow. And unstable. Don't use. Unless you're a dev working on this.
|
||||
/// Can collapse a [`GraphIr`] in meaningful ways and do interesting work on it.
|
||||
///
|
||||
/// It's surprisingly difficult to find a fitting description for this.
|
||||
pub trait Evaluator {
|
||||
/// Take some [`GraphIr`] which will then be processed later.
|
||||
/// May be called multiple times, in which the [`GraphIr`]s should add up.
|
||||
// TODO: atm they definitely don't add up -- add some functionality to GraphIr to
|
||||
// make it combine two graphs into one
|
||||
fn feed(&mut self, ir: GraphIr);
|
||||
|
||||
/// Walk through the _whole_ [`GraphIr`] and run through each instruction.
|
||||
fn eval_full(&mut self);
|
||||
|
||||
// TODO: for an LSP or the like, eval_single which starts at a given instr
|
||||
}
|
||||
|
||||
/// The available [`Evaluator`]s.
|
||||
///
|
||||
/// Checklist for adding new ones:
|
||||
///
|
||||
/// 1. Create a new module under the [`kind`] module.
|
||||
/// 2. Add a struct and implement [`Evaluator`] for it.
|
||||
#[derive(Clone, Copy, Debug, Default, clap::ValueEnum, serde::Deserialize, serde::Serialize)]
|
||||
pub enum Available {
|
||||
/// Runs fully on the CPU. Single-threaded, debug-friendly and quick to implement.
|
||||
#[default]
|
||||
Debug,
|
||||
}
|
||||
|
||||
trait Executor {
|
||||
fn execute(instruction: Kind, input: Option<Dynamic>) -> Option<Dynamic>;
|
||||
}
|
||||
|
||||
pub fn execute_all(instructions: Vec<Kind>) {
|
||||
let mut tmp = None;
|
||||
|
||||
for instruction in instructions {
|
||||
tmp = debug::Executor::execute(instruction, tmp);
|
||||
impl Available {
|
||||
/// Selects the [`Evaluator`] corresponding to this label.
|
||||
#[must_use]
|
||||
pub fn pick(&self) -> Box<dyn Evaluator> {
|
||||
match self {
|
||||
Self::Debug => Box::new(kind::debug::Evaluator::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scratchpad lol:
|
||||
// execution structure:
|
||||
// 1. take in rpl
|
||||
// 2. analyse/validate structure against allowed executors
|
||||
// 3. assign executors to instructions
|
||||
// 4. optimize
|
||||
// 5. prepare memory management patterns
|
||||
// 6. run
|
||||
|
|
|
@ -56,7 +56,7 @@ pub fn from_ron(source: &str) -> ron::error::SpannedResult<GraphIr> {
|
|||
/// So the vertices of the DAG are the **sockets**
|
||||
/// (which are either [`id::Input`] or [`id::Output`] depending on the direction),
|
||||
/// and each **socket** in turn belongs to an **instruction**.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct GraphIr {
|
||||
/// "Backbone" storage of all **instruction** IDs to
|
||||
/// what **kind of instruction** they are.
|
||||
|
@ -123,13 +123,13 @@ impl GraphIr {
|
|||
/// to actually have multiple [`GraphIr`]s at one point in time.
|
||||
/// Open an issue if that poses a problem for you.
|
||||
#[must_use]
|
||||
pub fn resolve<'ir>(&'ir self, subject: &id::Instruction) -> Option<Instruction<'ir>> {
|
||||
pub fn resolve<'ir>(&'ir self, subject: &id::Instruction) -> Option<InstructionRef<'ir>> {
|
||||
let (id, kind) = self.instructions.get_key_value(subject)?;
|
||||
|
||||
let input_sources = self.input_sources(subject)?.collect();
|
||||
let output_targets = self.output_targets(subject)?.collect();
|
||||
|
||||
Some(Instruction {
|
||||
Some(InstructionRef {
|
||||
id,
|
||||
kind,
|
||||
input_sources,
|
||||
|
@ -141,7 +141,7 @@ impl GraphIr {
|
|||
///
|
||||
/// The same caveats as for [`GraphIr::resolve`] apply.
|
||||
#[must_use]
|
||||
pub fn owner_of_input<'ir>(&'ir self, input: &id::Input) -> Option<Instruction<'ir>> {
|
||||
pub fn owner_of_input<'ir>(&'ir self, input: &id::Input) -> Option<InstructionRef<'ir>> {
|
||||
self.resolve(&input.socket().belongs_to)
|
||||
}
|
||||
|
||||
|
@ -149,7 +149,7 @@ impl GraphIr {
|
|||
///
|
||||
/// The same caveats as for [`GraphIr::resolve`] apply.
|
||||
#[must_use]
|
||||
pub fn owner_of_output<'ir>(&'ir self, output: &id::Output) -> Option<Instruction<'ir>> {
|
||||
pub fn owner_of_output<'ir>(&'ir self, output: &id::Output) -> Option<InstructionRef<'ir>> {
|
||||
self.resolve(&output.socket().belongs_to)
|
||||
}
|
||||
|
||||
|
@ -163,7 +163,7 @@ impl GraphIr {
|
|||
#[must_use]
|
||||
// yes, this function could probably return an iterator and be lazy
|
||||
// no, not today
|
||||
pub fn topological_sort(&self) -> Vec<Instruction> {
|
||||
pub fn topological_sort(&self) -> Vec<InstructionRef> {
|
||||
// count how many incoming edges each vertex has
|
||||
let mut nonzero_input_counts: Map<_, NonZeroUsize> =
|
||||
self.rev_edges
|
||||
|
@ -240,18 +240,58 @@ impl GraphIr {
|
|||
}
|
||||
}
|
||||
|
||||
/// A full instruction in context, with its inputs and outputs.
|
||||
/// Constructs an [`id::Socket`] a bit more tersely.
|
||||
fn socket(id: &id::Instruction, idx: u16) -> id::Socket {
|
||||
id::Socket {
|
||||
belongs_to: id.clone(),
|
||||
idx: id::SocketIdx(idx),
|
||||
}
|
||||
}
|
||||
|
||||
/// A full instruction bundeled in context, with its inputs and outputs.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Instruction<'ir> {
|
||||
pub struct Instruction {
|
||||
pub id: id::Instruction,
|
||||
pub kind: instruction::Kind,
|
||||
|
||||
// can't have these two public since then a user might corrupt their length
|
||||
input_sources: Vec<Option<id::Output>>,
|
||||
output_targets: Vec<Set<id::Input>>,
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
/// Where this instruction gets its inputs from.
|
||||
///
|
||||
/// [`None`] means that this input is unfilled,
|
||||
/// and must be filled before the instruction can be ran.
|
||||
#[must_use]
|
||||
pub fn input_sources(&self) -> &[Option<id::Output>] {
|
||||
&self.input_sources
|
||||
}
|
||||
|
||||
/// To whom outputs are sent.
|
||||
#[must_use]
|
||||
pub fn output_targets(&self) -> &[Set<id::Input>] {
|
||||
&self.output_targets
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Instruction`], but every single field is borrowed instead.
|
||||
/// See its docs.
|
||||
///
|
||||
/// Use the [`From`] impl to handily convert into an [`Instruction`].
|
||||
/// The other way around is unlikely to be wanted — since you already have an [`Instruction`],
|
||||
/// chances are you just want to take a reference (`&`) of it.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct InstructionRef<'ir> {
|
||||
pub id: &'ir id::Instruction,
|
||||
pub kind: &'ir instruction::Kind,
|
||||
|
||||
// can't have these two public since then a user might corrupt their length
|
||||
input_sources: Vec<Option<&'ir id::Output>>,
|
||||
output_targets: Vec<Option<&'ir Set<id::Input>>>,
|
||||
}
|
||||
|
||||
impl<'ir> Instruction<'ir> {
|
||||
impl<'ir> InstructionRef<'ir> {
|
||||
/// Where this instruction gets its inputs from.
|
||||
///
|
||||
/// [`None`] means that this input is unfilled,
|
||||
|
@ -268,10 +308,23 @@ impl<'ir> Instruction<'ir> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Constructs an [`id::Socket`] a bit more tersely.
|
||||
fn socket(id: &id::Instruction, idx: u16) -> id::Socket {
|
||||
id::Socket {
|
||||
belongs_to: id.clone(),
|
||||
idx: id::SocketIdx(idx),
|
||||
// would love to use ToOwned but Rust has no specialization yet
|
||||
// and it'd hurt a blanket impl of ToOwned otherwise
|
||||
impl From<InstructionRef<'_>> for Instruction {
|
||||
fn from(source: InstructionRef<'_>) -> Self {
|
||||
Self {
|
||||
id: source.id.clone(),
|
||||
kind: source.kind.clone(),
|
||||
input_sources: source
|
||||
.input_sources
|
||||
.into_iter()
|
||||
.map(Option::<&_>::cloned)
|
||||
.collect(),
|
||||
output_targets: source
|
||||
.output_targets
|
||||
.into_iter()
|
||||
.map(|outputs| outputs.map(Clone::clone).unwrap_or_default())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
subtitle: [don't worry, we're just dreaming],
|
||||
)
|
||||
|
||||
= Evaluation stages
|
||||
= Processing stages
|
||||
|
||||
#graphics.stages-overview
|
||||
|
||||
|
@ -18,7 +18,7 @@ This has a number of benefits and implications:
|
|||
- Bugs are easier to trace down to one stage.
|
||||
- Stages are also replacable, pluggable and usable somewhere else.
|
||||
- For example,
|
||||
one could write a Just-In-Time compiler as a new executor to replace the runtime stage,
|
||||
one could write a Just-In-Time compiler as a new evaluator to replace the runtime stage,
|
||||
while preserving the source #arrow() graph IR step.
|
||||
|
||||
However, this also makes the architecture somewhat more complicated. So here we try our best to describe how each stage looks like. If you have any feedback, feel free to drop it on #link("https://forge.katzen.cafe/katzen-cafe/iowo/issues")[the issues in the repository]!
|
||||
|
@ -155,12 +155,12 @@ Runs through all functions in the graph IR.
|
|||
It does not have any significantly other representation,
|
||||
and despite its name there's _no_ bytecode involved.
|
||||
|
||||
Different runtimes are called executors.
|
||||
Executors operate on instructions instead of functions.
|
||||
Different runtimes are called evaluators.
|
||||
Evaluators operate on instructions instead of functions.
|
||||
|
||||
=== Scheduler
|
||||
|
||||
Looks at the graph IR and decides when the VM should execute what.
|
||||
Looks at the graph IR and decides when the VM should evaluate what.
|
||||
|
||||
=== VM <vm>
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
"AST",
|
||||
"graph IR",
|
||||
"runtime",
|
||||
"executor",
|
||||
"evaluator",
|
||||
|
||||
"optimizer",
|
||||
"scheduler",
|
||||
|
|
Loading…
Reference in a new issue