Refactored AMD64 code generator
[voodoo-lang.git] / lib / voodoo / generators / amd64_nasm_generator.rb
blob1a06487b6202112fdaa65e7dd249e4686251be6e
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_BITS = 3
57       @WORDSIZE = 1 << @WORDSIZE_BITS
58       # Word name in NASM lingo
59       @WORD_NAME = 'qword'
60       # Default alignment for code
61       @CODE_ALIGNMENT = 0
62       # Default alignment for data
63       @DATA_ALIGNMENT = @WORDSIZE
64       # Default alignment for functions
65       @FUNCTION_ALIGNMENT = 16
66       # Register used for return values
67       @RETURN_REG = 'rax'
68       # Register used as scratch register
69       @SCRATCH_REG = 'r11'
70       # Stack alignment restrictions
71       @STACK_ALIGNMENT_BITS = @WORDSIZE_BITS
72       @STACK_ALIGNMENT = 1 << @STACK_ALIGNMENT_BITS
73       # Registers used for argument passing
74       @ARG_REGS = ['rdi', 'rsi', 'rdx', 'rcx', 'r8', 'r9']
75       @NREG_ARGS = @ARG_REGS.length
76       # Accumulator index
77       @AX = 'rax'
78       # Base index
79       @BX = 'rbx'
80       # Count index
81       @CX = 'rcx'
82       # Data index
83       @DX = 'rdx'
84       # Base pointer
85       @BP = 'rbp'
86       # Stack pointer
87       @SP = 'rsp'
88       super params
89       @features.merge! \
90         :'bits-per-word' => '64',
91         :'byte-order' => 'little-endian',
92         :'bytes-per-word' => '8'
93     end
95     #
96     # == Data Definition
97     #
99     # Define a machine word with the given value.
100     def word value
101       qword value
102     end
104     #
105     # == Functions
106     #
108     # Emits function preamble and declare +formals+ as function arguments.
109     def begin_function formals, nlocals
110       environment = Environment.new @environment
111       @environment = environment
112       emit "push rbp\nmov rbp, rsp\n"
113       formals.each_with_index do |arg,i|
114         @environment.add_arg arg, arg_offset(i)
115         comment "# #{arg} is at #{offset_reference(@environment[arg])}"
116         emit "push #{@ARG_REGS[i]}\n" if i < @NREG_ARGS
117       end
118       emit "sub rsp, #{nlocals * @WORDSIZE}\n"
119     end
121     # Calls a function.
122     def call func, *args
123       # First couple of arguments go in registers
124       register_args = args[0..(number_of_register_arguments - 1)] || []
125       # Rest of arguments go on the stack
126       stack_args = args[number_of_register_arguments..-1] || []
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       # Compute required number of stack words
155       nstackargs = number_of_stack_arguments args.length
156       # If we need more stack arguments than we have now,
157       # perform a normal call and return
158       if nstackargs > number_of_stack_arguments(@environment.args)
159         emit "; Not enough space for proper tail call; using regular call\n"
160         ret :call, func, *args
161       else
163         # If any arguments are going to be overwritten before they are
164         # used, save them to new local variables and use those instead.
165         (args.length - 1).downto(@NREG_ARGS) do |i|
166           arg = args[i]
167           next unless symbol?(arg)
168           old_arg_offset = @environment[arg]
169           next if old_arg_offset == nil || old_arg_offset < 0
170           # arg is an argument that was passed on the stack.
171           new_arg_offset = arg_offset i
172           next unless old_arg_offset > new_arg_offset
173           # arg will be overwritten before it is used.
174           # Save it in a newly created temporary variable,
175           # then use that instead.
176           newsym = @environment.gensym
177           let newsym, arg
178           args[i] = newsym
179         end
181         # Same for the function we will be calling.
182         if symbol?(func)
183           offset = @environment[func]
184           if offset != nil && offset > 0
185             newsym = @environment.gensym
186             let newsym, func
187             func = newsym
188           end
189         end
191         # Set stack arguments
192         if args.length > number_of_register_arguments
193           (args.length - 1).downto(number_of_register_arguments).each do |i|
194             arg = args[i]
195             
196             value_ref = load_value arg, @SCRATCH_REG
197             newarg_ref = load_arg i
198             # Elide code if source is same as destination
199             unless value_ref == newarg_ref
200               emit "mov #{@SCRATCH_REG}, #{value_ref}\n"
201               emit "mov #{newarg_ref}, #{@SCRATCH_REG}\n"
202             end
203           end
204         end
206         # Set register arguments
207         number_of_register_arguments(args.length).times do |i|
208           register = @ARG_REGS[i]
209           load_value_into_register args[i], register
210         end
212         # Tail call
213         func_ref = load_value func, @BX
214         emit "leave\n"
215         set_register @AX, 0
216         # If func_ref is a symbol, use PLT-relative addressing
217         if global?(func)
218           emit "jmp #{func_ref} wrt ..plt\n"
219         else
220           emit "jmp #{func_ref}\n"
221         end
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     # == Miscellaneous
244     #
246     # Returns the offset from rbp at which the nth argument is stored.
247     def arg_offset n
248       if n < @NREG_ARGS
249         (n + 1) * -@WORDSIZE
250       else
251         (n - @NREG_ARGS) * @WORDSIZE + 2 * @WORDSIZE
252       end
253     end
255     # Returns the offset from rbp at which the nth local is stored.
256     def local_offset n
257       nregister_args = [@NREG_ARGS, @environment.args].min
258       (n + nregister_args + 1) * -@WORDSIZE
259     end
261     # Load a value and push it on the stack.
262     def push_qword value
263       value_ref = load_value value, @SCRATCH_REG
264       emit "push qword #{value_ref}\n"
265     end
267     # Calculate the number of register arguments,
268     # given the total number of arguments.
269     # If _n_ is +nil+, returns the maximum number of
270     # register arguments.
271     def number_of_register_arguments n = nil
272       if n.nil?
273         @NREG_ARGS
274       else
275         [@NREG_ARGS, n].min
276       end
277     end
279     # Calculate the number of stack arguments,
280     # given the total number of arguments.
281     def number_of_stack_arguments n
282       [0, n - number_of_register_arguments].max
283     end
285     # Tests if the nth argument is a register argument.
286     def register_argument? n
287       n < number_of_register_arguments
288     end
290   end
292   # Register class
293   Voodoo::CodeGenerator.register_generator AMD64NasmGenerator,
294                                            :architecture => :amd64,
295                                            :format => :nasm