1 require 'voodoo/generators/common_code_generator'
4 # = NASM Code Generator
6 # The NASM code generator is a common base class for generators that output
7 # assembly code for use with the {Netwide Assembler}[http://www.nasm.us/].
9 # This class is used by both the I386NasmGenerator and the
10 # AMD64NasmGenerator, and contains the functionality that is common to
13 # To use the functionality from this class, a subclass must define the
14 # following methods and constants:
18 # - @FUNCTION_ALIGNMENT
20 # - @STACK_ALIGNMENT_BITS
25 # - @AX, @BX, @CX, @DX, @BP, and @SP
26 class NasmGenerator < CommonCodeGenerator
27 def initialize params = {}
30 @output_file_suffix = '.asm'
37 # Export symbols from the current section
38 def emit_export *symbols
39 case real_section_name(section)
41 symbols.each { |sym| emit "global #{sym}:function\n" }
43 symbols.each { |sym| emit "global #{sym}:data #{sym}.end-#{sym}\n" }
47 # Continue execution at the given address
49 with_temporary do |temporary|
50 value_ref = load_value value, temporary
51 emit "jmp #{value_ref}\n"
55 # Emits code to declare symbols as imported from an external object.
56 def emit_import *symbols
57 emit "extern #{symbols.join ', '}\n"
65 def emit_label_size name
69 def emit_label_type name, type
77 # Define a byte with the given value
82 # Define a dword with the given value
87 # Define a qword with the given value
92 # Define a string with the given value
96 value.each_byte do |b|
97 if b >= 32 && b < 127 && b != 39
101 code << ',' unless code.empty?
110 code << ',' unless code.empty?
115 code << "'" if in_quote
123 def emit_align alignment
124 emit "align #{alignment}\n"
131 # Emits function epilogue.
132 def emit_function_epilogue formals = []
136 # Ends a function body
138 if @environment == @top_level
139 raise "Cannot end function when not in a function"
141 @environment = @top_level
145 # Returns from a function.
147 # _words_ may contain an expression to be evaluated. The result
148 # of the evaluation is returned from the function.
150 eval_expr(words) unless words.empty?
151 emit_function_epilogue
159 # Begins a new block.
160 def begin_block *code
161 # If entering a block at top level,
162 # Save @BP, then set @BP to @SP
163 if @environment == @top_level
165 emit "mov #{@BP}, #{@SP}\n"
167 environment = Environment.new @environment
168 @environment = environment
171 # Ends the current block.
173 # Restore old value of @environment
174 @environment = @environment.parent
176 # If returning to top level, restore old @BP
177 emit "leave\n" if @environment == @top_level
181 # == Memory Allocation
184 # Allocates n bytes on the stack and stores a pointer to the allocated
185 # memory in the specified register. The number of bytes is rounded up
186 # to the nearest multiple of @STACK_ALIGNMENT.
187 def auto_bytes n, register = @RETURN_REG
188 if n.kind_of? Integer
189 auto_bytes_immediate n, register
191 load_value_into_register n, register
192 auto_bytes_register register, register
196 # Implements auto_bytes where the number of bytes to allocate is given
197 # as an immediate value.
198 def auto_bytes_immediate nbytes, register
199 nbytes = ((nbytes + @STACK_ALIGNMENT - 1) >> @STACK_ALIGNMENT_BITS) <<
200 @STACK_ALIGNMENT_BITS
201 emit "sub #{@SP}, #{nbytes}\n"
202 emit "mov #{register}, #{@SP}\n" if register != @SP
205 # Implements auto_bytes where the number of bytes is supplied in a
207 def auto_bytes_register nbytes, register = @RETURN_REG
208 emit "add #{nbytes}, #{@STACK_ALIGNMENT - 1}\n"
209 emit "shr #{nbytes}, #{@STACK_ALIGNMENT_BITS}\n"
210 emit "shl #{nbytes}, #{@STACK_ALIGNMENT_BITS}\n"
211 emit "sub #{@SP}, #{nbytes}\n"
212 emit "mov #{register}, #{@SP}\n" if register != @SP
215 # Allocates n words on the stack and stores a pointer to the allocated
216 # memory in the specified register.
217 def auto_words n, register = @RETURN_REG
218 if n.kind_of? Integer
219 auto_bytes_immediate n * @WORDSIZE, register
221 load_value_into_register n, register
222 if @STACK_ALIGNMENT_BITS > @WORDSIZE_BITS
223 emit "add #{register}, " +
224 "#{(1 << @STACK_ALIGNMENT_BITS >> @WORDSIZE_BITS) - 1}\n"
225 emit "shr #{register}, #{@STACK_ALIGNMENT_BITS - @WORDSIZE_BITS}\n"
226 emit "shl #{register}, #{STACK_ALIGNMENT_BITS}\n"
228 emit "shl #{register}, #{@WORDSIZE_BITS}\n"
230 emit "sub #{@SP}, #{register}\n"
231 emit "mov #{register}, #{@SP}\n" if register != @SP
239 # Introduces a new local variable.
240 def let symbol, *words
241 loc = local_offset_or_register @environment.locals
242 @environment.add_local symbol, loc
250 # End a conditional body
252 label = @if_labels.pop
257 # == Value Classification
260 # Tests if an operand is an immediate operand
261 def immediate_operand? operand
265 # Tests if an operand is a memory operand
266 def memory_operand? operand
267 operand.kind_of?(String) && operand[0] == ?[
274 # Loads a word into a register.
275 def emit_load_word register, base, offset = 0
277 emit "mov #{register}, [#{base}]\n"
279 emit "mov #{register}, [#{base} + #{offset} * #{@WORDSIZE}]\n"
283 # Create a value reference to an address.
284 # Invoking this code may use a temporary and/or clobber @CX
285 def load_address base, offset, scale
286 with_temporary do |temporary|
287 base_ref = load_value base, temporary
288 offset_ref = load_value offset, @CX
292 # Only an integer base
295 # Some complex base; load in temporary
296 emit "mov #{temporary}, #{base_ref}\n"
300 if integer? offset_ref
301 # Only a scaled offset
302 "[#{offset_ref.to_i * scale}]"
304 # Some complex offset; load in @CX
305 emit "mov #{@CX}, #{offset_ref}\n"
306 "[#{@CX} * #{scale}]"
308 elsif integer? base_ref
309 if integer? offset_ref
310 # All integers, combine them together
311 "[#{base_ref.to_i + (offset_ref.to_i * scale)}]"
313 # Complex offset; use @CX
314 emit "mov #{@CX}, #{offset_ref}\n"
315 "[#{base_ref} + #{@CX} * #{scale}]"
317 elsif integer? offset_ref
318 # Complex base, integer offset; use temporary
319 emit "mov #{temporary}, #{base_ref}\n"
320 "[#{temporary} + #{offset_ref.to_i * scale}]"
322 # Both base and offset are complex
323 # Use both temporary and @CX
324 emit "mov #{temporary}, #{base_ref}\n"
325 emit "mov #{@CX}, #{offset_ref}\n"
326 "[#{temporary} + #{@CX} * #{scale}]"
331 # Load the value at the given address.
332 def load_at address, reg
336 load_value_into_register address, reg
341 # Loads the value associated with the given symbol.
342 def load_symbol symbol, reg
343 x = @environment[symbol]
346 elsif x.kind_of? Integer
350 @symbol_tracker.use symbol
351 if @relocated_symbols.include? symbol
352 load_symbol_from_got symbol, reg
360 # Returns a string that can be used to refer to the loaded value.
361 def load_value value, reg
362 if substitution? value
363 value = substitute_number value[1]
367 if @WORDSIZE > 4 && (value < -2147483648 || value > 2147483647)
368 # AMD64 can load immediate values that are outside the range
369 # that can be represented as a 32-bit signed integer, but
370 # only with a mov instruction that loads the value into a
372 emit "mov #{@WORD_NAME} #{reg}, #{value}\n"
378 load_symbol value, reg
380 load_at value[1], reg
382 raise "Don't know how to load #{value.inspect}"
386 # Loads a value into a register.
387 def load_value_into_register value, register
388 value_ref = load_value value, register
389 set_register register, value_ref
396 # Stores the value of a register in memory.
397 def emit_store_word register, base, offset = 0
399 emit "mov [#{base}], #{register}\n"
401 emit "mov [#{base} + #{offset} * #{@WORDSIZE}], #{register}\n"
405 # Evaluate the expr in words and store the result in target
406 def set target, *words
408 raise "Cannot change value of integer #{target}"
409 elsif global?(target)
410 raise "Cannot change value of global #{target}"
413 if words.length != 1 || words[0] != target
414 if symbol?(target) && symbol?(@environment[target])
415 eval_expr words, @environment[target]
417 eval_expr words, @RETURN_REG
418 with_temporary do |temporary|
419 target_ref = load_value target, temporary
420 emit "mov #{target_ref}, #{@RETURN_REG}\n"
426 # Set the byte at _base_ + _offset_ to _value_
427 def set_byte base, offset, value
428 if immediate_operand?(value)
431 load_value_into_register value, @RETURN_REG
434 addr_ref = load_address base, offset, 1
435 emit "mov byte #{addr_ref}, #{value_ref}\n"
438 # Set the word at _base_ + _offset_ * +@WORDSIZE+ to _value_
439 def set_word base, offset, value
440 if immediate_operand?(value)
441 value_ref = load_value value, @RETURN_REG
443 load_value_into_register value, @RETURN_REG
444 value_ref = @RETURN_REG
446 addr_ref = load_address base, offset, @WORDSIZE
447 emit "mov #{@WORD_NAME} #{addr_ref}, #{value_ref}\n"
450 # Divide x by y and store the quotient in target
453 with_temporary do |temporary|
454 target_ref = load_value target, temporary
455 emit "mov #{target_ref}, #{@AX}\n"
459 # Divide target by x and store the quotient in target
461 div target, target, x
464 # Divide x by y and store the remainder in target
467 with_temporary do |temporary|
468 target_ref = load_value target, temporary
469 emit "mov #{target_ref}, #{@DX}\n"
473 # Divide target by x and store the remainder in target
475 mod target, target, x
478 # Multiply x by y and store the result in target
481 with_temporary do |temporary|
482 target_ref = load_value target, temporary
483 emit "mov #{target_ref}, #{@RETURN_REG}\n"
487 # Multiply target by x and store the result in target
489 mul target, target, x
497 # The quotient is stored in @AX, the remainder in @DX.
499 with_temporary do |temporary|
500 x_ref = load_value_into_register x, @AX
501 y_ref = load_value y, temporary
502 emit "mov #{@DX}, #{@AX}\n"
503 emit "sar #{@DX}, #{@WORDSIZE * 8 - 1}\n"
504 if immediate_operand?(y_ref)
505 set_register temporary, y_ref
506 emit "idiv #{temporary}\n"
508 emit "idiv #{@WORD_NAME} #{y_ref}\n"
513 # Evaluate an expression.
514 # The result is stored in _register_ (@RETURN_REG by default).
515 # The following registers may be clobbered: @AX, @CX, @DX
516 def eval_expr words, register = @RETURN_REG
519 emit "xor #{register}, #{register}\n"
521 load_value_into_register words[0], register
526 when :asr, :bsr, :rol, :ror, :shl, :shr
530 load_value_into_register words[2], @CX
533 load_value_into_register words[1], register
534 emit "#{action_to_mnemonic op} #{register}, #{y_ref}\n"
536 auto_bytes words[1], register
538 auto_words words[1], register
541 emit "mov #{register}, #{@RETURN_REG}\n" if register != @RETURN_REG
543 eval_div words[1], words[2]
544 set_register register, @AX
546 # Get address reference
547 address_ref = load_address words[1], words[2], 1
548 # Load byte from address
551 set_register register, 0
552 set_register :al, address_ref
554 set_register register, 0
555 set_register :bl, address_ref
557 set_register register, 0
558 set_register :cl, address_ref
560 set_register register, 0
561 set_register :dl, address_ref
564 set_register :al, address_ref
565 set_register register, @AX
568 address_ref = load_address words[1], words[2], @WORDSIZE
569 set_register register, address_ref
571 eval_div words[1], words[2]
572 set_register register, @DX
574 eval_mul words[1], words[2], register
576 load_value_into_register words[1], register
577 emit "not #{register}\n"
580 with_temporary do |t1|
581 x_ref = load_value words[1], @DX
582 y_ref = load_value words[2], t1
584 emit "mov #{@DX}, #{x_ref}\n"
585 emit "#{op} #{@DX}, #{y_ref}\n"
586 emit "mov #{register}, #{@DX}\n"
588 emit "mov #{register}, #{x_ref}\n" unless register == x_ref
589 emit "#{op} #{register}, #{y_ref}\n"
593 raise "Not a magic word: #{words[0]}"
600 # The result is stored in @AX by default, but
601 # a different register can be specified by passing
603 def eval_mul x, y, register = @AX
604 with_temporary do |t1|
605 x_ref = load_value x, @DX
606 y_ref = load_value y, t1
608 if immediate_operand? x_ref
609 if immediate_operand? y_ref
610 set_register register, x_ref * y_ref
612 emit "imul #{register}, #{y_ref}, #{x_ref}\n"
614 elsif immediate_operand? y_ref
615 emit "imul #{register}, #{x_ref}, #{y_ref}\n"
616 elsif y_ref != register
617 emit "mov #{register}, #{x_ref}\n" unless x_ref == register
618 emit "imul #{register}, #{y_ref}\n"
620 emit "imul #{register}, #{x_ref}\n"
629 # Start a conditional using the specified branch instruction
630 # after the comparison.
631 def common_if branch, x, y = nil
632 # Inverses of branches. E.g.
647 y_ref = load_value y, @DX
648 x_ref = load_value x, @AX
649 if immediate_operand?(x_ref)
650 # Can't have an immediate value as the first operand.
651 if immediate_operand?(y_ref)
652 # Both are immediates. Put the first in a register.
653 emit "mov #{@AX}, #{x_ref}\n"
656 # y isn't immediate; swap x and y.
657 x_ref, y_ref = [y_ref, x_ref]
658 branch = inverse_branch[branch]
660 elsif memory_operand?(x_ref) && memory_operand?(y_ref)
661 # Can't have two memory operands. Move the first into a register.
662 emit "mov #{@AX}, #{x_ref}\n"
665 truelabel = @environment.gensym
666 falselabel = @environment.gensym
667 @if_labels.push falselabel
669 emit "cmp #{@WORD_NAME} #{x_ref}, #{y_ref}\n"
670 emit "#{branch} #{truelabel}\n"
671 emit "jmp #{falselabel}\n"
672 emit "#{truelabel}:\n"
677 label = @if_labels.pop
681 # Start the false path of a conditional.
683 newlabel = @environment.gensym
684 emit "jmp #{newlabel}\n"
685 label = @if_labels.pop
687 @if_labels.push newlabel
690 # Test if x is equal to y
695 # Test if x is greater than or equal to y
700 # Test if x is strictly greater than y
705 # Test if x is less than or equal to y
710 # Test if x is strictly less than y
715 # Test if x different from y
724 # Translates a Voodoo action name to an x86 mnemonic
725 def action_to_mnemonic action
741 # Returns a memory reference for the address at the given offset
742 # from the frame pointer.
743 def offset_reference offset
745 "[#{@BP} + #{offset}]"
747 "[#{@BP} - #{-offset}]"
753 # Set a register to a value.
754 # The value must be a valid operand to the mov instruction.
755 def set_register register, value_ref
760 emit "xor #{register}, #{register}\n"
762 emit "mov #{register}, #{value_ref}\n"
770 # Write generated code to the given IO object.
772 io.puts "bits #{@WORDSIZE * 8}\n\n"
773 @sections.each do |section,code|
775 io.puts "section #{section.to_s}"