New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implementing Nesting #15
Conversation
The current way on how importing values from ancestors works mainly aims at hiding how it works from the outside. The main problem was that it required a complex implementation in each backend which only one of the backends (x86) implemented. This commit changes accessing nested values, making it completely frontend based. Furthermore it allows the library users to retrieve the frame pointer of a parent and pass it in any way they like instead of forcing them to use jit_insn_call. This means the new way allows implementing things such as lambdas as function pointer (closure) together with a pointer to the parent frame and then call this lambda using jit_insn_call_indirect.
The previous commit intrduced a couple of bugs. While importing values from the parent worked importing values from a higher ancestor or calling siblings was broken. This commit fixes these issues
… functions when calling a nested function the function needs a pointer to the parent frame to import values. With the new nested importing system the work of importing values was taken away from the backends, so this commit takes away setting up calls to nested functions from the backend. Instead this is done in jit-rules.c (and architecture specific versions of jit-rule.c).
… frame pointer currently nested functions can only be called using jit_insn_call. This requires the caller to have the jit_function_t object available. The new function jit_insn_call_nested_indirect is similar to jit_insn_call_indirect - allowing to call a function using a jit_value_t - but it has an additional parameter for passing a pointer to the parent frame of the callee. Using jit_insn_get_parent_frame_pointer_of and jit_function_to_closure one can now create both the closure and the parent frame and pass it around together as jit values, calling them does not require the caller to know the actual jit_function_t he is calling. This makes it possible to implement what C# calls delegates.
previousely this was handeled by incoming_reg however the register allocator doesn't allow to move registers from different reg classes. Nesting is tested on x86-86, x86 and the interpreter.
… memory location this commit also makes sure that the frame offset of parameters is available when the function is built, so they dont get assigned a different offset when being imported.
jit/jit-insn.c
Outdated
while(frame_start != 0 && nesting_level-- > 0) | ||
{ | ||
jit_value_ref(func, current_func->parent_frame); | ||
_jit_gen_fix_value(current_func->parent_frame); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jit_gen* functions are normally called during code generation so it is inappropriate to call one here. But I think for nested functions we could reserve a position for the parent pointer just at the beginning of the stack frame.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_jit_gen_fix_value
is used later in line 6427 and 6444 again. It is used on every value imported by a nested function.
On one hand this means that reserving stack space isn't really an option as we don't know how many values we are importing and thus don't know how much space we need to reserve.
On the other hand reserving frame space is exactly what _jit_gen_fix_value
does. It increments the blocks frame size and assigns a frame offset to the value. Apart from alignment rules it is the same on all backends.
I agree that for good coupling the frontend should not make calls to jit_gen_*
functions. We could create a frontend function for managing stack space, but it would need knowledge about stack alignment etc.. Alternatively we could re-add an IMPORT opcode which calls _jit_gen_fix_value
and then loads the value.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks okay to re-add IMPORT opcode although make it simpler than it was before. It will take frame_start value you find here and the value it needs to import.
@@ -463,7 +463,7 @@ memory_start(_jit_compile_t *state) | |||
|
|||
/* Store the bounds of the available space */ | |||
state->gen.mem_start = _jit_memory_get_break(state->gen.context); | |||
state->gen.mem_limit = _jit_memory_get_limit(state->gen.context); | |||
state->gen.mem_limit = _jit_memory_get_limit(state->gen.context); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please avoid such white-space only changes especially for large patches.
jit/jit-rules.c
Outdated
if(is_nested) | ||
{ | ||
need_outgoing_word(&passing); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please clarify why all this stuff is re-ordered here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm this looks like a mistake, as I didn't change the order in _jit_create_entry_insns
. The ABI says the struct return pointer is the first argument, so the argument order should probably be fn(return_ptr, frame_ptr)
, but this needs to be changed in _jit_create_entry_insns
too.
I'll check it out tomorrow.
Alright, i re-added the As usual the interpreter requires special care as args and values are in different frames. btw, this is the code i'm using for testing: https://gist.github.com/M4GNV5/629e5ca55dd449c9bcb4d5bb00de317a |
e6032ce
to
c392f54
Compare
… import opcode, which is transformed into an add_relative in jit-compile.c
…ame pointer arguments
the current implementations of incoming_frame_posn and outgoing_frame_posn tend to break addressable variables, as they simply change a values frame_offset. For incoming_frame_posn this is fine, as it is only used at the start of a function. This commit changes the implementation of outgoing_frame_posn to store the value at the target frame offset.
Alright, I finally had time to tackle down the Problem. The problem is with values which It now works on x64, x86 and the interpreter. So as far as I can see, this can now be merged. |
In upstream libjit nesting is mostly unimplemented. There are the import related opcodes but no backend except x86 (32-bit Intel) implements them.
The commits in this pull request implement nesting, mostly in the front end. It removes all import related opcodes and adds one new one to retrieve a functions frame pointer. The frame pointer is then either manually or automatically passed when calling child functions and using it and their
frame_offset
values are imported.When using
jit_insn_call
on a nested function the frame pointer is passed automatically. In many languages there is a concept of closures (some call it delegate), essentially they are functions with a context pointer, very often they are nested functions plus a pointer to their parents frame (which of course are only valid as long as their parent function has not returned yet).Previousely one was not able to create such a data type using libjit as there was no way of calling a nested function using it's function pointer. This is now possible using
jit_insn_get_frame_pointer
orjit_insn_get_parent_frame_pointer_of
to retrieve the frame pointer of the current function or a ancestor respectively and then call the nested function usingjit_insn_call_nested_indirect
. If a child function has a custom way of retrieving it's parent frame pointer (e.g. through a local variable) it can do so and sets it usingjit_function_set_parent_frame
before importing values.The following pseudo code is a small exmaple which shows what was previousely not possible:
I've tested the code of this pull request on x86 (cross compiled), x86_64 and the interpreter.