CommonCodeGenerator
The NASM code generator is a common base class for generators that output assembly code for use with the Netwide Assembler.
This class is used by both the I386NasmGenerator and the AMD64NasmGenerator, and contains the functionality that is common to both.
To use the functionality from this class, a subclass must define the following methods and constants:
begin_function
@CODE_ALIGNMENT
@DATA_ALIGNMENT
@FUNCTION_ALIGNMENT
@STACK_ALIGNMENT
@STACK_ALIGNMENT_BITS
@TEMPORARIES
@WORD_NAME
@WORDSIZE
@WORDSIZE_BITS
@AX, @BX, @CX, @DX, @BP, and @SP
Translates a Voodoo action name to an x86 mnemonic
# File voodoo/generators/nasm_generator.rb, line 725 def action_to_mnemonic action case action when :asr :sar when :bsr :shr else action end end
Allocates n bytes on the stack and stores a pointer to the allocated memory in the specified register. The number of bytes is rounded up to the nearest multiple of @STACK_ALIGNMENT.
# File voodoo/generators/nasm_generator.rb, line 187 def auto_bytes n, register = @RETURN_REG if n.kind_of? Integer auto_bytes_immediate n, register else load_value_into_register n, register auto_bytes_register register, register end end
Implements auto_bytes where the number of bytes to allocate is given as an immediate value.
# File voodoo/generators/nasm_generator.rb, line 198 def auto_bytes_immediate nbytes, register nbytes = ((nbytes + @STACK_ALIGNMENT - 1) >> @STACK_ALIGNMENT_BITS) << @STACK_ALIGNMENT_BITS emit "sub #{@SP}, #{nbytes}\n" emit "mov #{register}, #{@SP}\n" if register != @SP end
Implements auto_bytes where the number of bytes is supplied in a register.
# File voodoo/generators/nasm_generator.rb, line 207 def auto_bytes_register nbytes, register = @RETURN_REG emit "add #{nbytes}, #{@STACK_ALIGNMENT - 1}\n" emit "shr #{nbytes}, #{@STACK_ALIGNMENT_BITS}\n" emit "shl #{nbytes}, #{@STACK_ALIGNMENT_BITS}\n" emit "sub #{@SP}, #{nbytes}\n" emit "mov #{register}, #{@SP}\n" if register != @SP end
Allocates n words on the stack and stores a pointer to the allocated memory in the specified register.
# File voodoo/generators/nasm_generator.rb, line 217 def auto_words n, register = @RETURN_REG if n.kind_of? Integer auto_bytes_immediate n * @WORDSIZE, register else load_value_into_register n, register if @STACK_ALIGNMENT_BITS > @WORDSIZE_BITS emit "add #{register}, " + "#{(1 << @STACK_ALIGNMENT_BITS >> @WORDSIZE_BITS) - 1}\n" emit "shr #{register}, #{@STACK_ALIGNMENT_BITS - @WORDSIZE_BITS}\n" emit "shl #{register}, #{STACK_ALIGNMENT_BITS}\n" else emit "shl #{register}, #{@WORDSIZE_BITS}\n" end emit "sub #{@SP}, #{register}\n" emit "mov #{register}, #{@SP}\n" if register != @SP end end
Begins a new block.
# File voodoo/generators/nasm_generator.rb, line 160 def begin_block *code # If entering a block at top level, # Save @BP, then set @BP to @SP if @environment == @top_level emit "push #{@BP}\n" emit "mov #{@BP}, #{@SP}\n" end environment = Environment.new @environment @environment = environment end
Define a byte with the given value
# File voodoo/generators/nasm_generator.rb, line 78 def byte value emit "db #{value}\n" end
Start a conditional using the specified branch instruction after the comparison.
# File voodoo/generators/nasm_generator.rb, line 631 def common_if branch, x, y = nil # Inverses of branches. E.g. # cmp x, y # jle somewhere # is the same as # cmp y, x # jgt somewhere inverse_branch = { :je => :je, :jge => :jl, :jg => :jle, :jl => :jge, :jle => :jg, :jne => :jne, } y_ref = load_value y, @DX x_ref = load_value x, @AX if immediate_operand?(x_ref) # Can't have an immediate value as the first operand. if immediate_operand?(y_ref) # Both are immediates. Put the first in a register. emit "mov #{@AX}, #{x_ref}\n" x_ref = @AX else # y isn't immediate; swap x and y. x_ref, y_ref = [y_ref, x_ref] branch = inverse_branch[branch] end elsif memory_operand?(x_ref) && memory_operand?(y_ref) # Can't have two memory operands. Move the first into a register. emit "mov #{@AX}, #{x_ref}\n" x_ref = @AX end truelabel = @environment.gensym falselabel = @environment.gensym @if_labels.push falselabel emit "cmp #{@WORD_NAME} #{x_ref}, #{y_ref}\n" emit "#{branch} #{truelabel}\n" emit "jmp #{falselabel}\n" emit "#{truelabel}:\n" end
Divide x by y and store the quotient in target
# File voodoo/generators/nasm_generator.rb, line 451 def div target, x, y eval_div x, y with_temporary do |temporary| target_ref = load_value target, temporary emit "mov #{target_ref}, #{@AX}\n" end end
Divide target by x and store the quotient in target
# File voodoo/generators/nasm_generator.rb, line 460 def div2 target, x div target, target, x end
Define a dword with the given value
# File voodoo/generators/nasm_generator.rb, line 83 def dword value emit "dd #{value}\n" end
# File voodoo/generators/nasm_generator.rb, line 123 def emit_align alignment emit "align #{alignment}\n" end
Export symbols from the current section
# File voodoo/generators/nasm_generator.rb, line 38 def emit_export *symbols case real_section_name(section) when ".text" symbols.each { |sym| emit "global #{sym}:function\n" } else symbols.each { |sym| emit "global #{sym}:data #{sym}.end-#{sym}\n" } end end
Emits function epilogue.
# File voodoo/generators/nasm_generator.rb, line 132 def emit_function_epilogue formals = [] emit "leave\n" end
Emits code to declare symbols as imported from an external object.
# File voodoo/generators/nasm_generator.rb, line 56 def emit_import *symbols emit "extern #{symbols.join ', '}\n" end
Emits a label.
# File voodoo/generators/nasm_generator.rb, line 61 def emit_label name emit "#{name}:\n" end
# File voodoo/generators/nasm_generator.rb, line 65 def emit_label_size name emit ".end:\n" end
# File voodoo/generators/nasm_generator.rb, line 69 def emit_label_type name, type # nothing to do end
Loads a word into a register.
# File voodoo/generators/nasm_generator.rb, line 275 def emit_load_word register, base, offset = 0 if offset == 0 emit "mov #{register}, [#{base}]\n" else emit "mov #{register}, [#{base} + #{offset} * #{@WORDSIZE}]\n" end end
Stores the value of a register in memory.
# File voodoo/generators/nasm_generator.rb, line 397 def emit_store_word register, base, offset = 0 if offset == 0 emit "mov [#{base}], #{register}\n" else emit "mov [#{base} + #{offset} * #{@WORDSIZE}], #{register}\n" end end
Ends the current block.
# File voodoo/generators/nasm_generator.rb, line 172 def end_block # Restore old value of @environment @environment = @environment.parent # If returning to top level, restore old @BP emit "leave\n" if @environment == @top_level end
Ends a function body
# File voodoo/generators/nasm_generator.rb, line 137 def end_function if @environment == @top_level raise "Cannot end function when not in a function" else @environment = @top_level end end
End a conditional body
# File voodoo/generators/nasm_generator.rb, line 251 def end_if label = @if_labels.pop emit "#{label}:\n" end
Perform division. The quotient is stored in @AX, the remainder in @DX.
# File voodoo/generators/nasm_generator.rb, line 498 def eval_div x, y with_temporary do |temporary| x_ref = load_value_into_register x, @AX y_ref = load_value y, temporary emit "mov #{@DX}, #{@AX}\n" emit "sar #{@DX}, #{@WORDSIZE * 8 - 1}\n" if immediate_operand?(y_ref) set_register temporary, y_ref emit "idiv #{temporary}\n" else emit "idiv #{@WORD_NAME} #{y_ref}\n" end end end
Evaluate an expression. The result is stored in register (@RETURN_REG by default). The following registers may be clobbered: @AX, @CX, @DX
# File voodoo/generators/nasm_generator.rb, line 516 def eval_expr words, register = @RETURN_REG if words.length == 1 if words[0] == 0 emit "xor #{register}, #{register}\n" else load_value_into_register words[0], register end else op = words[0] case op when :asr, :bsr, :rol, :ror, :shl, :shr if integer? words[2] y_ref = words[2] else load_value_into_register words[2], @CX y_ref = :cl end load_value_into_register words[1], register emit "#{action_to_mnemonic op} #{register}, #{y_ref}\n" when :'auto-bytes' auto_bytes words[1], register when :'auto-words' auto_words words[1], register when :call call *words[1..-1] emit "mov #{register}, #{@RETURN_REG}\n" if register != @RETURN_REG when :div eval_div words[1], words[2] set_register register, @AX when :'get-byte' # Get address reference address_ref = load_address words[1], words[2], 1 # Load byte from address case register when :eax, :rax set_register register, 0 set_register :al, address_ref when :ebx, :rbx set_register register, 0 set_register :bl, address_ref when :ecx, :rcx set_register register, 0 set_register :cl, address_ref when :edx, :rdx set_register register, 0 set_register :dl, address_ref else set_register @AX, 0 set_register :al, address_ref set_register register, @AX end when :'get-word' address_ref = load_address words[1], words[2], @WORDSIZE set_register register, address_ref when :mod eval_div words[1], words[2] set_register register, @DX when :mul eval_mul words[1], words[2], register when :not load_value_into_register words[1], register emit "not #{register}\n" else if binop?(op) with_temporary do |t1| x_ref = load_value words[1], @DX y_ref = load_value words[2], t1 if register == y_ref emit "mov #{@DX}, #{x_ref}\n" emit "#{op} #{@DX}, #{y_ref}\n" emit "mov #{register}, #{@DX}\n" else emit "mov #{register}, #{x_ref}\n" unless register == x_ref emit "#{op} #{register}, #{y_ref}\n" end end else raise "Not a magic word: #{words[0]}" end end end end
Multiply x by y. The result is stored in @AX by default, but a different register can be specified by passing a third argument.
# File voodoo/generators/nasm_generator.rb, line 603 def eval_mul x, y, register = @AX with_temporary do |t1| x_ref = load_value x, @DX y_ref = load_value y, t1 if immediate_operand? x_ref if immediate_operand? y_ref set_register register, x_ref * y_ref else emit "imul #{register}, #{y_ref}, #{x_ref}\n" end elsif immediate_operand? y_ref emit "imul #{register}, #{x_ref}, #{y_ref}\n" elsif y_ref != register emit "mov #{register}, #{x_ref}\n" unless x_ref == register emit "imul #{register}, #{y_ref}\n" else emit "imul #{register}, #{x_ref}\n" end end end
Continue execution at the given address
# File voodoo/generators/nasm_generator.rb, line 48 def goto value with_temporary do |temporary| value_ref = load_value value, temporary emit "jmp #{value_ref}\n" end end
Start the false path of a conditional.
# File voodoo/generators/nasm_generator.rb, line 682 def ifelse newlabel = @environment.gensym emit "jmp #{newlabel}\n" label = @if_labels.pop emit "#{label}:\n" @if_labels.push newlabel end
Test if x is equal to y
# File voodoo/generators/nasm_generator.rb, line 691 def ifeq x, y common_if :je, x, y end
Test if x is greater than or equal to y
# File voodoo/generators/nasm_generator.rb, line 696 def ifge x, y common_if :jge, x, y end
Test if x is strictly greater than y
# File voodoo/generators/nasm_generator.rb, line 701 def ifgt x, y common_if :jg, x, y end
Test if x is less than or equal to y
# File voodoo/generators/nasm_generator.rb, line 706 def ifle x, y common_if :jle, x, y end
Test if x is strictly less than y
# File voodoo/generators/nasm_generator.rb, line 711 def iflt x, y common_if :jl, x, y end
Test if x different from y
# File voodoo/generators/nasm_generator.rb, line 716 def ifne x, y common_if :jne, x, y end
Tests if an operand is an immediate operand
# File voodoo/generators/nasm_generator.rb, line 261 def immediate_operand? operand integer?(operand) end
Introduces a new local variable.
# File voodoo/generators/nasm_generator.rb, line 240 def let symbol, *words loc = local_offset_or_register @environment.locals @environment.add_local symbol, loc set symbol, *words end
Create a value reference to an address. Invoking this code may use a temporary and/or clobber @CX
# File voodoo/generators/nasm_generator.rb, line 285 def load_address base, offset, scale with_temporary do |temporary| base_ref = load_value base, temporary offset_ref = load_value offset, @CX if offset_ref == 0 if integer? base_ref # Only an integer base "[#{base_ref}]" else # Some complex base; load in temporary emit "mov #{temporary}, #{base_ref}\n" "[#{temporary}]" end elsif base_ref == 0 if integer? offset_ref # Only a scaled offset "[#{offset_ref.to_i * scale}]" else # Some complex offset; load in @CX emit "mov #{@CX}, #{offset_ref}\n" "[#{@CX} * #{scale}]" end elsif integer? base_ref if integer? offset_ref # All integers, combine them together "[#{base_ref.to_i + (offset_ref.to_i * scale)}]" else # Complex offset; use @CX emit "mov #{@CX}, #{offset_ref}\n" "[#{base_ref} + #{@CX} * #{scale}]" end elsif integer? offset_ref # Complex base, integer offset; use temporary emit "mov #{temporary}, #{base_ref}\n" "[#{temporary} + #{offset_ref.to_i * scale}]" else # Both base and offset are complex # Use both temporary and @CX emit "mov #{temporary}, #{base_ref}\n" emit "mov #{@CX}, #{offset_ref}\n" "[#{temporary} + #{@CX} * #{scale}]" end end end
Load the value at the given address.
# File voodoo/generators/nasm_generator.rb, line 332 def load_at address, reg if integer?(address) "[#{address}]" else load_value_into_register address, reg "[#{reg}]" end end
Loads the value associated with the given symbol.
# File voodoo/generators/nasm_generator.rb, line 342 def load_symbol symbol, reg x = @environment[symbol] if x.kind_of? Symbol x elsif x.kind_of? Integer offset_reference x else # Assume global @symbol_tracker.use symbol if @relocated_symbols.include? symbol load_symbol_from_got symbol, reg else symbol end end end
Loads a value. Returns a string that can be used to refer to the loaded value.
# File voodoo/generators/nasm_generator.rb, line 361 def load_value value, reg if substitution? value value = substitute_number value[1] end if integer? value if @WORDSIZE > 4 && (value < -2147483648 || value > 2147483647) # AMD64 can load immediate values that are outside the range # that can be represented as a 32-bit signed integer, but # only with a mov instruction that loads the value into a # register. emit "mov #{@WORD_NAME} #{reg}, #{value}\n" reg else value end elsif symbol? value load_symbol value, reg elsif at_expr? value load_at value[1], reg else raise "Don't know how to load #{value.inspect}" end end
Loads a value into a register.
# File voodoo/generators/nasm_generator.rb, line 387 def load_value_into_register value, register value_ref = load_value value, register set_register register, value_ref end
Tests if an operand is a memory operand
# File voodoo/generators/nasm_generator.rb, line 266 def memory_operand? operand operand.kind_of?(String) && operand[0] == [[ end
Divide x by y and store the remainder in target
# File voodoo/generators/nasm_generator.rb, line 465 def mod target, x, y eval_div x, y with_temporary do |temporary| target_ref = load_value target, temporary emit "mov #{target_ref}, #{@DX}\n" end end
Divide target by x and store the remainder in target
# File voodoo/generators/nasm_generator.rb, line 474 def mod2 target, x mod target, target, x end
Multiply x by y and store the result in target
# File voodoo/generators/nasm_generator.rb, line 479 def mul target, x, y eval_mul x, y with_temporary do |temporary| target_ref = load_value target, temporary emit "mov #{target_ref}, #{@RETURN_REG}\n" end end
Multiply target by x and store the result in target
# File voodoo/generators/nasm_generator.rb, line 488 def mul2 target, x mul target, target, x end
Returns a memory reference for the address at the given offset from the frame pointer.
# File voodoo/generators/nasm_generator.rb, line 743 def offset_reference offset if offset > 0 "[#{@BP} + #{offset}]" elsif offset < 0 "[#{@BP} - #{-offset}]" else "[#{@BP}]" end end
Define a qword with the given value
# File voodoo/generators/nasm_generator.rb, line 88 def qword value emit "dq #{value}\n" end
Returns from a function.
words may contain an expression to be evaluated. The result of the evaluation is returned from the function.
# File voodoo/generators/nasm_generator.rb, line 149 def ret *words eval_expr(words) unless words.empty? emit_function_epilogue emit "ret\n" end
Evaluate the expr in words and store the result in target
# File voodoo/generators/nasm_generator.rb, line 406 def set target, *words if integer? target raise "Cannot change value of integer #{target}" elsif global?(target) raise "Cannot change value of global #{target}" end if words.length != 1 || words[0] != target if symbol?(target) && symbol?(@environment[target]) eval_expr words, @environment[target] else eval_expr words, @RETURN_REG with_temporary do |temporary| target_ref = load_value target, temporary emit "mov #{target_ref}, #{@RETURN_REG}\n" end end end end
Set the byte at base + offset to value
# File voodoo/generators/nasm_generator.rb, line 427 def set_byte base, offset, value if immediate_operand?(value) value_ref = value else load_value_into_register value, @RETURN_REG value_ref = :al end addr_ref = load_address base, offset, 1 emit "mov byte #{addr_ref}, #{value_ref}\n" end
Set a register to a value. The value must be a valid operand to the mov instruction.
# File voodoo/generators/nasm_generator.rb, line 755 def set_register register, value_ref case value_ref when register # Nothing to do when 0 emit "xor #{register}, #{register}\n" else emit "mov #{register}, #{value_ref}\n" end end
Set the word at base + offset * +@WORDSIZE+ to value
# File voodoo/generators/nasm_generator.rb, line 439 def set_word base, offset, value if immediate_operand?(value) value_ref = load_value value, @RETURN_REG else load_value_into_register value, @RETURN_REG value_ref = @RETURN_REG end addr_ref = load_address base, offset, @WORDSIZE emit "mov #{@WORD_NAME} #{addr_ref}, #{value_ref}\n" end
Define a string with the given value
# File voodoo/generators/nasm_generator.rb, line 93 def string value code = '' in_quote = false value.each_byte do |b| if b >= 32 && b < 127 && b != 39 if in_quote code << b.chr else code << ',' unless code.empty? code << "'" + b.chr in_quote = true end else if in_quote code << "',#{b}" in_quote = false else code << ',' unless code.empty? code << "#{b}" end end end code << "'" if in_quote emit "db #{code}\n" end
Generated with the Darkfish Rdoc Generator 2.
Emit a comment