Removed common NasmGenerator code from I386NasmGenerator.
[voodoo-lang.git] / lib / ruby / voodoo / generators / amd64_nasm_generator.rb
blob6bfdc44016a1cf1a097c9ad3daebbeb50c494278
1 require 'voodoo/generators/nasm_generator'
3 module Voodoo
4   # Code generator that emits NASM assembly code for AMD64 processors.
5   # 
6   # = Calling Convention
7   #
8   # The calling convention implemented by this code generator is
9   # compatible with the System V ABI for AMD64, provided that all
10   # arguments are integers or pointers.
11   #
12   # Arguments are passed in registers. The registers are used in the
13   # following order:
14   #
15   # 1. +rdi+
16   # 2. +rsi+
17   # 3. +rdx+
18   # 4. +rcx+
19   # 5. +r8+
20   # 6. +r9+
21   #
22   # Additional arguments are pushed on the stack, starting with the last
23   # argument and working backwards.
24   #
25   # The return value is passed in +rax+.
26   #
27   # For varargs functions, +rax+ must be set to an upper bound on the
28   # number of vector arguments. Since the code generator does not know
29   # whether the called function is a varargs function, this is always
30   # done. Since the code generator never passes any vector arguments,
31   # this means +rax+ is set to +0+ before each call. 
32   #
33   # = Call Frames
34   #
35   #   arg_n
36   #   :
37   #   arg_7
38   #   arg_6
39   #   saved_rip
40   #   saved_rbp <-- rbp
41   #   arg_0
42   #   arg_1
43   #   :
44   #   arg_5
45   #   local_0
46   #   local_1
47   #   :
48   #   local_n   <-- rsp
49   #
50   class AMD64NasmGenerator < NasmGenerator
51     def initialize params
52       # Number of bytes in a word
53       @WORDSIZE = 8
54       # Word name in NASM lingo
55       @WORD_NAME = 'qword'
56       # Default alignment for code
57       @CODE_ALIGNMENT = 0
58       # Default alignment for data
59       @DATA_ALIGNMENT = @WORDSIZE
60       # Default alignment for functions
61       @FUNCTION_ALIGNMENT = 16
62       # Register used for return values
63       @RETURN_REG = 'rax'
64       # Register used as scratch register
65       @SCRATCH_REG = 'r11'
66       # Registers used for argument passing
67       @ARG_REGS = ['rdi', 'rsi', 'rdx', 'rcx', 'r8', 'r9']
68       # Accumulator index
69       @AX = 'rax'
70       # Base index
71       @BX = 'rbx'
72       # Count index
73       @CX = 'rcx'
74       # Data index
75       @DX = 'rdx'
76       super params
77     end
79     #
80     # == Data Definition
81     #
83     # Define a machine word with the given value.
84     def word value
85       qword value
86     end
88     #
89     # == Functions
90     #
92     # Call a function.
93     def call func, *args
94       emit "; call #{func} #{args.join ' '}\n"
95       # First couple of arguments go in registers
96       register_args = args[0..number_of_register_arguments] || []
97       # Rest of arguments go on the stack
98       stack_args = args[number_of_register_arguments..-1] || []
99       emit "; register_args: #{register_args.inspect}\n"
100       emit "; stack_args: #{stack_args.inspect}\n"
101       # Push stack arguments
102       stack_args.reverse.each { |arg| push_qword arg }
103       # Load register arguments
104       register_args.each_with_index do |arg,i|
105         register = @ARG_REGS[i]
106         value_ref = load_value arg, register
107         if value_ref != register
108           emit "mov #{register}, #{value_ref}\n"
109         end
110       end
111       # Call function
112       value_ref = load_value func, @SCRATCH_REG
113       emit "xor rax, rax\n"
114       emit "call #{value_ref}\n"
115       # Clean up stack
116       unless stack_args.empty?
117         emit "add rsp, #{stack_args.length * @WORDSIZE}\n"
118       end
119     end
121     # Emit function prologue.
122     def emit_function_prologue formals = []
123       emit "push rbp\nmov rbp, rsp\n"
124       unless formals.empty?
125         register_args = formals[0...number_of_register_arguments]
126         register_args.each_with_index do |arg,i|
127           emit "push #{@ARG_REGS[i]}\n"
128         end
129       end
130     end
132     # Call a function, re-using the current call frame if possible.
133     def tail_call func, *args
134       emit "; tail-call #{func} #{args.join ' '}\n"
135       # Compute required number of stack words
136       nstackargs = number_of_stack_arguments args.length
137       # If we need more stack arguments than we have now,
138       # perform a normal call and return
139       if nstackargs > number_of_stack_arguments(@environment.args)
140         emit "; Not enough space for proper tail call; using regular call\n"
141         ret :call, func, *args
142       end
144       # If any arguments are going to be overwritten before they are
145       # used, save them to new local variables and use those instead.
146       i = args.length - 1
147       while i >= -1
148         arg = (i >= 0) ? args[i] : func
150         if symbol?(arg)
151           x = @environment[arg]
152           if x && x[0] == :arg && x[1] < args.length && x[1] > i &&
153               (i >= 0 || func != args[x[1]])
154             # Save value
155             newsym = @environment.gensym
156             let newsym, arg
157             # Change reference
158             if i >= 0
159               args[i] = newsym
160             else
161               func = newsym
162             end
163           end
164         end
165         i = i - 1
166       end
168       # Set stack arguments
169       if args.length > number_of_register_arguments
170         (args.length - 1 .. number_of_register_arguments).each do |i|
171           arg = args[i]
172           
173           value_ref = load_value arg, @SCRATCH_REG
174           newarg_ref = load_arg i
175           # Elide code if source is same as destination
176           unless value_ref == newarg_ref
177             emit "mov #{@WORD_NAME} #{newarg_ref}, #{value_ref}\n"
178           end
179         end
180       end
182       # Set register arguments
183       if args.length > 0
184         number_of_register_arguments(args.length).times do |i|
185           register = @ARG_REGS[i]
186           load_value_into_register args[i], register
187         end
188       end
190       # Tail call
191       func_ref = load_value func, @BX
192       emit "leave\n"
193       set_register @AX, 0
194       emit "jmp #{func_ref}\n"
195     end
197     #
198     # == Loading Values
199     #
201     # Load the value of the nth argument
202     def load_arg n, reg = @SCRATCH_REG
203       if register_argument?(n)
204         # Arguments that were originally passed in a register
205         # are now below rbp
206         "[rbp - #{(n + 1) * @WORDSIZE}]"
207       else
208         # Arguments that were originally passed on the stack
209         # are now above rbp
210         "[rbp + #{(n + 1 - number_of_register_arguments) * @WORDSIZE}]"
211       end
212     end
214     # Load the value of the nth local variable
215     def load_local n, reg = @SCRATCH_REG
216       # If there current function has any arguments,
217       # local variables are offset by
218       # number_of_register_arguments(number_of_arguments)
219       # words.
220       offset = number_of_register_arguments(@environment.args) * @WORDSIZE
221       "[rbp - #{offset + (n + 1) * @WORDSIZE}]"
222     end
224     #
225     # == Variables
226     #
228     # Introduce a new local variable
229     def let symbol, *words
230       emit "; let #{symbol} #{words.join ' '}\n"
231       @environment.add_local symbol
232       eval_expr words, @RETURN_REG
233       emit "push #{@RETURN_REG}\n"
234     end
236     #
237     # == Miscellaneous
238     #
240     # Load a value and push it on the stack.
241     def push_qword value
242       value_ref = load_value value, @SCRATCH_REG
243       emit "push qword #{value_ref}\n"
244     end
246     # Calculate the number of register arguments,
247     # given the total number of arguments.
248     # If _n_ is +nil+, returns the maximum number of
249     # register arguments.
250     def number_of_register_arguments n = nil
251       if n.nil?
252         @ARG_REGS.length
253       else
254         [@ARG_REGS.length, n].min
255       end
256     end
258     # Calculate the number of stack arguments,
259     # given the total number of arguments.
260     def number_of_stack_arguments n
261       [0, n - number_of_register_arguments].max
262     end
264     # Tests if the nth argument is a register argument.
265     def register_argument? n
266       n < number_of_register_arguments
267     end
269   end
271   # Register class
272   Voodoo::CodeGenerator.register_generator AMD64NasmGenerator,
273                                            :architecture => :amd64,
274                                            :format => :nasm