Charles Oliver Nutter
2018-01-02 20:17:54 UTC
Hello all, long time no write!
I'm finally playing with writing a "compiler" for JRuby that uses only
method handles to represent code structure. For most simple expressions,
this obviously works well. However I'm having trouble with blocks of code
that contain multiple expressions.
Starting with the standard call signature through the handle tree, we have
a basic (Object[])Object type. The Object[] contains local variable state
for the script, and will be as wide as there are local variables. AST nodes
are basically compiled into little functions that take in the variable
state and produce a value. In this way, every expression in the tree can be
compiled, including local variable sets and gets, loops, and so on.
Now the tricky bit...
The root node for a given script contains one or more expressions that
should be executed in sequence, with the final result being returned. The
way I'm handling this in method handles is as follows (invokebinder code
but hopefully easy to read):
MethodHandle[] handles =
Arrays
.stream(rootNode.children())
.map(node -> compile(node))
.toArray(n -> new MethodHandle[n]);
return Binder.from(Object.class, Object[].class)
.permute(new int[handles.length])
.filter(0, handles)
.drop(0, handles.length - 1)
.identity();
In pseudo-code, this basically duplicates the Object[] as many times as
there are lines of code to execute, and then uses filterArguments to
evaluate each in turn. Then everything but the last result is culled and
the final result is returned.
Unfortunately, this doesn't work right: filterArguments appears to execute
in reverse order. When I try to run a simple script like "a = 1; a" the "a"
value comes back null, because it is executed first.
Is this expected? Do filters, when executed, actually process from the last
argument back, rather than the first argument forward?
Note: I know this would be possible to do with guaranteed ordering using
the new loop combinators in 9. I'm working up to that for examples for a
talk.
- Charlie
I'm finally playing with writing a "compiler" for JRuby that uses only
method handles to represent code structure. For most simple expressions,
this obviously works well. However I'm having trouble with blocks of code
that contain multiple expressions.
Starting with the standard call signature through the handle tree, we have
a basic (Object[])Object type. The Object[] contains local variable state
for the script, and will be as wide as there are local variables. AST nodes
are basically compiled into little functions that take in the variable
state and produce a value. In this way, every expression in the tree can be
compiled, including local variable sets and gets, loops, and so on.
Now the tricky bit...
The root node for a given script contains one or more expressions that
should be executed in sequence, with the final result being returned. The
way I'm handling this in method handles is as follows (invokebinder code
but hopefully easy to read):
MethodHandle[] handles =
Arrays
.stream(rootNode.children())
.map(node -> compile(node))
.toArray(n -> new MethodHandle[n]);
return Binder.from(Object.class, Object[].class)
.permute(new int[handles.length])
.filter(0, handles)
.drop(0, handles.length - 1)
.identity();
In pseudo-code, this basically duplicates the Object[] as many times as
there are lines of code to execute, and then uses filterArguments to
evaluate each in turn. Then everything but the last result is culled and
the final result is returned.
Unfortunately, this doesn't work right: filterArguments appears to execute
in reverse order. When I try to run a simple script like "a = 1; a" the "a"
value comes back null, because it is executed first.
Is this expected? Do filters, when executed, actually process from the last
argument back, rather than the first argument forward?
Note: I know this would be possible to do with guaranteed ordering using
the new loop combinators in 9. I'm working up to that for examples for a
talk.
- Charlie