1 require 'voodoo/generators/nasm_generator'
4 # = AMD64 NASM Code Generator
6 # Code generator that emits NASM assembly code for AMD64 processors.
8 # == Calling Convention
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.
14 # Arguments are passed in registers. The registers are used in the
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.
28 # The return value is passed in +rax+.
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.
53 class AMD64NasmGenerator < NasmGenerator
54 def initialize params = {}
55 # Number of bytes in a word
57 @WORDSIZE = 1 << @WORDSIZE_BITS
58 # Word name in NASM lingo
60 # Default alignment for code
62 # Default alignment for data
63 @DATA_ALIGNMENT = @WORDSIZE
64 # Default alignment for functions
65 @FUNCTION_ALIGNMENT = 16
66 # Register used for return values
68 # Register used as scratch register
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
90 :'bits-per-word' => '64',
91 :'byte-order' => 'little-endian',
92 :'bytes-per-word' => '8'
99 # Define a machine word with the given value.
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
118 emit "sub rsp, #{nlocals * @WORDSIZE}\n"
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"
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
142 emit "call #{value_ref} wrt ..plt\n"
144 emit "call #{value_ref}\n"
147 unless stack_args.empty?
148 emit "add rsp, #{stack_args.length * @WORDSIZE}\n"
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
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|
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
181 # Same for the function we will be calling.
183 offset = @environment[func]
184 if offset != nil && offset > 0
185 newsym = @environment.gensym
191 # Set stack arguments
192 if args.length > number_of_register_arguments
193 (args.length - 1).downto(number_of_register_arguments).each do |i|
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"
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
213 func_ref = load_value func, @BX
216 # If func_ref is a symbol, use PLT-relative addressing
218 emit "jmp #{func_ref} wrt ..plt\n"
220 emit "jmp #{func_ref}\n"
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
234 "[rbp - #{(n + 1) * @WORDSIZE}]"
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}]"
246 # Returns the offset from rbp at which the nth argument is stored.
251 (n - @NREG_ARGS) * @WORDSIZE + 2 * @WORDSIZE
255 # Returns the offset from rbp at which the nth local is stored.
257 nregister_args = [@NREG_ARGS, @environment.args].min
258 (n + nregister_args + 1) * -@WORDSIZE
261 # Load a value and push it on the stack.
263 value_ref = load_value value, @SCRATCH_REG
264 emit "push qword #{value_ref}\n"
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
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
285 # Tests if the nth argument is a register argument.
286 def register_argument? n
287 n < number_of_register_arguments
293 Voodoo::CodeGenerator.register_generator AMD64NasmGenerator,
294 :architecture => :amd64,