Code generator that emits NASM assembly code for AMD64 processors.
The calling convention implemented by this code generator is compatible with the System V ABI for AMD64, provided that all arguments are integers or pointers.
Arguments are passed in registers. The registers are used in the following order:
rdi
rsi
rdx
rcx
r8
r9
Additional arguments are pushed on the stack, starting with the last argument and working backwards. These arguments are removed from the stack by the caller, after the called function returns.
The return value is passed in rax.
For varargs functions, rax must be set to an upper bound on the number of vector arguments. Since the code generator does not know whether the called function is a varargs function, this is always done. Since the code generator never passes any vector arguments, this means rax is set to 0 before each call.
arg_n : arg_7 arg_6 saved_rip saved_rbp <-- rbp arg_0 arg_1 : arg_5 saved_r12 : saved_r15 local_4 : local_n <-- rsp
rbp, rbx, and r12 through r15 are callee-save registers.
All other registers are caller-save.
# File voodoo/generators/amd64_nasm_generator.rb, line 64 def initialize params = {} # Number of bytes in a word @WORDSIZE_BITS = 3 @WORDSIZE = 1 << @WORDSIZE_BITS # Word name in NASM lingo @WORD_NAME = 'qword' # Default alignment for code @CODE_ALIGNMENT = 0 # Default alignment for data @DATA_ALIGNMENT = @WORDSIZE # Default alignment for functions @FUNCTION_ALIGNMENT = 16 # Register used for return values @RETURN_REG = :rax # Stack alignment restrictions @STACK_ALIGNMENT_BITS = @WORDSIZE_BITS @STACK_ALIGNMENT = 1 << @STACK_ALIGNMENT_BITS @TEMPORARIES = [:r11] # Registers used for argument passing @ARG_REGS = [:rdi, :rsi, :rdx, :rcx, :r8, :r9] @NREG_ARGS = @ARG_REGS.length # Registers used to store locals @LOCAL_REGISTERS = [:r12, :r13, :r14, :r15] @NLOCAL_REGISTERS = @LOCAL_REGISTERS.length @LOCAL_REGISTERS_SET = Set.new @LOCAL_REGISTERS # Accumulator index @AX = :rax # Base index @BX = :rbx # Count index @CX = :rcx # Data index @DX = :rdx # Base pointer @BP = :rbp # Stack pointer @SP = :rsp @SAVE_FRAME_REGISTERS = [:rbx, :r12, :r13, :r14, :r15, :rsp, :rbp] @SAVED_FRAME_LAYOUT = {} @SAVE_FRAME_REGISTERS.each_with_index { |r,i| @SAVED_FRAME_LAYOUT[r] = i } super params @features.merge! :'bits-per-word' => '64', :'byte-order' => 'little-endian', :'bytes-per-word' => '8' @saved_registers = [] @function_end_label = nil end
Returns the offset from rbp at which the nth argument is stored.
# File voodoo/generators/amd64_nasm_generator.rb, line 314 def arg_offset n if n < @NREG_ARGS (n + 1) * -@WORDSIZE else (n - @NREG_ARGS) * @WORDSIZE + 2 * @WORDSIZE end end
Emits function preamble and declare formals as function arguments.
# File voodoo/generators/amd64_nasm_generator.rb, line 127 def begin_function formals, nlocals environment = Environment.new @environment @saved_registers = [] @environment = environment emit "push #{@BP}\nmov #{@BP}, #{@SP}\n" formals.each_with_index do |arg,i| @environment.add_arg arg, arg_offset(i) comment "# #{arg} is at #{offset_reference(@environment[arg])}" emit "push #{@ARG_REGS[i]}\n" if i < @NREG_ARGS end emit "sub #{@SP}, #{nlocals * @WORDSIZE}\n" number_of_register_locals(nlocals).times do |i| register = @LOCAL_REGISTERS[i] ref = offset_reference saved_local_offset(i) emit "mov #{ref}, #{register}\n" @saved_registers << register end @function_end_label = gensym end
Calls a function.
# File voodoo/generators/amd64_nasm_generator.rb, line 148 def call func, *args # First couple of arguments go in registers register_args = args[0..(@NREG_ARGS - 1)] || [] # Rest of arguments go on the stack stack_args = args[@NREG_ARGS..-1] || [] # Push stack arguments stack_args.reverse.each { |arg| push_qword arg } # Load register arguments register_args.each_with_index do |arg,i| register = @ARG_REGS[i] value_ref = load_value arg, register if value_ref != register emit "mov #{register}, #{value_ref}\n" end end # Call function with_temporary do |temporary| # If func is a global, use PLT-relative addressing if global?(func) @symbol_tracker.use func emit "xor rax, rax\n" emit "call #{func} wrt ..plt\n" else value_ref = load_value func, temporary emit "xor rax, rax\n" emit "call #{value_ref}\n" end end # Clean up stack unless stack_args.empty? emit "add rsp, #{stack_args.length * @WORDSIZE}\n" end end
Ends a function body.
# File voodoo/generators/amd64_nasm_generator.rb, line 183 def end_function label @function_end_label restore_saved_registers emit "leave\n" emit "ret\n" if @environment == @top_level raise "Cannot end function when not in a function" else @environment = @top_level end end
Loads the value of the nth argument.
# File voodoo/generators/amd64_nasm_generator.rb, line 292 def load_arg n if register_argument?(n) # Arguments that were originally passed in a register # are now below rbp "[rbp - #{(n + 1) * @WORDSIZE}]" else # Arguments that were originally passed on the stack # are now above rbp, starting 2 words above it "[rbp + #{(n + 2 - @NREG_ARGS) * @WORDSIZE}]" end end
Loads a symbol from the global offset table.
# File voodoo/generators/amd64_nasm_generator.rb, line 305 def load_symbol_from_got symbol, reg "[rel #{symbol} wrt ..gotpc]" end
If the nth local is stored in a register, returns that register. Otherwise, returns the offset from the frame pointer.
# File voodoo/generators/amd64_nasm_generator.rb, line 324 def local_offset_or_register n if n < @NLOCAL_REGISTERS @LOCAL_REGISTERS[n] else (n + number_of_register_arguments + 1) * -@WORDSIZE end end
Calculates the number of register arguments, given the total number of arguments.
# File voodoo/generators/amd64_nasm_generator.rb, line 342 def number_of_register_arguments n = @environment.args n < @NREG_ARGS ? n : @NREG_ARGS end
Calculates the number of locals that are stored in registers.
# File voodoo/generators/amd64_nasm_generator.rb, line 347 def number_of_register_locals n = @environment.locals n < @NLOCAL_REGISTERS ? n : @NLOCAL_REGISTERS end
Calculates the number of stack arguments, given the total number of arguments.
# File voodoo/generators/amd64_nasm_generator.rb, line 353 def number_of_stack_arguments n = @environment.args x = n - @NREG_ARGS x < 0 ? 0 : x end
Calculates the number of locals that are stored on the stack.
# File voodoo/generators/amd64_nasm_generator.rb, line 359 def number_of_stack_locals n = @environment.locals x = n - @NLOCAL_REGISTERS x < 0 ? 0 : x end
Loads a value and push it on the stack.
# File voodoo/generators/amd64_nasm_generator.rb, line 333 def push_qword value with_temporary do |temporary| value_ref = load_value value, temporary emit "push qword #{value_ref}\n" end end
Tests if the nth argument is a register argument.
# File voodoo/generators/amd64_nasm_generator.rb, line 365 def register_argument? n n < number_of_register_arguments end
Restores saved registers.
# File voodoo/generators/amd64_nasm_generator.rb, line 196 def restore_saved_registers @saved_registers.each_with_index do |register,i| ref = offset_reference saved_local_offset(i) emit "mov #{register}, #{ref}\n" end end
Returns from a function.
# File voodoo/generators/amd64_nasm_generator.rb, line 204 def ret *words eval_expr words, @AX unless words.empty? goto @function_end_label end
Returns the offset of the nth saved local.
# File voodoo/generators/amd64_nasm_generator.rb, line 370 def saved_local_offset n (number_of_register_arguments + n + 1) * -@WORDSIZE end
Calls a function, re-using the current call frame if possible.
# File voodoo/generators/amd64_nasm_generator.rb, line 210 def tail_call func, *args # Compute required number of stack words nstackargs = number_of_stack_arguments args.length # If we need more stack arguments than we have now, # perform a normal call and return if nstackargs > number_of_stack_arguments(@environment.args) emit "; Not enough space for proper tail call; using regular call\n" ret :call, func, *args else # If any arguments are going to be overwritten before they are # used, save them to new local variables and use those instead. (args.length - 1).downto(@NREG_ARGS) do |i| arg = args[i] next unless symbol?(arg) old_arg_offset = @environment[arg] next if old_arg_offset == nil || old_arg_offset < 0 # arg is an argument that was passed on the stack. new_arg_offset = arg_offset i next unless old_arg_offset > new_arg_offset # arg will be overwritten before it is used. # Save it in a newly created temporary variable, # then use that instead. newsym = @environment.gensym let newsym, arg args[i] = newsym end # Same for the function we will be calling. if symbol?(func) offset = @environment[func] if offset != nil && offset > 0 newsym = @environment.gensym let newsym, func func = newsym end end # Set stack arguments if args.length > @NREG_ARGS (args.length - 1).downto(@NREG_ARGS).each do |i| arg = args[i] with_temporary do |temporary| value_ref = load_value arg, temporary newarg_ref = load_arg i # Elide code if source is same as destination unless value_ref == newarg_ref emit "mov #{temporary}, #{value_ref}\n" emit "mov #{newarg_ref}, #{temporary}\n" end end end end # Set register arguments number_of_register_arguments(args.length).times do |i| register = @ARG_REGS[i] load_value_into_register args[i], register end # Tail call with_temporary do |temporary| # If func is a global, use PLT-relative addressing if global?(func) func_ref = "#{func} wrt ..plt\n" else func_ref = load_value func, temporary end restore_saved_registers emit "leave\n" set_register @AX, 0 emit "jmp #{func_ref}\n" end end end
Generated with the Darkfish Rdoc Generator 2.