1 require 'voodoo/generators/nasm_generator'
5 # = AMD64 NASM Code Generator
7 # Code generator that emits NASM assembly code for AMD64 processors.
9 # == Calling Convention
11 # The calling convention implemented by this code generator is
12 # compatible with the System V ABI for AMD64, provided that all
13 # arguments are integers or pointers.
15 # Arguments are passed in registers. The registers are used in the
25 # Additional arguments are pushed on the stack, starting with the last
26 # argument and working backwards. These arguments are removed from the
27 # stack by the caller, after the called function returns.
29 # The return value is passed in +rax+.
31 # For varargs functions, +rax+ must be set to an upper bound on the
32 # number of vector arguments. Since the code generator does not know
33 # whether the called function is a varargs function, this is always
34 # done. Since the code generator never passes any vector arguments,
35 # this means +rax+ is set to +0+ before each call.
57 # == Callee-Save Registers
59 # +rbp+, +rbx+, and +r12+ through +r15+ are callee-save registers.
61 # All other registers are caller-save.
63 class AMD64NasmGenerator < NasmGenerator
64 def initialize params = {}
65 # Number of bytes in a word
67 @WORDSIZE = 1 << @WORDSIZE_BITS
68 # Word name in NASM lingo
70 # Default alignment for code
72 # Default alignment for data
73 @DATA_ALIGNMENT = @WORDSIZE
74 # Default alignment for functions
75 @FUNCTION_ALIGNMENT = 16
76 # Register used for return values
78 # Stack alignment restrictions
79 @STACK_ALIGNMENT_BITS = @WORDSIZE_BITS
80 @STACK_ALIGNMENT = 1 << @STACK_ALIGNMENT_BITS
82 # Registers used for argument passing
83 @ARG_REGS = [:rdi, :rsi, :rdx, :rcx, :r8, :r9]
84 @NREG_ARGS = @ARG_REGS.length
85 # Registers used to store locals
86 @LOCAL_REGISTERS = [:r12, :r13, :r14, :r15]
87 @NLOCAL_REGISTERS = @LOCAL_REGISTERS.length
88 @LOCAL_REGISTERS_SET = Set.new @LOCAL_REGISTERS
101 @SAVE_FRAME_REGISTERS = [:rbx, :r12, :r13, :r14, :r15, :rsp, :rbp]
102 @SAVED_FRAME_LAYOUT = {}
103 @SAVE_FRAME_REGISTERS.each_with_index { |r,i| @SAVED_FRAME_LAYOUT[r] = i }
106 :'bits-per-word' => '64',
107 :'byte-order' => 'little-endian',
108 :'bytes-per-word' => '8'
109 @saved_registers = []
110 @function_end_label = nil
117 # Define a machine word with the given value.
126 # Emits function preamble and declare +formals+ as function arguments.
127 def begin_function formals, nlocals
128 environment = Environment.new @environment
129 @saved_registers = []
130 @environment = environment
131 emit "push #{@BP}\nmov #{@BP}, #{@SP}\n"
132 formals.each_with_index do |arg,i|
133 @environment.add_arg arg, arg_offset(i)
134 comment "# #{arg} is at #{offset_reference(@environment[arg])}"
135 emit "push #{@ARG_REGS[i]}\n" if i < @NREG_ARGS
137 emit "sub #{@SP}, #{nlocals * @WORDSIZE}\n"
138 number_of_register_locals(nlocals).times do |i|
139 register = @LOCAL_REGISTERS[i]
140 ref = offset_reference saved_local_offset(i)
141 emit "mov #{ref}, #{register}\n"
142 @saved_registers << register
144 @function_end_label = gensym
149 # First couple of arguments go in registers
150 register_args = args[0..(@NREG_ARGS - 1)] || []
151 # Rest of arguments go on the stack
152 stack_args = args[@NREG_ARGS..-1] || []
153 # Push stack arguments
154 stack_args.reverse.each { |arg| push_qword arg }
155 # Load register arguments
156 register_args.each_with_index do |arg,i|
157 register = @ARG_REGS[i]
158 value_ref = load_value arg, register
159 if value_ref != register
160 emit "mov #{register}, #{value_ref}\n"
164 with_temporary do |temporary|
165 value_ref = load_value func, temporary
166 emit "xor rax, rax\n"
167 # If value_ref is a symbol, use PLT-relative addressing
169 emit "call #{value_ref} wrt ..plt\n"
171 emit "call #{value_ref}\n"
175 unless stack_args.empty?
176 emit "add rsp, #{stack_args.length * @WORDSIZE}\n"
180 # Ends a function body.
182 label @function_end_label
183 # Restore saved registers
184 @saved_registers.each_with_index do |register,i|
185 ref = offset_reference saved_local_offset(i)
186 emit "mov #{register}, #{ref}\n"
192 if @environment == @top_level
193 raise "Cannot end function when not in a function"
195 @environment = @top_level
199 # Returns from a function.
201 eval_expr words, @AX unless words.empty?
202 goto @function_end_label
205 # Calls a function, re-using the current call frame if possible.
206 def tail_call func, *args
207 # Compute required number of stack words
208 nstackargs = number_of_stack_arguments args.length
209 # If we need more stack arguments than we have now,
210 # perform a normal call and return
211 if nstackargs > number_of_stack_arguments(@environment.args)
212 emit "; Not enough space for proper tail call; using regular call\n"
213 ret :call, func, *args
216 # If any arguments are going to be overwritten before they are
217 # used, save them to new local variables and use those instead.
218 (args.length - 1).downto(@NREG_ARGS) do |i|
220 next unless symbol?(arg)
221 old_arg_offset = @environment[arg]
222 next if old_arg_offset == nil || old_arg_offset < 0
223 # arg is an argument that was passed on the stack.
224 new_arg_offset = arg_offset i
225 next unless old_arg_offset > new_arg_offset
226 # arg will be overwritten before it is used.
227 # Save it in a newly created temporary variable,
228 # then use that instead.
229 newsym = @environment.gensym
234 # Same for the function we will be calling.
236 offset = @environment[func]
237 if offset != nil && offset > 0
238 newsym = @environment.gensym
244 # Set stack arguments
245 if args.length > @NREG_ARGS
246 (args.length - 1).downto(@NREG_ARGS).each do |i|
249 with_temporary do |temporary|
250 value_ref = load_value arg, temporary
251 newarg_ref = load_arg i
252 # Elide code if source is same as destination
253 unless value_ref == newarg_ref
254 emit "mov #{temporary}, #{value_ref}\n"
255 emit "mov #{newarg_ref}, #{temporary}\n"
261 # Set register arguments
262 number_of_register_arguments(args.length).times do |i|
263 register = @ARG_REGS[i]
264 load_value_into_register args[i], register
268 func_ref = load_value func, @BX
271 # If func_ref is a symbol, use PLT-relative addressing
273 emit "jmp #{func_ref} wrt ..plt\n"
275 emit "jmp #{func_ref}\n"
284 # Loads the value of the nth argument.
286 if register_argument?(n)
287 # Arguments that were originally passed in a register
289 "[rbp - #{(n + 1) * @WORDSIZE}]"
291 # Arguments that were originally passed on the stack
292 # are now above rbp, starting 2 words above it
293 "[rbp + #{(n + 2 - @NREG_ARGS) * @WORDSIZE}]"
301 # Returns the offset from rbp at which the nth argument is stored.
306 (n - @NREG_ARGS) * @WORDSIZE + 2 * @WORDSIZE
310 # If the nth local is stored in a register, returns that register.
311 # Otherwise, returns the offset from the frame pointer.
312 def local_offset_or_register n
313 if n < @NLOCAL_REGISTERS
316 (n + number_of_register_arguments + 1) * -@WORDSIZE
320 # Loads a value and push it on the stack.
322 with_temporary do |temporary|
323 value_ref = load_value value, temporary
324 emit "push qword #{value_ref}\n"
328 # Calculates the number of register arguments,
329 # given the total number of arguments.
330 def number_of_register_arguments n = @environment.args
331 n < @NREG_ARGS ? n : @NREG_ARGS
334 # Calculates the number of locals that are stored in registers.
335 def number_of_register_locals n = @environment.locals
336 n < @NLOCAL_REGISTERS ? n : @NLOCAL_REGISTERS
339 # Calculates the number of stack arguments,
340 # given the total number of arguments.
341 def number_of_stack_arguments n = @environment.args
346 # Calculates the number of locals that are stored on the stack.
347 def number_of_stack_locals n = @environment.locals
348 x = n - @NLOCAL_REGISTERS
352 # Tests if the nth argument is a register argument.
353 def register_argument? n
354 n < number_of_register_arguments
357 # Returns the offset of the nth saved local.
358 def saved_local_offset n
359 (number_of_register_arguments + n + 1) * -@WORDSIZE
365 Voodoo::CodeGenerator.register_generator AMD64NasmGenerator,
366 :architecture => :amd64,