forked from katzen-cafe/iowo
docs(design): add graph ir repr and explain a bit
This commit is contained in:
parent
b9ea83b1c6
commit
bebf2a97a4
4 changed files with 202 additions and 8 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,3 +3,4 @@
|
||||||
/target
|
/target
|
||||||
.pre-commit-config.yaml
|
.pre-commit-config.yaml
|
||||||
*.pdf
|
*.pdf
|
||||||
|
*.png
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
cetz.canvas({
|
cetz.canvas({
|
||||||
// any preamble-ish stuff can go here
|
// any preamble-ish stuff can go here
|
||||||
set-style(
|
set-style(
|
||||||
mark: (angle: 90deg)
|
mark: (angle: 90deg),
|
||||||
|
stroke: (cap: "round", join: "round"),
|
||||||
)
|
)
|
||||||
|
|
||||||
what
|
what
|
||||||
|
@ -24,7 +25,7 @@
|
||||||
|
|
||||||
// smaller stuff
|
// smaller stuff
|
||||||
|
|
||||||
#let arrow(length: 1cm, lift: 4pt, stroke: 1pt) = graphic({
|
#let arrow(length: 0.4cm, lift: 3pt, stroke: 1pt) = graphic({
|
||||||
line((0, lift), (rel: (length, 0)), mark: (end: ">", stroke: stroke))
|
line((0, lift), (rel: (length, 0)), mark: (end: ">", stroke: stroke))
|
||||||
|
|
||||||
// hack for the bounding box bottom
|
// hack for the bounding box bottom
|
||||||
|
@ -34,7 +35,7 @@
|
||||||
|
|
||||||
// larger stuff
|
// larger stuff
|
||||||
|
|
||||||
#let nodes(
|
#let sequence(
|
||||||
distance: 3cm,
|
distance: 3cm,
|
||||||
arrow-spacing: 0.15cm,
|
arrow-spacing: 0.15cm,
|
||||||
// cetz will support rounded rects in 0.2.0
|
// cetz will support rounded rects in 0.2.0
|
||||||
|
@ -64,14 +65,187 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
#let stages-overview = canvas({
|
#let stages-overview = canvas({
|
||||||
nodes(
|
sequence(
|
||||||
[Source],
|
[Source],
|
||||||
[AST],
|
|
||||||
[Graph IR],
|
[Graph IR],
|
||||||
[Runtime],
|
[Runtime],
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// A few commands to help demonstration in the docs.
|
||||||
|
// Supply a string to mark the input or output as simple.
|
||||||
|
// (fwiw in typst, parenthesis around a single expression just evaluate the expression, and don't put it into an array)
|
||||||
|
#let cmds = (
|
||||||
|
"const": (
|
||||||
|
inputs: (),
|
||||||
|
outputs: ("data",),
|
||||||
|
),
|
||||||
|
"open": (
|
||||||
|
inputs: ("path",),
|
||||||
|
outputs: ("data",),
|
||||||
|
),
|
||||||
|
"save": (
|
||||||
|
inputs: ("data", "path"),
|
||||||
|
outputs: (),
|
||||||
|
),
|
||||||
|
"show": (
|
||||||
|
inputs: ("data",),
|
||||||
|
outputs: (),
|
||||||
|
),
|
||||||
|
"invert": (
|
||||||
|
inputs: ("base",),
|
||||||
|
outputs: ("",),
|
||||||
|
),
|
||||||
|
"mask": (
|
||||||
|
inputs: ("base", "stencil"),
|
||||||
|
outputs: ("masked", "rest"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
#let opposite(anchor) = {
|
||||||
|
(
|
||||||
|
"bottom": "top",
|
||||||
|
"top": "bottom",
|
||||||
|
)
|
||||||
|
.at(anchor)
|
||||||
|
}
|
||||||
|
|
||||||
|
#let sockets(
|
||||||
|
start,
|
||||||
|
stop,
|
||||||
|
sockets,
|
||||||
|
socket-size: (0.5, 0.1),
|
||||||
|
socket-shape: "circle",
|
||||||
|
parent-name: "",
|
||||||
|
label-anchor: "bottom",
|
||||||
|
) = {
|
||||||
|
for (i, socket) in sockets.enumerate() {
|
||||||
|
let x-ratio = (i + 1) / (sockets.len() + 1)
|
||||||
|
let center = (start, x-ratio, stop)
|
||||||
|
|
||||||
|
let socket-name = parent-name + "/" + socket
|
||||||
|
|
||||||
|
let common-args = (name: socket-name, fill: black)
|
||||||
|
if socket-shape == "rect" {
|
||||||
|
rect(
|
||||||
|
(rel: ((0, 0), -0.5, socket-size), to: center),
|
||||||
|
(rel: socket-size),
|
||||||
|
..common-args,
|
||||||
|
)
|
||||||
|
} else if socket-shape == "circle" {
|
||||||
|
circle(
|
||||||
|
center,
|
||||||
|
radius: socket-size.at(1),
|
||||||
|
..common-args,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
panic("unknown socket shape: `" + socket-shape + "`")
|
||||||
|
}
|
||||||
|
set-style(fill: none)
|
||||||
|
|
||||||
|
// don't ask why, I don't know myself
|
||||||
|
let use-opposite-anchor = socket-shape == "circle"
|
||||||
|
content(
|
||||||
|
socket-name + "." + if use-opposite-anchor { opposite(label-anchor) } else { label-anchor },
|
||||||
|
anchor: opposite(label-anchor),
|
||||||
|
box(inset: 0.25em, text(8pt, socket)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#let node(
|
||||||
|
at,
|
||||||
|
size: (3, 1.5),
|
||||||
|
ty: none,
|
||||||
|
body: none,
|
||||||
|
name: "unnamed",
|
||||||
|
) = {
|
||||||
|
set-origin(at)
|
||||||
|
let label = [#ty]
|
||||||
|
if body != none {
|
||||||
|
label += [\ ] + text(0.7em, font: "IBM Plex Mono", body)
|
||||||
|
size.at(1) += 0.5
|
||||||
|
}
|
||||||
|
rect((0, 0), (rel: size), name: name)
|
||||||
|
content(((0, 0), 0.5, size), align(center, label))
|
||||||
|
|
||||||
|
// input and output sockets
|
||||||
|
if ty == none { return }
|
||||||
|
let ty = cmds.at(ty)
|
||||||
|
|
||||||
|
let sockets = sockets.with(parent-name: name)
|
||||||
|
sockets(
|
||||||
|
((0, 0), "|-", size),
|
||||||
|
size,
|
||||||
|
ty.inputs,
|
||||||
|
)
|
||||||
|
sockets(
|
||||||
|
(0, 0),
|
||||||
|
((0, 0), "-|", size),
|
||||||
|
label-anchor: "top",
|
||||||
|
ty.outputs,
|
||||||
|
)
|
||||||
|
|
||||||
|
// helper text
|
||||||
|
let helper(base, label, where) = {
|
||||||
|
if not type(base) != list or base.len() != 0 {
|
||||||
|
content(
|
||||||
|
name + "." + where + "-left",
|
||||||
|
anchor: "right",
|
||||||
|
box(inset: 0.25em, text(fill: luma(75%), label))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
helper(ty.inputs, [in], "top")
|
||||||
|
helper(ty.outputs, [out], "bottom")
|
||||||
|
|
||||||
|
// reset the origin transform so other nodes can still work in the global coord system
|
||||||
|
// can't use groups since otherwise the anchors are not exported
|
||||||
|
set-origin(((0, 0), -1, at))
|
||||||
|
}
|
||||||
|
|
||||||
|
#let connect(from, to, bend: 1.5, mark-cfg: (size: 0.25, offset: 0.1)) = {
|
||||||
|
bezier(
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
(rel: (0, -bend * 1cm), to: from),
|
||||||
|
(rel: (0, bend * 1cm), to: to),
|
||||||
|
)
|
||||||
|
mark(
|
||||||
|
(rel: (0, mark-cfg.size + mark-cfg.offset), to: to),
|
||||||
|
(rel: (0, -mark-cfg.size)),
|
||||||
|
symbol: ">",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#let graph-example = canvas({
|
||||||
|
let x = 3
|
||||||
|
let y = -3
|
||||||
|
node((-x, -0.75 * y), ty: "const", body: "\"base.png\"", name: "base")
|
||||||
|
node((x, -0.75 * y), ty: "const", body: "\"stencil.png\"", name: "stencil")
|
||||||
|
node((-x, 0), ty: "open", name: "a")
|
||||||
|
node((x, 0), ty: "open", name: "b")
|
||||||
|
node((0, y), ty: "mask", name: "c")
|
||||||
|
node((-x, 2 * y), ty: "invert", name: "d")
|
||||||
|
node((-x, 2.75 * y), ty: "show", name: "e")
|
||||||
|
node((x, 2.75 * y), ty: "show", name: "f")
|
||||||
|
|
||||||
|
connect("base/data", "a/path")
|
||||||
|
connect("stencil/data", "b/path")
|
||||||
|
|
||||||
|
connect("a/data", "c/base")
|
||||||
|
connect("b/data", "c/stencil")
|
||||||
|
|
||||||
|
connect("c/masked", "d/base")
|
||||||
|
connect("d/", "e/data")
|
||||||
|
|
||||||
|
connect("c/rest", "f/data", bend: 2.5)
|
||||||
|
})
|
||||||
|
|
||||||
// literally just for standalone display of the graphics alone
|
// literally just for standalone display of the graphics alone
|
||||||
#import "../template.typ": conf
|
#import "../template.typ": conf
|
||||||
#show: conf
|
#show: conf
|
||||||
|
#set page(width: auto, height: auto)
|
||||||
|
|
||||||
|
#graph-example
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#import "../template.typ": conf
|
#import "../template.typ": conf
|
||||||
#import "graphics.typ"
|
#import "graphics.typ"
|
||||||
|
#import graphics: arrow
|
||||||
|
|
||||||
#show: conf.with(title: [iOwO design], subtitle: [don't worry, we're just dreaming])
|
#show: conf.with(title: [iOwO design], subtitle: [don't worry, we're just dreaming])
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@
|
||||||
// at which point `query` doesn't find anything anymore
|
// at which point `query` doesn't find anything anymore
|
||||||
#let terms = (
|
#let terms = (
|
||||||
"source",
|
"source",
|
||||||
"ast",
|
"AST",
|
||||||
"graph IR",
|
"graph IR",
|
||||||
"runtime",
|
"runtime",
|
||||||
|
|
||||||
|
@ -85,18 +86,29 @@
|
||||||
- algebraic enums
|
- algebraic enums
|
||||||
- traits (`Numeric`...)
|
- traits (`Numeric`...)
|
||||||
|
|
||||||
|
#pagebreak()
|
||||||
|
|
||||||
= Execution stages
|
= Execution stages
|
||||||
|
|
||||||
#graphics.stages-overview
|
#graphics.stages-overview
|
||||||
|
|
||||||
|
iOwO operates in stages. This has a number of benefits and implications:
|
||||||
|
|
||||||
|
- One can just work on one aspect of the stages without having to know about the rest.
|
||||||
|
- 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 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]!
|
||||||
|
|
||||||
== Source <source>
|
== Source <source>
|
||||||
|
|
||||||
```iowo
|
```iowo
|
||||||
open base.png >|
|
open base.png >|
|
||||||
open stencil.png >|
|
open stencil.png >|
|
||||||
mask
|
mask
|
||||||
|
|> show12
|
||||||
|> (invert | show)
|
|> (invert | show)
|
||||||
|> show
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Functional and pipeline-based.
|
Functional and pipeline-based.
|
||||||
|
@ -151,8 +163,8 @@ To handle each output of a streamer individually, they can be _split_:
|
||||||
|
|
||||||
```iowo
|
```iowo
|
||||||
mask
|
mask
|
||||||
|> invert -- would invert the unmasked part
|
|
||||||
|> show -- would show the masked part
|
|> show -- would show the masked part
|
||||||
|
|> invert -- would invert the unmasked part
|
||||||
```
|
```
|
||||||
|
|
||||||
==== Combination <combination>
|
==== Combination <combination>
|
||||||
|
@ -193,16 +205,22 @@ Done with any of `--` or `//`.
|
||||||
|
|
||||||
== Graph IR
|
== Graph IR
|
||||||
|
|
||||||
|
#graphics.graph-example
|
||||||
|
|
||||||
The parsed representation of the source, and also what the runtime operates on.
|
The parsed representation of the source, and also what the runtime operates on.
|
||||||
|
|
||||||
In a way, this is the AST, except that it's not a tree.
|
In a way, this is the AST, except that it's not a tree.
|
||||||
|
|
||||||
|
It is represented in iOwO using adjacencies, where essentially the vertices#footnote[Nodes or commands in this case.] and their edges#footnote[Connections or pipes in this case.] are stored separately.
|
||||||
|
|
||||||
=== Optimizer
|
=== Optimizer
|
||||||
|
|
||||||
Merges and simplifies commands in the graph IR.
|
Merges and simplifies commands in the graph IR.
|
||||||
|
|
||||||
== Runtime
|
== Runtime
|
||||||
|
|
||||||
|
Runs through all commands in the graph IR. It does not have any significantly other representation, and despite its name there's _no_ bytecode involved.
|
||||||
|
|
||||||
=== Scheduler
|
=== 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 execute what.
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
set text(..fonts.main)
|
set text(..fonts.main)
|
||||||
set heading(numbering: "A.1")
|
set heading(numbering: "A.1")
|
||||||
|
|
||||||
|
show link: text.with(fill: blue)
|
||||||
show heading: it => text(..fonts.heading, it)
|
show heading: it => text(..fonts.heading, it)
|
||||||
show raw.where(block: true): box.with(
|
show raw.where(block: true): box.with(
|
||||||
fill: luma(95%),
|
fill: luma(95%),
|
||||||
|
|
Loading…
Reference in a new issue