diff options
author | Brian Picciano <mediocregopher@gmail.com> | 2022-05-21 14:07:14 -0600 |
---|---|---|
committer | Brian Picciano <mediocregopher@gmail.com> | 2022-05-21 14:07:14 -0600 |
commit | f3340ae5f4ac6c60823bf4d14e1fcdbeaaec353c (patch) | |
tree | 41d7eda870083cfa0776d650478ceeb2baee2837 /static/src/_posts/2022-04-03-ginger-a-small-vm-update.md | |
parent | 55eb40d4bb489b2b26ab5d0ce2c70c7cb2f766b7 (diff) |
Remove old code related to static, it's not needed anymore
Diffstat (limited to 'static/src/_posts/2022-04-03-ginger-a-small-vm-update.md')
-rw-r--r-- | static/src/_posts/2022-04-03-ginger-a-small-vm-update.md | 222 |
1 files changed, 0 insertions, 222 deletions
diff --git a/static/src/_posts/2022-04-03-ginger-a-small-vm-update.md b/static/src/_posts/2022-04-03-ginger-a-small-vm-update.md deleted file mode 100644 index 74395f0..0000000 --- a/static/src/_posts/2022-04-03-ginger-a-small-vm-update.md +++ /dev/null @@ -1,222 +0,0 @@ ---- -title: >- - Ginger: A Small VM Update -description: >- - It works gooder now. -tags: tech -series: ginger ---- - -During some recent traveling I had to be pulled away from cryptic-net work for a -while. Instead I managed to spend a few free hours, and the odd international -plane ride, to fix the ginger vm. - -The problem, as it stood, was that it only functioned "correctly" in a very -accidental sense. I knew from the moment that I published it that it would get -mostly rewritten immediately. - -And so here we are, with a rewritten vm and some new realizations. - -## Operation - -The `Operation` type was previously defined like so: - -``` -type Operation interface { - Perform([]Thunk, Operation) (Thunk, error) -} -``` - -I'm not going to explain it, because it's both confusing and wrong. - -One thing that is helpful in a refactor, especially in a strongly typed -language, is to tag certain interfaces as being axiomatic, and conforming the -rest of your changes around those. If those interfaces are simple enough to -apply broadly _and_ accurately describe desired behavior, they will help -pre-decide many difficult decisions you'd otherwise have to make. - -So with that mind, I tagged `Operation` as being an axiomatic interface, given -that ginger is aiming to be a functional language (and I'm wondering if I should -just rename `Operation` to `Function`, while I'm at it). The new definition of -the interface is: - -``` -type Operation interface { - Perform(Value) Value -} -``` - -`Operation` takes and argument and returns a result, it could not possibly be -boiled down any further. By holding `Operation` to this definition and making -decisions from there, it was pretty clear what the next point of attack was. - -## If/Recur - -The reason that `Operation` had previously been defined in such a fucked up way -was to support the `if` and `recur` `Operation`s, as if they weren't different -than any other `Operation`s. But truthfully they are different, as they are -actually control flow constructs, and so require capabilities that no other -`Operation` would be allowed to use anyway. - -The new implementation reflects this. `if` and `recur` are now both handled -directly by the compiler, while global `Operation`s like `tupEl` are -implementations of the `Operation` interface. - -## Compile Step - -The previous iteration of the vm hadn't distinguished between a compile step and -a run step. In a way it did both at the same time, by abusing the `Thunk` type. -Separating the two steps, and ditching the `Thunk` type in the process, was the -next major change in the refactoring. - -The compile step can be modeled as a function which takes a `Graph` and returns -an `Operation`, where the `Graph`'s `in` and `out` names correspond to the -`Operation`'s argument and return, respectively. The run step then reads an -input from the user, calls the compiled `Operation` with that input, and outputs -the result back to the user. - -As an example, given the following program: - -``` -* six-or-more.gg - -max = { - a = tupEl < (in, 0) - b = tupEl < (in, 1) - out = if < (gt < (a, b), a, b) -} - -out = max < (in, 6) -``` - -we want to compile an `Operation` which accepts a number and returns the greater -of that number and 6. I'm going to use anonymous go functions to demonstrate the -anatomy of the compiled `Operation`, as that's what's happening in the current -compiler anyway. - -``` -// After compilation, this function will be in-memory and usable as an -// Operation. - -sixOrMore := func(in Value) Value { - - max := func(in Value) Value { - - a := tupEl(in, 0) - b := tupEl(in, 1) - - if a > b { - return a - } - - return b - } - - return max(in, 6) -} -``` - -Or at least, this is what I tried for _initially_. What I found was that it was -easier, in the context of how `graph.MapReduce` works, to make even the leaf -values, e.g. `in`, `0`, `1`, and `6`, map to `Operations` as well. `in` is -replaced with an anonymous function which returns its argument, and the numbers -are replaced with anonymous functions which ignore their argument and always -return their respective number. - -So the compiled `Operation` looks more like this: - -``` -// After compilation, this function will be in-memory and usable as an -// Operation. - -sixOrMore := func(in Value) Value { - - max := func(in Value) Value { - - a := tupEl( - func(in Value) Value { return in }(in), - func(_ Value) Value { return 0}(in), - ) - - b := tupEl( - func(in Value) Value { return in }(in), - func(_ Value) Value { return 1}(in), - ) - - if a > b { - return a - } - - return b - } - - return max( - func(in Value) Value { return in }(in), - func(_ Value) Value { return 6}(in), - ) -} -``` - -This added layer of indirection for all leaf values is not great for -performance, and there's probably further refactoring which could be done to -make the result look more like the original ideal. - -To make things a bit messier, even that representation isn't quite accurate to -the current result. The compiler doesn't properly de-duplicate work when -following name values. In other words, everytime `a` is referenced within `max`, -the `Operation` which the compiler produces will recompute `a` via `tupEl`. - -So the _actual_ compiled `Operation` looks more like this: - -``` -// After compilation, this function will be in-memory and usable as an -// Operation. - -sixOrMore := func(in Value) Value { - - return func(in Value) Value { - - if tupEl(func(in Value) Value { return in }(in), func(_ Value) Value { return 0}(in)) > - tupEl(func(in Value) Value { return in }(in), func(_ Value) Value { return 1}(in)) { - - return tupEl(func(in Value) Value { return in }(in), func(_ Value) Value { return 0}(in)) - } - - return tupEl(func(in Value) Value { return in }(in), func(_ Value) Value { return 1}(in)) - }( - func(in Value) Value { return in }(in), - func(_ Value) Value { return 6}(in), - ) -} -``` - -Clearly, there's some optimization to be done still. - -## Results - -While it's still not perfect, the new implementation is far and away better than -the old. This can be seen just in the performance for the fibonacci program: - -``` -# Previous VM - -$ time ./eval "$(cat examples/fib.gg)" 10 -55 - -real 0m8.737s -user 0m9.871s -sys 0m0.309s -``` - -``` -# New VM - -$ time ./eval "$(cat examples/fib.gg)" 50 -12586269025 - -real 0m0.003s -user 0m0.003s -sys 0m0.000s -``` - -They're not even comparable. |