Class: Voodoo::AMD64NasmGenerator

Inherits:
NasmGenerator show all
Defined in:
lib/voodoo/generators/amd64_nasm_generator.rb

Overview

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.

Direct Known Subclasses

AMD64ELFGenerator

Instance Method Summary collapse

Methods inherited from NasmGenerator

#action_to_mnemonic, #auto_bytes, #auto_bytes_immediate, #auto_bytes_register, #auto_words, #begin_block, #byte, #comment, #common_if, #div, #div2, #dword, #emit_align, #emit_function_epilogue, #emit_label, #emit_label_size, #emit_label_type, #emit_load_word, #emit_store_word, #end_block, #end_if, #eval_div, #eval_expr, #eval_mul, #export, #goto, #ifelse, #ifeq, #ifge, #ifgt, #ifle, #iflt, #ifne, #immediate_operand?, #import, #let, #load_address, #load_at, #load_symbol, #load_value, #load_value_into_register, #memory_operand?, #mod, #mod2, #mul, #mul2, #offset_reference, #qword, #set, #set_byte, #set_register, #set_word, #string, #write

Methods inherited from CommonCodeGenerator

#add, #add_function, #align, #assymetric_binop?, #at_expr?, #binop?, #block, #count_locals, #default_alignment, #each_statement, #emit, #emit_label, #emit_voodoo, #export, #features, #function, #gensym, #global?, #has_feature?, #import, #in_section, #integer?, #label, #local_register, #output_file_name, #output_file_suffix, #real_section_name, #register?, #register_arg?, #registers_for_locals, #restore_frame, #restore_locals, #restore_registers_from_frame, #save_frame, #save_frame_and_locals, #save_locals, #save_registers_to_frame, #saved_frame_size, #section, #section=, #section_alias, #stack_align, #substitute_number, #substitution?, #symbol?, #symmetric_binop?, #with_temporaries, #with_temporary, #write

Constructor Details

#initialize(params = {}) ⇒ AMD64NasmGenerator

Returns a new instance of AMD64NasmGenerator.



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/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

Instance Method Details

#arg_offset(n) ⇒ Object

Returns the offset from rbp at which the nth argument is stored.



307
308
309
310
311
312
313
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 307

def arg_offset n
  if n < @NREG_ARGS
    (n + 1) * -@WORDSIZE
  else
    (n - @NREG_ARGS) * @WORDSIZE + 2 * @WORDSIZE
  end
end

#begin_function(formals, nlocals) ⇒ Object

Emits function preamble and declare formals as function arguments.



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/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) ⇒ Object

Calls a function.



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/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|
    value_ref = load_value func, temporary
    emit "xor rax, rax\n"
    # If value_ref is a symbol, use PLT-relative addressing
    if global?(func)
      emit "call #{value_ref} wrt ..plt\n"
    else
      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_functionObject

Ends a function body.



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 181

def end_function
  label @function_end_label
  # Restore saved registers
  @saved_registers.each_with_index do |register,i|
    ref = offset_reference saved_local_offset(i)
    emit "mov #{register}, #{ref}\n"
  end
  # Destroy frame.
  emit "leave\n"
  # Return.
  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) ⇒ Object

Loads the value of the nth argument.



285
286
287
288
289
290
291
292
293
294
295
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 285

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) ⇒ Object

Loads a symbol from the global offset table.



298
299
300
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 298

def load_symbol_from_got symbol, reg
  "[rel #{symbol} wrt ..gotpc]"
end

#local_offset_or_register(n) ⇒ Object

If the nth local is stored in a register, returns that register. Otherwise, returns the offset from the frame pointer.



317
318
319
320
321
322
323
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 317

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) ⇒ Object

Calculates the number of register arguments, given the total number of arguments.



335
336
337
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 335

def number_of_register_arguments n = @environment.args
  n < @NREG_ARGS ? n : @NREG_ARGS
end

#number_of_register_locals(n = @environment.locals) ⇒ Object

Calculates the number of locals that are stored in registers.



340
341
342
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 340

def number_of_register_locals n = @environment.locals
  n < @NLOCAL_REGISTERS ? n : @NLOCAL_REGISTERS
end

#number_of_stack_arguments(n = @environment.args) ⇒ Object

Calculates the number of stack arguments, given the total number of arguments.



346
347
348
349
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 346

def number_of_stack_arguments n = @environment.args
  x = n - @NREG_ARGS
  x < 0 ? 0 : x
end

#number_of_stack_locals(n = @environment.locals) ⇒ Object

Calculates the number of locals that are stored on the stack.



352
353
354
355
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 352

def number_of_stack_locals n = @environment.locals
  x = n - @NLOCAL_REGISTERS
  x < 0 ? 0 : x
end

#push_qword(value) ⇒ Object

Loads a value and push it on the stack.



326
327
328
329
330
331
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 326

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) ⇒ Boolean

Tests if the nth argument is a register argument.

Returns:

  • (Boolean)


358
359
360
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 358

def register_argument? n
  n < number_of_register_arguments
end

#ret(*words) ⇒ Object

Returns from a function.



200
201
202
203
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 200

def ret *words
  eval_expr words, @AX unless words.empty?
  goto @function_end_label
end

#saved_local_offset(n) ⇒ Object

Returns the offset of the nth saved local.



363
364
365
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 363

def saved_local_offset n
  (number_of_register_arguments + n + 1) * -@WORDSIZE
end

#tail_call(func, *args) ⇒ Object

Calls a function, re-using the current call frame if possible.



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 206

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
    func_ref = load_value func, @BX
    emit "leave\n"
    set_register @AX, 0
    # If func_ref is a symbol, use PLT-relative addressing
    if global?(func)
      emit "jmp #{func_ref} wrt ..plt\n"
    else
      emit "jmp #{func_ref}\n"
    end
  end
end

#word(value) ⇒ Object

Define a machine word with the given value.



118
119
120
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 118

def word value
  qword value
end