New Environment class. Tests pass on AMD64
[voodoo-lang.git] / lib / voodoo / generators / amd64_nasm_generator.rb
blobf2e91386d8013c22e3b6b59bd951db24ce11a53f
1 require 'voodoo/generators/nasm_generator'
3 module Voodoo
4   # = AMD64 NASM Code Generator
5   #
6   # Code generator that emits NASM assembly code for AMD64 processors.
7   # 
8   # == Calling Convention
9   #
10   # The calling convention implemented by this code generator is
11   # compatible with the System V ABI for AMD64, provided that all
12   # arguments are integers or pointers.
13   #
14   # Arguments are passed in registers. The registers are used in the
15   # following order:
16   #
17   # 1. +rdi+
18   # 2. +rsi+
19   # 3. +rdx+
20   # 4. +rcx+
21   # 5. +r8+
22   # 6. +r9+
23   #
24   # Additional arguments are pushed on the stack, starting with the last
25   # argument and working backwards. These arguments are removed from the
26   # stack by the caller, after the called function returns.
27   #
28   # The return value is passed in +rax+.
29   #
30   # For varargs functions, +rax+ must be set to an upper bound on the
31   # number of vector arguments. Since the code generator does not know
32   # whether the called function is a varargs function, this is always
33   # done. Since the code generator never passes any vector arguments,
34   # this means +rax+ is set to +0+ before each call. 
35   #
36   # == Call Frames
37   #
38   #   arg_n
39   #   :
40   #   arg_7
41   #   arg_6
42   #   saved_rip
43   #   saved_rbp <-- rbp
44   #   arg_0
45   #   arg_1
46   #   :
47   #   arg_5
48   #   local_0
49   #   local_1
50   #   :
51   #   local_n   <-- rsp
52   #
53   class AMD64NasmGenerator < NasmGenerator
54     def initialize params = {}
55       # Number of bytes in a word
56       @WORDSIZE = 8
57       # Word name in NASM lingo
58       @WORD_NAME = 'qword'
59       # Default alignment for code
60       @CODE_ALIGNMENT = 0
61       # Default alignment for data
62       @DATA_ALIGNMENT = @WORDSIZE
63       # Default alignment for functions
64       @FUNCTION_ALIGNMENT = 16
65       # Register used for return values
66       @RETURN_REG = 'rax'
67       # Register used as scratch register
68       @SCRATCH_REG = 'r11'
69       # Registers used for argument passing
70       @ARG_REGS = ['rdi', 'rsi', 'rdx', 'rcx', 'r8', 'r9']
71       @NREG_ARGS = @ARG_REGS.length
72       # Accumulator index
73       @AX = 'rax'
74       # Base index
75       @BX = 'rbx'
76       # Count index
77       @CX = 'rcx'
78       # Data index
79       @DX = 'rdx'
80       # Base pointer
81       @BP = 'rbp'
82       # Stack pointer
83       @SP = 'rsp'
84       super params
85       @features.merge! \
86         :'bits-per-word' => '64',
87         :'byte-order' => 'little-endian',
88         :'bytes-per-word' => '8'
89     end
91     #
92     # == Data Definition
93     #
95     # Define a machine word with the given value.
96     def word value
97       qword value
98     end
100     #
101     # == Functions
102     #
104     # Emits function preamble and declare +formals+ as function arguments.
105     def begin_function *formals
106       environment = Environment.new @environment
107       @environment = environment
108       emit "push rbp\nmov rbp, rsp\n"
109       register_args = formals[0...number_of_register_arguments]
110       register_args.each_with_index do |arg,i|
111         let_register arg, @ARG_REGS[i]
112       end
113       (@NREG_ARGS...formals.length).each do |i|
114         environment.add_arg formals[i], arg_offset(i)
115       end
116     end
118     # Calls a function.
119     def call func, *args
120       emit "; call #{func} #{args.join ' '}\n"
121       # First couple of arguments go in registers
122       register_args = args[0..(number_of_register_arguments - 1)] || []
123       # Rest of arguments go on the stack
124       stack_args = args[number_of_register_arguments..-1] || []
125       emit "; register_args: #{register_args.inspect}\n"
126       emit "; stack_args: #{stack_args.inspect}\n"
127       # Push stack arguments
128       stack_args.reverse.each { |arg| push_qword arg }
129       # Load register arguments
130       register_args.each_with_index do |arg,i|
131         register = @ARG_REGS[i]
132         value_ref = load_value arg, register
133         if value_ref != register
134           emit "mov #{register}, #{value_ref}\n"
135         end
136       end
137       # Call function
138       value_ref = load_value func, @SCRATCH_REG
139       emit "xor rax, rax\n"
140       # If value_ref is a symbol, use PLT-relative addressing
141       if global?(func)
142         emit "call #{value_ref} wrt ..plt\n"
143       else
144         emit "call #{value_ref}\n"
145       end
146       # Clean up stack
147       unless stack_args.empty?
148         emit "add rsp, #{stack_args.length * @WORDSIZE}\n"
149       end
150     end
152     # Call a function, re-using the current call frame if possible.
153     def tail_call func, *args
154       emit "; tail-call #{func} #{args.join ' '}\n"
155       # Compute required number of stack words
156       nstackargs = number_of_stack_arguments args.length
157       # If we need more stack arguments than we have now,
158       # perform a normal call and return
159       if nstackargs > number_of_stack_arguments(@environment.args)
160         emit "; Not enough space for proper tail call; using regular call\n"
161         ret :call, func, *args
162       end
164       # If any arguments are going to be overwritten before they are
165       # used, save them to new local variables and use those instead.
166       (args.length - 1).downto(@NREG_ARGS) do |i|
167         arg = args[i]
168         next unless symbol?(arg)
169         old_arg_offset = @environment[arg]
170         next if old_arg_offset == nil || old_arg_offset < 0
171         # arg is an argument that was passed on the stack.
172         new_arg_offset = arg_offset i
173         next unless old_arg_offset < new_arg_offset
174         # arg will be overwritten before it is used.
175         # Save it in a newly created temporary variable,
176         # then use that instead.
177         newsym = @environment.gensym
178         let newsym, arg
179         args[i] = newsym
180       end
182       # Same for the function we will be calling.
183       if symbol?(func)
184         offset = @environment[func]
185         if offset != nil && offset > 0
186           newsym = @environment.gensym
187           let newsym, func
188           func = newsym
189         end
190       end
192       # Set stack arguments
193       if args.length > number_of_register_arguments
194         (args.length - 1).downto(number_of_register_arguments).each do |i|
195           arg = args[i]
196           
197           value_ref = load_value arg, @SCRATCH_REG
198           newarg_ref = load_arg i
199           # Elide code if source is same as destination
200           unless value_ref == newarg_ref
201             emit "mov #{@SCRATCH_REG}, #{value_ref}\n"
202             emit "mov #{newarg_ref}, #{@SCRATCH_REG}\n"
203           end
204         end
205       end
207       # Set register arguments
208       number_of_register_arguments(args.length).times do |i|
209         register = @ARG_REGS[i]
210         load_value_into_register args[i], register
211       end
213       # Tail call
214       func_ref = load_value func, @BX
215       emit "leave\n"
216       set_register @AX, 0
217       # If func_ref is a symbol, use PLT-relative addressing
218       if global?(func)
219         emit "jmp #{func_ref} wrt ..plt\n"
220       else
221         emit "jmp #{func_ref}\n"
222       end
223     end
225     #
226     # == Loading Values
227     #
229     # Loads the value of the nth argument.
230     def load_arg n, reg = @SCRATCH_REG
231       if register_argument?(n)
232         # Arguments that were originally passed in a register
233         # are now below rbp
234         "[rbp - #{(n + 1) * @WORDSIZE}]"
235       else
236         # Arguments that were originally passed on the stack
237         # are now above rbp, starting 2 words above it
238         "[rbp + #{(n + 2 - number_of_register_arguments) * @WORDSIZE}]"
239       end
240     end
242     #
243     # == Variables
244     #
246     # Introduce a new local variable
247     def let symbol, *words
248       eval_expr words, @RETURN_REG
249       let_register symbol, @RETURN_REG
250     end
252     # Allocates space for a variable, adds it to the environment,
253     # and initializes it to the value in a register.
254     def let_register symbol, register
255       emit "push #{register}\n"
256       @environment.bytes = @environment.bytes + 8
257       offset = @environment.offset - 8
258       @environment.offset = offset
259       @environment.add_local symbol, offset
260     end
262     #
263     # == Miscellaneous
264     #
266     # Returns the offset from rbp at which the nth argument is stored.
267     def arg_offset n
268       if n < @NREG_ARGS
269         n * -8 - 8
270       else
271         (n - @NREG_ARGS) * 8 + 16
272       end
273     end
275     # Load a value and push it on the stack.
276     def push_qword value
277       value_ref = load_value value, @SCRATCH_REG
278       emit "push qword #{value_ref}\n"
279     end
281     # Calculate the number of register arguments,
282     # given the total number of arguments.
283     # If _n_ is +nil+, returns the maximum number of
284     # register arguments.
285     def number_of_register_arguments n = nil
286       if n.nil?
287         @NREG_ARGS
288       else
289         [@NREG_ARGS, n].min
290       end
291     end
293     # Calculate the number of stack arguments,
294     # given the total number of arguments.
295     def number_of_stack_arguments n
296       [0, n - number_of_register_arguments].max
297     end
299     # Tests if the nth argument is a register argument.
300     def register_argument? n
301       n < number_of_register_arguments
302     end
304   end
306   # Register class
307   Voodoo::CodeGenerator.register_generator AMD64NasmGenerator,
308                                            :architecture => :amd64,
309                                            :format => :nasm