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
Initial implementation of JIT engine for amd64 target #60
Conversation
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
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.
well documented.. unleashing some more comments before proceeding to unblock you a bit
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
hope addressed all comments 😄 |
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.
OK DONE!
I can't say I made meaningful comments about the assembly translation, but I did look over your tests that they are there and if there was a problem it wouldn't be shotguns in the dark.
This is an epic progress, so happy for you to merge when you feel ready!
|
||
import "syscall" | ||
|
||
const mmapFlags = syscall.MAP_ANONYMOUS |
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.
comment why or rename the constant to mmapFlagAnonymous
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
so for mmap stuff, I will do it in a follow-up PR as I have to understand the system call more 😄 |
merging!!!!!!!!!!! |
Here's the tracking issue for amd64 JIT: #65 |
This commit completes the baseline (singlepass) JIT WebAssembly compiler for amd64 target with the implementation for br_table instruction. The implementation passes 100% of WebAssembly specification test, just like our interpreter. This is the world's first JIT compilation engine purely written in Go, and the implementation is stable under multple goroutines, and never broken by Go runtime. The JIT engine is tested both for Linux and Darwin. Even though this passes the spectests and is implemented as "JIT", our calling convention proxies all function calls though non-native Go world, therefore there's some performance overhead. This would be fixed in a following commit which allows us to make function calls in the fully native way. See #60 and RATIONALE.md for design details. resolves #65 #42 Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit adds the initial implementation of Just-In-Time compilation engine. The implementation is based on my experiments in https://github.com/mathetake/go-jit-exp, and aims to avoid the massive PR by introducing the minimal JIT engine like only being able to execute fibonacci func, or only subset of instructions are supported.
Notably, this commit adds
jit
package which implements the JIT engine for WebAssembly purely written in Go.Here are background, technical difficulties and some of the design choices (the same contents from
wasm/jit/README.md
)General limitations on pure Go JIT engines
In Go program, each Goroutine manages its own stack, and each item on Goroutine stack is managed by Go runtime for garbage collection, etc.
These impose some difficulties on JIT engine purely written in Go because we cannot use native push/pop instructions to save/restore temporaly variables spilling from registers. This results in making it impossible for us to invoke Go functions from JITed native codes with the native
call
instruction since it involves stack manipulations.TODO: maybe it is possible to hack the runtime to make it possible to achieve function calls with
call
.How to generate native codes
Currently we rely on
twitchyliquid64/golang-asm
to assemble native codes. The library is just a copy of Go official compiler's assembler with modified import paths. So once we reach some maturity, we could implement our own assembler to reduce the unnecessary dependency as being less dependency is one of our primary goal in this project.The assembled native codes are represented as
[]byte
and the slice region is marked as executable via mmap system call.How to enter native codes
Assuming that we have a native code as
[]byte
, it is straightforward to enter the native code region viaGo assembly code. In this package, we have the function without body called
jitcall
where we pass
codeSegment uintptr
as a first argument. This pointer is pointing to the first instruction to be executed. The pointer can be easily derived from[]byte
viaunsafe.Pointer
:And
jitcall
is actually implemented in jit_amd64.s as a convenience layer to comply with the Go's official calling convention and we delegate the task to jump into the code segment to the Go assembler code.How to achieve function calls
Given that we cannot use
call
instruction at all in native code, here's how we achieve the function calls back and forth among Go and (JITed) Wasm native functions.The general principle is that all the function calls consists of 1) emitting instruction to record the continuation program counter to
engine.continuationAddressOffset
2) emittingreturn
instruction.For example, the following Wasm code
will be compiled as
This way, the engine, which enters the native code via
jitcall
, can know the continuation address of the caller's function frame:and calling into another function in JIT engine's main loop:
After finished executing the callee code, we return back to the caller's code with the specified return address:
To summarize, every function call is achieved by returning back to Go code (
engine.exec
's main loop) with some continuation info, and enter the callee native code (or host functions) from there. That, of course, comes with a bit of overhead because each function call is implemented by two steps (returning back tojitcall
callsite AND enteringjitcall
again) vs justcall
instruction (orjmp
) in usual native codes.Note that this mechanism is a minimal PoC impl, so in the near future, we would achieve the function calls without returning back to
engine.exec
's main loop and insteadjmp
directly to the callee native code.Supported instructions
Supported instructions are
but they are enough to prove that it is actually feasible to implement the complete JIT engine purely in Go!
Example code
Here's the Fibonacci function in wat
This function will be compiled to wazeroir as follows:
Then this is compiled as the following native code: