docs(design): add graph ir repr and explain a bit

This commit is contained in:
multisn8 2024-01-10 21:10:05 +01:00
parent b9ea83b1c6
commit bebf2a97a4
Signed by: multisamplednight
GPG key ID: C81EF9B053977241
4 changed files with 202 additions and 8 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@
/target /target
.pre-commit-config.yaml .pre-commit-config.yaml
*.pdf *.pdf
*.png

View file

@ -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

View file

@ -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.

View file

@ -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%),