Voodoo::AMD64NasmGenerator

AMD64 NASM Code Generator

Code generator that emits NASM assembly code for AMD64 processors.

Calling Convention

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:

  1. rdi

  2. rsi

  3. rdx

  4. rcx

  5. r8

  6. 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.

Call Frames

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

Callee-Save Registers

rbp, rbx, and r12 through r15 are callee-save registers.

All other registers are caller-save.

Public Class Methods

new(params = {}) click to toggle source
# 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

Public Instance Methods

arg_offset(n) click to toggle source

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
begin_function(formals, nlocals) click to toggle source

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
call(func, *args) click to toggle source

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
end_function() click to toggle source

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
load_arg(n) click to toggle source

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
load_symbol_from_got(symbol, reg) click to toggle source

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
local_offset_or_register(n) click to toggle source

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
number_of_register_arguments(n = @environment.args) click to toggle source

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
number_of_register_locals(n = @environment.locals) click to toggle source

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
number_of_stack_arguments(n = @environment.args) click to toggle source

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
number_of_stack_locals(n = @environment.locals) click to toggle source

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
push_qword(value) click to toggle source

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
register_argument?(n) click to toggle source

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
restore_saved_registers() click to toggle source

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
ret(*words) click to toggle source

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
saved_local_offset(n) click to toggle source

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
tail_call(func, *args) click to toggle source

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
word(value) click to toggle source

Define a machine word with the given value.

# File voodoo/generators/amd64_nasm_generator.rb, line 118
def word value
  qword value
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.