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 # Register used as scratch register
80 # Stack alignment restrictions
81 @STACK_ALIGNMENT_BITS = @WORDSIZE_BITS
82 @STACK_ALIGNMENT = 1 << @STACK_ALIGNMENT_BITS
83 # Registers used for argument passing
84 @ARG_REGS = [:rdi, :rsi, :rdx, :rcx, :r8, :r9]
85 @NREG_ARGS = @ARG_REGS.length
86 # Registers used to store locals
87 @LOCAL_REGS = [:r12, :r13, :r14, :r15]
88 @NLOCAL_REGS = @LOCAL_REGS.length
89 @LOCAL_REGISTERS_SET = Set.new @LOCAL_REGS
102 @SAVED_FRAME_LAYOUT = {
103 :rbx => 0, :r12 => 1, :r13 => 2, :r14 => 3, :r15 => 4,
104 :rsp => 5, :rbp => 6,
106 @SAVE_FRAME_REGISTERS = @SAVED_FRAME_LAYOUT.keys
109 :'bits-per-word' => '64',
110 :'byte-order' => 'little-endian',
111 :'bytes-per-word' => '8'
112 @saved_registers = []
113 @function_end_label = nil
120 # Define a machine word with the given value.
129 # Emits function preamble and declare +formals+ as function arguments.
130 def begin_function formals, nlocals
131 environment = Environment.new @environment
132 @saved_registers = []
133 @environment = environment
134 emit "push #{@BP}\nmov #{@BP}, #{@SP}\n"
135 formals.each_with_index do |arg,i|
136 @environment.add_arg arg, arg_offset(i)
137 comment "# #{arg} is at #{offset_reference(@environment[arg])}"
138 emit "push #{@ARG_REGS[i]}\n" if i < @NREG_ARGS
140 emit "sub #{@SP}, #{nlocals * @WORDSIZE}\n"
141 number_of_register_locals(nlocals).times do |i|
142 register = @LOCAL_REGS[i]
143 ref = offset_reference saved_local_offset(i)
144 emit "mov #{ref}, #{register}\n"
145 @saved_registers << register
147 @function_end_label = gensym
152 # First couple of arguments go in registers
153 register_args = args[0..(@NREG_ARGS - 1)] || []
154 # Rest of arguments go on the stack
155 stack_args = args[@NREG_ARGS..-1] || []
156 # Push stack arguments
157 stack_args.reverse.each { |arg| push_qword arg }
158 # Load register arguments
159 register_args.each_with_index do |arg,i|
160 register = @ARG_REGS[i]
161 value_ref = load_value arg, register
162 if value_ref != register
163 emit "mov #{register}, #{value_ref}\n"
167 value_ref = load_value func, @SCRATCH_REG
168 emit "xor rax, rax\n"
169 # If value_ref is a symbol, use PLT-relative addressing
171 emit "call #{value_ref} wrt ..plt\n"
173 emit "call #{value_ref}\n"
176 unless stack_args.empty?
177 emit "add rsp, #{stack_args.length * @WORDSIZE}\n"
181 # Ends a function body.
183 label @function_end_label
184 # Restore saved registers
185 @saved_registers.each_with_index do |register,i|
186 ref = offset_reference saved_local_offset(i)
187 emit "mov #{register}, #{ref}\n"
193 if @environment == @top_level
194 raise "Cannot end function when not in a function"
196 @environment = @top_level
200 # Returns from a function.
202 eval_expr words, @AX unless words.empty?
203 goto @function_end_label
206 # Calls a function, re-using the current call frame if possible.
207 def tail_call func, *args
208 # Compute required number of stack words
209 nstackargs = number_of_stack_arguments args.length
210 # If we need more stack arguments than we have now,
211 # perform a normal call and return
212 if nstackargs > number_of_stack_arguments(@environment.args)
213 emit "; Not enough space for proper tail call; using regular call\n"
214 ret :call, func, *args
217 # If any arguments are going to be overwritten before they are
218 # used, save them to new local variables and use those instead.
219 (args.length - 1).downto(@NREG_ARGS) do |i|
221 next unless symbol?(arg)
222 old_arg_offset = @environment[arg]
223 next if old_arg_offset == nil || old_arg_offset < 0
224 # arg is an argument that was passed on the stack.
225 new_arg_offset = arg_offset i
226 next unless old_arg_offset > new_arg_offset
227 # arg will be overwritten before it is used.
228 # Save it in a newly created temporary variable,
229 # then use that instead.
230 newsym = @environment.gensym
235 # Same for the function we will be calling.
237 offset = @environment[func]
238 if offset != nil && offset > 0
239 newsym = @environment.gensym
245 # Set stack arguments
246 if args.length > @NREG_ARGS
247 (args.length - 1).downto(@NREG_ARGS).each do |i|
250 value_ref = load_value arg, @SCRATCH_REG
251 newarg_ref = load_arg i
252 # Elide code if source is same as destination
253 unless value_ref == newarg_ref
254 emit "mov #{@SCRATCH_REG}, #{value_ref}\n"
255 emit "mov #{newarg_ref}, #{@SCRATCH_REG}\n"
260 # Set register arguments
261 number_of_register_arguments(args.length).times do |i|
262 register = @ARG_REGS[i]
263 load_value_into_register args[i], register
267 func_ref = load_value func, @BX
270 # If func_ref is a symbol, use PLT-relative addressing
272 emit "jmp #{func_ref} wrt ..plt\n"
274 emit "jmp #{func_ref}\n"
283 # Loads the value of the nth argument.
284 def load_arg n, reg = @SCRATCH_REG
285 if register_argument?(n)
286 # Arguments that were originally passed in a register
288 "[rbp - #{(n + 1) * @WORDSIZE}]"
290 # Arguments that were originally passed on the stack
291 # are now above rbp, starting 2 words above it
292 "[rbp + #{(n + 2 - @NREG_ARGS) * @WORDSIZE}]"
300 # Returns the offset from rbp at which the nth argument is stored.
305 (n - @NREG_ARGS) * @WORDSIZE + 2 * @WORDSIZE
309 # If the nth local is stored in a register, returns that register.
310 # Otherwise, returns the offset from the frame pointer.
311 def local_offset_or_register n
315 (n + number_of_register_arguments + 1) * -@WORDSIZE
319 # Loads a value and push it on the stack.
321 value_ref = load_value value, @SCRATCH_REG
322 emit "push qword #{value_ref}\n"
325 # Calculates the number of register arguments,
326 # given the total number of arguments.
327 def number_of_register_arguments n = @environment.args
328 n < @NREG_ARGS ? n : @NREG_ARGS
331 # Calculates the number of locals that are stored in registers.
332 def number_of_register_locals n = @environment.locals
333 n < @NLOCAL_REGS ? n : @NLOCAL_REGS
336 # Calculates the number of stack arguments,
337 # given the total number of arguments.
338 def number_of_stack_arguments n = @environment.args
343 # Calculates the number of locals that are stored on the stack.
344 def number_of_stack_locals n = @environment.locals
349 # Tests if the nth argument is a register argument.
350 def register_argument? n
351 n < number_of_register_arguments
354 # Returns the offset of the nth saved local.
355 def saved_local_offset n
356 (number_of_register_arguments + n + 1) * -@WORDSIZE
362 Voodoo::CodeGenerator.register_generator AMD64NasmGenerator,
363 :architecture => :amd64,