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
local_0
local_1
:
local_n <-- rsp
Direct Known Subclasses
Instance Method Summary collapse
-
#call(func, *args) ⇒ Object
Call a function.
-
#emit_function_prologue(formals = []) ⇒ Object
Emit function prologue.
-
#initialize(params = {}) ⇒ AMD64NasmGenerator
constructor
A new instance of AMD64NasmGenerator.
-
#let(symbol, *words) ⇒ Object
Introduce a new local variable.
-
#load_arg(n, reg = @SCRATCH_REG) ⇒ Object
Load the value of the nth argument.
-
#load_local(n, reg = @SCRATCH_REG) ⇒ Object
Load the value of the nth local variable.
-
#number_of_register_arguments(n = nil) ⇒ Object
Calculate the number of register arguments, given the total number of arguments.
-
#number_of_stack_arguments(n) ⇒ Object
Calculate the number of stack arguments, given the total number of arguments.
-
#push_qword(value) ⇒ Object
Load a value and push it on the stack.
-
#register_argument?(n) ⇒ Boolean
Tests if the nth argument is a register argument.
-
#tail_call(func, *args) ⇒ Object
Call 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, #align, #at_expr?, #begin_block, #begin_function, #binop, #binop2, #binop?, #byte, #comment, #common_if, #div, #div2, #dword, #emit_function_epilogue, #end_block, #end_function, #end_if, #eval_div, #eval_expr, #eval_mul, #export, #global?, #goto, #ifelse, #ifeq, #ifge, #ifgt, #ifle, #iflt, #ifne, #immediate_operand?, #import, #integer?, #label, #load_address, #load_at, #load_symbol, #load_value, #load_value_into_register, #memory_operand?, #mod, #mod2, #mul, #mul2, #qword, #ret, #set, #set_byte, #set_register, #set_word, #string, #symbol?, #symmetric_operation?, #wordsize, #write
Methods inherited from CommonCodeGenerator
#add, #add_function, #block, #each_statement, #emit, #function, #gensym, #in_section, #output_file_name, #output_file_suffix, #real_section_name, #section, #section=, #section_alias
Methods included from GeneratorApi1
#add_code, #add_code_label, #add_data, #add_data_label, #add_function_label, #align_code, #align_data, #align_function
Constructor Details
#initialize(params = {}) ⇒ AMD64NasmGenerator
Returns a new instance of AMD64NasmGenerator.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 54 def initialize params = {} # Number of bytes in a word @WORDSIZE = 8 # 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' # Register used as scratch register @SCRATCH_REG = 'r11' # Registers used for argument passing @ARG_REGS = ['rdi', 'rsi', 'rdx', 'rcx', 'r8', 'r9'] # Accumulator index @AX = 'rax' # Base index @BX = 'rbx' # Count index @CX = 'rcx' # Data index @DX = 'rdx' # Base pointer @BP = 'rbp' # Stack pointer @SP = 'rsp' super params end |
Instance Method Details
#call(func, *args) ⇒ Object
Call a function.
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 100 def call func, *args emit "; call #{func} #{args.join ' '}\n" # First couple of arguments go in registers register_args = args[0..(number_of_register_arguments - 1)] || [] # Rest of arguments go on the stack stack_args = args[number_of_register_arguments..-1] || [] emit "; register_args: #{register_args.inspect}\n" emit "; stack_args: #{stack_args.inspect}\n" # 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 value_ref = load_value func, @SCRATCH_REG 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 # Clean up stack unless stack_args.empty? emit "add rsp, #{stack_args.length * @WORDSIZE}\n" end end |
#emit_function_prologue(formals = []) ⇒ Object
Emit function prologue.
134 135 136 137 138 139 140 141 142 |
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 134 def emit_function_prologue formals = [] emit "push rbp\nmov rbp, rsp\n" unless formals.empty? register_args = formals[0...number_of_register_arguments] register_args.each_with_index do |arg,i| emit "push #{@ARG_REGS[i]}\n" end end end |
#let(symbol, *words) ⇒ Object
Introduce a new local variable
245 246 247 248 249 250 |
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 245 def let symbol, *words emit "; let #{symbol} #{words.join ' '}\n" @environment.add_local symbol eval_expr words, @RETURN_REG emit "push #{@RETURN_REG}\n" end |
#load_arg(n, reg = @SCRATCH_REG) ⇒ Object
Load the value of the nth argument
218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 218 def load_arg n, reg = @SCRATCH_REG 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 - number_of_register_arguments) * @WORDSIZE}]" end end |
#load_local(n, reg = @SCRATCH_REG) ⇒ Object
Load the value of the nth local variable
231 232 233 234 235 236 237 238 |
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 231 def load_local n, reg = @SCRATCH_REG # If there current function has any arguments, # local variables are offset by # number_of_register_arguments(number_of_arguments) # words. offset = number_of_register_arguments(@environment.args) * @WORDSIZE "[rbp - #{offset + (n + 1) * @WORDSIZE}]" end |
#number_of_register_arguments(n = nil) ⇒ Object
Calculate the number of register arguments, given the total number of arguments. If n is nil
, returns the maximum number of register arguments.
266 267 268 269 270 271 272 |
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 266 def number_of_register_arguments n = nil if n.nil? @ARG_REGS.length else [@ARG_REGS.length, n].min end end |
#number_of_stack_arguments(n) ⇒ Object
Calculate the number of stack arguments, given the total number of arguments.
276 277 278 |
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 276 def number_of_stack_arguments n [0, n - number_of_register_arguments].max end |
#push_qword(value) ⇒ Object
Load a value and push it on the stack.
257 258 259 260 |
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 257 def push_qword value value_ref = load_value value, @SCRATCH_REG emit "push qword #{value_ref}\n" end |
#register_argument?(n) ⇒ Boolean
Tests if the nth argument is a register argument.
281 282 283 |
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 281 def register_argument? n n < number_of_register_arguments end |
#tail_call(func, *args) ⇒ Object
Call a function, re-using the current call frame if possible.
145 146 147 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 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 145 def tail_call func, *args emit "; tail-call #{func} #{args.join ' '}\n" # 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 end # If any arguments are going to be overwritten before they are # used, save them to new local variables and use those instead. i = args.length - 1 while i >= -1 arg = (i >= 0) ? args[i] : func if symbol?(arg) x = @environment[arg] if x && x[0] == :arg && x[1] < args.length && x[1] > i && (i >= 0 || func != args[x[1]]) # Save value newsym = @environment.gensym let newsym, arg # Change reference if i >= 0 args[i] = newsym else func = newsym end end end i = i - 1 end # Set stack arguments if args.length > number_of_register_arguments (args.length - 1).downto(number_of_register_arguments).each do |i| arg = args[i] value_ref = load_value arg, @SCRATCH_REG newarg_ref = load_arg i # Elide code if source is same as destination unless value_ref == newarg_ref emit "mov #{@SCRATCH_REG}, #{value_ref}\n" emit "mov #{newarg_ref}, #{@SCRATCH_REG}\n" 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 |
#word(value) ⇒ Object
Define a machine word with the given value.
91 92 93 |
# File 'lib/voodoo/generators/amd64_nasm_generator.rb', line 91 def word value qword value end |