Class: Voodoo::AMD64NasmGenerator
- Inherits:
-
NasmGenerator
- Object
- CommonCodeGenerator
- NasmGenerator
- Voodoo::AMD64NasmGenerator
- 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:
-
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.
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
Instance Method Summary collapse
-
#arg_offset(n) ⇒ Object
Returns the offset from rbp at which the nth argument is stored.
-
#begin_function(formals, nlocals) ⇒ Object
Emits function preamble and declare
formals
as function arguments. -
#call(func, *args) ⇒ Object
Calls a function.
-
#end_function ⇒ Object
Ends a function body.
-
#initialize(params = {}) ⇒ AMD64NasmGenerator
constructor
A new instance of AMD64NasmGenerator.
-
#load_arg(n) ⇒ Object
Loads the value of the nth argument.
-
#load_symbol_from_got(symbol, reg) ⇒ Object
Loads a symbol from the global offset table.
-
#local_offset_or_register(n) ⇒ Object
If the nth local is stored in a register, returns that register.
-
#number_of_register_arguments(n = @environment.args) ⇒ Object
Calculates the number of register arguments, given the total number of arguments.
-
#number_of_register_locals(n = @environment.locals) ⇒ Object
Calculates the number of locals that are stored in registers.
-
#number_of_stack_arguments(n = @environment.args) ⇒ Object
Calculates the number of stack arguments, given the total number of arguments.
-
#number_of_stack_locals(n = @environment.locals) ⇒ Object
Calculates the number of locals that are stored on the stack.
-
#push_qword(value) ⇒ Object
Loads a value and push it on the stack.
-
#register_argument?(n) ⇒ Boolean
Tests if the nth argument is a register argument.
-
#restore_saved_registers ⇒ Object
Restores saved registers.
-
#ret(*words) ⇒ Object
Returns from a function.
-
#saved_local_offset(n) ⇒ Object
Returns the offset of the nth saved local.
-
#tail_call(func, *args) ⇒ Object
Calls a function, re-using the current call frame if possible.
-
#word(value) ⇒ Object
Define a machine word with the given value.
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_export, #emit_function_epilogue, #emit_import, #emit_label, #emit_label_size, #emit_label_type, #emit_load_word, #emit_store_word, #end_block, #end_if, #eval_div, #eval_expr, #eval_mul, #goto, #ifelse, #ifeq, #ifge, #ifgt, #ifle, #iflt, #ifne, #immediate_operand?, #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_import, #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?, #undefined_symbols, #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.
314 315 316 317 318 319 320 |
# File 'lib/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) ⇒ 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 179 180 |
# 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| # 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 ⇒ Object
Ends a function body.
183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/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) ⇒ Object
Loads the value of the nth argument.
292 293 294 295 296 297 298 299 300 301 302 |
# File 'lib/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) ⇒ Object
Loads a symbol from the global offset table.
305 306 307 |
# File 'lib/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) ⇒ Object
If the nth local is stored in a register, returns that register. Otherwise, returns the offset from the frame pointer.
324 325 326 327 328 329 330 |
# File 'lib/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) ⇒ Object
Calculates the number of register arguments, given the total number of arguments.
342 343 344 |
# File 'lib/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) ⇒ Object
Calculates the number of locals that are stored in registers.
347 348 349 |
# File 'lib/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) ⇒ Object
Calculates the number of stack arguments, given the total number of arguments.
353 354 355 356 |
# File 'lib/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) ⇒ Object
Calculates the number of locals that are stored on the stack.
359 360 361 362 |
# File 'lib/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) ⇒ Object
Loads a value and push it on the stack.
333 334 335 336 337 338 |
# File 'lib/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) ⇒ Boolean
Tests if the nth argument is a register argument.
365 366 367 |
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 365 def register_argument? n n < number_of_register_arguments end |
#restore_saved_registers ⇒ Object
Restores saved registers.
196 197 198 199 200 201 |
# File 'lib/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) ⇒ Object
Returns from a function.
204 205 206 207 |
# File 'lib/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) ⇒ Object
Returns the offset of the nth saved local.
370 371 372 |
# File 'lib/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) ⇒ Object
Calls a function, re-using the current call frame if possible.
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 279 280 281 282 283 284 285 |
# File 'lib/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) ⇒ 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 |