Skip to content
This repository has been archived by the owner on Oct 5, 2019. It is now read-only.

Milestone: Compile a complete function #1

Closed
sunfishcode opened this issue Oct 30, 2018 · 7 comments
Closed

Milestone: Compile a complete function #1

sunfishcode opened this issue Oct 30, 2018 · 7 comments

Comments

@sunfishcode
Copy link
Member

Let's start by working to compile this function into machine code. The steps are:

  • Handle local variable declarations -- for now, we'll assume the locals will be allocated in a contiguous memory region, so the task here is: using the information from the local declarations at the top of function_body::translate, to write a function that maps from local indices to offsets in that region, and to compute the needed size of that region.
  • Keep track of the stack pointer -- Move the backend's Registers struct into a backend Context, add a field recording the current depth of the stack pointer, and make push_i32 and pop_i32 subtract from and add to this field so that we always know where the stack pointer is (relative to where it started).
  • Implement get_local/set_local -- In function_body.rs, use the mapping we created above to get the offset within the locals area, then pass that to backend.rs to do the load or store. In the backend, this will look like mov offset(%rsp), Rq(op) where offset is the offset within the locals area plus the current stack depth.
  • Implement function entry -- For now, we can use this simple sequence:
	push   %rbp               # save incoming frame pointer
	mov    %rbp, %rsp         # copy stack pointer to frame pointer
        sub    %rsp, framesize    # allocate the stack frame (the stack grows down)

where framesize is the size of the locals area.

  • Store the incoming function arguments into their slots in the locals area. If you're on Linux/Mac/etc., the first 4 arguments are in rdi, rsi, rdx, rcx. If you're on Windows, they're in rcx, rdx, r8, r9. Eventually we'll want to make the calling convention configurable, but it's ok to hardcode stuff to get started with.

  • Implement returns - at the function exit, pop the last remaining i32, copy it into RAX, add the size of the locals area back to the stack pointer, then do a ret.

    • Then, at the end of function_body::translate, instead of just disassembling the output, return the compiled code, make examples/test.rs transmute the address to a function pointer and... call it!
    • Write a test case to test that "add" works :-).

Once that milestone is achieved, the work can branch out a little bit. One set of tasks is implementing more integer arithmetic operations. Another is to add floating-point register support and after that, floating-point arithmetic operations. And independently of those, the next big milestone will be to compile a fibonacci function. Once we achieve this first milestone, I'll write up a new design issue for that :-).

For anyone interested in getting involved, welcome! and please post in the issue here so that we can coordinate work.

@pepyakin
Copy link
Collaborator

I will start working on that, slowly though!

Can we limit the scope to just linux/mac (systemv calling conv) for now?

@sunfishcode
Copy link
Member Author

Sounds good! And yeah, we can just support one calling convention for now. As long as we keep platform-specific things reasonably localized, we can easily add more platforms later.

@pepyakin
Copy link
Collaborator

pepyakin commented Nov 1, 2018

  1. Calculating the frame size requires us to know the signature of a function we are compiling. But function section data is just discarded. There is also other information required when we are compiling a function. Do you have an idea how we store this data?
  2. Why do we need to track SP and load/store locals relative to id? Isn't it would be easier to address relative to RBP?
  3. Why add size of locals area in epilogue? Can we mov rsp, rbp ; pop rbp?
  4. Can we assume that every local variable is of same size (8 bytes)? This would allow to make the function that maps from local indices to stack offsets to be as simple as (idx * -8) - 8 ?

@CensoredUsername
Copy link

@pepyakin as long as you specify the ABI properly when transmuting to function pointers (which you should be doing anyway), you should be able to make it work on all x64 targets with a single calling convention. You can use the system V AMD64 abi on Windows in rust without issues.

@sunfishcode
Copy link
Member Author

sunfishcode commented Nov 1, 2018

(2.) Yes, either way would work for now. I have a mild preference for using RSP here because that will oblige us to always keep track of RSP as we add more code, which will make it easier to do things like frame pointer elimination or dynamically-aligned stack frames in the future. But I'm ok keeping it simpler for now too.

(4.) Yeah, that's fine for now. It might be a good idea to start a list of these kinds of things somewhere, as they would be good discrete projects for someone looking to get involved to pick up.

@pepyakin
Copy link
Collaborator

pepyakin commented Nov 1, 2018

Oops, sorry, I editted a question meanwhile. Can you update your answer, @sunfishcode ?

@sunfishcode
Copy link
Member Author

Ok, numbering updated :-).

(3.) Yes, this is similar to (2.); if we track where RSP is, we can adjust it back, but if we have RBP, we can use that instead. Either way works.

(1.) Ah, you're right, we will need to know the function's signature for this milestone. Then the simplest way to achieve our first milestone here then would be to just assume the signature is (i32, i32) -> (i32), which is fine for now.

The step I was picturing we'd do in a later milestone is to have translate_sections collect up the types and functions into a rudimentary Module data structure. This should be straightforward to do, however the tricky part is that we eventually want lightbeam to be integrated into existing VMs which will typically already be decoding this information into their own Module data structures, so I'm picturing we'll have a trait between this Module and function_body.rs so that when we're integrated into a VM we can just query it. Does that make sense?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants