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.
-
#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_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_function ⇒ Object
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.
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 |