AMD64NASMGenerator: fixed bugs uncovered by many-vars tests.
[voodoo-lang.git] / lib / voodoo / generators / amd64_nasm_generator.rb
blob0c521dc73fec6a5826684f06fe8e97ea879b7776
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       # Accumulator index
72       @AX = 'rax'
73       # Base index
74       @BX = 'rbx'
75       # Count index
76       @CX = 'rcx'
77       # Data index
78       @DX = 'rdx'
79       super params
80     end
82     #
83     # == Data Definition
84     #
86     # Define a machine word with the given value.
87     def word value
88       qword value
89     end
91     #
92     # == Functions
93     #
95     # Call a function.
96     def call func, *args
97       emit "; call #{func} #{args.join ' '}\n"
98       # First couple of arguments go in registers
99       register_args = args[0..(number_of_register_arguments - 1)] || []
100       # Rest of arguments go on the stack
101       stack_args = args[number_of_register_arguments..-1] || []
102       emit "; register_args: #{register_args.inspect}\n"
103       emit "; stack_args: #{stack_args.inspect}\n"
104       # Push stack arguments
105       stack_args.reverse.each { |arg| push_qword arg }
106       # Load register arguments
107       register_args.each_with_index do |arg,i|
108         register = @ARG_REGS[i]
109         value_ref = load_value arg, register
110         if value_ref != register
111           emit "mov #{register}, #{value_ref}\n"
112         end
113       end
114       # Call function
115       value_ref = load_value func, @SCRATCH_REG
116       emit "xor rax, rax\n"
117       # If value_ref is a symbol, use PLT-relative addressing
118       if global?(func)
119         emit "call #{value_ref} wrt ..plt\n"
120       else
121         emit "call #{value_ref}\n"
122       end
123       # Clean up stack
124       unless stack_args.empty?
125         emit "add rsp, #{stack_args.length * @WORDSIZE}\n"
126       end
127     end
129     # Emit function prologue.
130     def emit_function_prologue formals = []
131       emit "push rbp\nmov rbp, rsp\n"
132       unless formals.empty?
133         register_args = formals[0...number_of_register_arguments]
134         register_args.each_with_index do |arg,i|
135           emit "push #{@ARG_REGS[i]}\n"
136         end
137       end
138     end
140     # Call a function, re-using the current call frame if possible.
141     def tail_call func, *args
142       emit "; tail-call #{func} #{args.join ' '}\n"
143       # Compute required number of stack words
144       nstackargs = number_of_stack_arguments args.length
145       # If we need more stack arguments than we have now,
146       # perform a normal call and return
147       if nstackargs > number_of_stack_arguments(@environment.args)
148         emit "; Not enough space for proper tail call; using regular call\n"
149         ret :call, func, *args
150       end
152       # If any arguments are going to be overwritten before they are
153       # used, save them to new local variables and use those instead.
154       i = args.length - 1
155       while i >= -1
156         arg = (i >= 0) ? args[i] : func
158         if symbol?(arg)
159           x = @environment[arg]
160           if x && x[0] == :arg && x[1] < args.length && x[1] > i &&
161               (i >= 0 || func != args[x[1]])
162             # Save value
163             newsym = @environment.gensym
164             let newsym, arg
165             # Change reference
166             if i >= 0
167               args[i] = newsym
168             else
169               func = newsym
170             end
171           end
172         end
173         i = i - 1
174       end
176       # Set stack arguments
177       if args.length > number_of_register_arguments
178         (args.length - 1).downto(number_of_register_arguments).each do |i|
179           arg = args[i]
180           
181           value_ref = load_value arg, @SCRATCH_REG
182           newarg_ref = load_arg i
183           # Elide code if source is same as destination
184           unless value_ref == newarg_ref
185             emit "mov #{@SCRATCH_REG}, #{value_ref}\n"
186             emit "mov #{newarg_ref}, #{@SCRATCH_REG}\n"
187           end
188         end
189       end
191       # Set register arguments
192       number_of_register_arguments(args.length).times do |i|
193         register = @ARG_REGS[i]
194         load_value_into_register args[i], register
195       end
197       # Tail call
198       func_ref = load_value func, @BX
199       emit "leave\n"
200       set_register @AX, 0
201       # If func_ref is a symbol, use PLT-relative addressing
202       if global?(func)
203         emit "jmp #{func_ref} wrt ..plt\n"
204       else
205         emit "jmp #{func_ref}\n"
206       end
207     end
209     #
210     # == Loading Values
211     #
213     # Load the value of the nth argument
214     def load_arg n, reg = @SCRATCH_REG
215       if register_argument?(n)
216         # Arguments that were originally passed in a register
217         # are now below rbp
218         "[rbp - #{(n + 1) * @WORDSIZE}]"
219       else
220         # Arguments that were originally passed on the stack
221         # are now above rbp, starting 2 words above it
222         "[rbp + #{(n + 2 - number_of_register_arguments) * @WORDSIZE}]"
223       end
224     end
226     # Load the value of the nth local variable
227     def load_local n, reg = @SCRATCH_REG
228       # If there current function has any arguments,
229       # local variables are offset by
230       # number_of_register_arguments(number_of_arguments)
231       # words.
232       offset = number_of_register_arguments(@environment.args) * @WORDSIZE
233       "[rbp - #{offset + (n + 1) * @WORDSIZE}]"
234     end
236     #
237     # == Variables
238     #
240     # Introduce a new local variable
241     def let symbol, *words
242       emit "; let #{symbol} #{words.join ' '}\n"
243       @environment.add_local symbol
244       eval_expr words, @RETURN_REG
245       emit "push #{@RETURN_REG}\n"
246     end
248     #
249     # == Miscellaneous
250     #
252     # Load a value and push it on the stack.
253     def push_qword value
254       value_ref = load_value value, @SCRATCH_REG
255       emit "push qword #{value_ref}\n"
256     end
258     # Calculate the number of register arguments,
259     # given the total number of arguments.
260     # If _n_ is +nil+, returns the maximum number of
261     # register arguments.
262     def number_of_register_arguments n = nil
263       if n.nil?
264         @ARG_REGS.length
265       else
266         [@ARG_REGS.length, n].min
267       end
268     end
270     # Calculate the number of stack arguments,
271     # given the total number of arguments.
272     def number_of_stack_arguments n
273       [0, n - number_of_register_arguments].max
274     end
276     # Tests if the nth argument is a register argument.
277     def register_argument? n
278       n < number_of_register_arguments
279     end
281   end
283   # Register class
284   Voodoo::CodeGenerator.register_generator AMD64NasmGenerator,
285                                            :architecture => :amd64,
286                                            :format => :nasm