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:
15 # - #emit_function_prologue
20 # - @FUNCTION_ALIGNMENT
24 # - @AX, @BX, @CX, and @DX
25 class NasmGenerator < CommonCodeGenerator
26 def initialize params = {}
32 # == Information About the Generator
35 # Given an input file name, returns the canonical output file name
36 # for this code generator.
37 def output_file_name input_name
38 input_name.sub(/\.voo$/, '') + '.asm'
41 # Returns the number of bits per word for this code generator.
50 # Export symbols from the current section
52 emit "global #{symbols.join ', '}\n"
55 # Continue execution at the given address
57 emit "; goto #{value}\n"
58 value_ref = load_value value, @SCRATCH_REG
59 emit "jmp #{value_ref}\n"
62 # Import labels into the current section
64 emit "extern #{symbols.join ', '}\n"
67 # Define a label in the current section
76 # Define a byte with the given value
81 # Define a dword with the given value
86 # Define a qword with the given value
91 # Define a string with the given value
95 value.each_byte do |b|
100 code << ',' unless code.empty?
120 # Align data on the next _alignment_-byte boundary.
121 # If _alignemnt_ is not specified, the default data alignment
123 def align_data alignment = @DATA_ALIGNMENT
124 in_section(:data) { emit "align #{alignment},db 0\n" }
127 # Align code on the next _alignment_-byte boundary.
128 # If _alignemnt_ is not specified, the default code alignment
130 def align_code alignment = @CODE_ALIGNMENT
131 in_section(:code) { emit "align #{alignment},nop\n" }
134 # Align function on the next _alignment_-byte boundary.
135 # If _alignemnt_ is not specified, the default function alignment
137 def align_function alignment = @FUNCTION_ALIGNMENT
138 in_section(:code) { emit "align #{alignment},nop\n" }
145 # Emit function preamble and declare _formals_ as function arguments
146 def begin_function *formals
147 emit "; function #{formals.join ' '}\n"
148 environment = Environment.new @environment
149 environment.add_args formals
150 @environment = environment
151 emit_function_prologue formals
154 # Emit function epilogue.
155 def emit_function_epilogue formals = []
159 # End a function body
161 emit "; end function\n\n"
162 if @environment == @top_level
163 raise "Cannot end function when not in a function"
165 @environment = @top_level
169 # Return a from a function.
171 # _words_ may contain an expression to be evaluated. The result
172 # of the evaluation is returned from the function.
174 emit "; return #{words.join ' '}\n"
176 emit_function_epilogue
184 # End a conditional body
186 label = @if_labels.pop
191 # == Value Classification
194 # Test if op is a binary operation
196 [:div, :mod, :sub].member?(op) || symmetric_operation?(op)
199 # Test if a value is an integer
201 value.kind_of? Integer
204 # Test if a symbol refers to a global
206 symbol?(symbol) && @environment[symbol] == nil
209 # Tests if an operand is an immediate operand
210 def immediate_operand? operand
211 integer?(operand) || global?(operand)
214 # Tests if an operand is a memory operand
215 def memory_operand? operand
219 # Test if a value is a symbol
221 value.kind_of? Symbol
224 # Test if op is a symmetric operation (i.e. it will yield the
225 # same result if the order of its source operands is changed).
226 def symmetric_operation? op
227 [:add, :and, :mul, :or, :xor].member? op
234 # Create a value reference to an address.
235 # Invoking this code may clobber @BX and/or @CX
236 def load_address base, offset, scale
237 base_ref = load_value base, @BX
238 offset_ref = load_value offset, @CX
242 # Only an integer base
245 # Some complex base; load in @BX
246 emit "mov #{@BX}, #{base_ref}\n"
250 if integer? offset_ref
251 # Only a scaled offset
252 "[#{offset_ref.to_i * scale}]"
254 # Some complex offset; load in @CX
255 emit "mov #{@CX}, #{offset_ref}\n"
256 "[#{@CX} * #{scale}]"
258 elsif integer? base_ref
259 if integer? offset_ref
260 # All integers, combine them together
261 "[#{base_ref.to_i + (offset_ref.to_i * scale)}]"
263 # Complex offset; use @CX
264 emit "mov #{@CX}, #{offset_ref}\n"
265 "[#{base_ref} + #{@CX} * #{scale}]"
267 elsif integer? offset_ref
268 # Complex base, integer offset; use @BX
269 emit "mov #{@BX}, #{base_ref}\n"
270 "[#{@BX} + #{offset_ref.to_i * scale}]"
272 # Both base and offset are complex
273 # Use both @BX and @CX
274 emit "mov #{@BX}, #{base_ref}\n"
275 emit "mov #{@CX}, #{offset_ref}\n"
276 "[#{@BX} + #{@CX} * #{scale}]"
280 # Load the value associated with the given symbol.
281 # Returns a string that can be used to refer to the loaded value.
282 def load_symbol symbol, reg = @SCRATCH_REG
283 x = @environment[symbol]
291 raise "Invalid variable type: #{x[0]}"
300 # Returns a string that can be used to refer to the loaded value.
301 def load_value value, reg = @SCRATCH_REG
303 # Integers can be used as is
306 load_symbol value, reg
314 # Evaluate the expr in words and store the result in target
315 def set target, *words
317 raise "Cannot change value of integer #{target}"
318 elsif global?(target)
319 raise "Cannot change value of global #{target}"
322 emit "; set #{target} #{words.join ' '}\n"
324 if words[0] == target
325 emit "; nothing to do; destination equals source\n"
327 target_ref = load_value target, @BX
328 if integer?(words[0])
329 if words[0].to_i == 0
330 # Set destination to 0
331 emit "xor #{@AX}, #{@AX}\n"
332 emit "mov #{target_ref}, #{@AX}\n"
335 emit "mov #{@WORD_NAME} #{target_ref}, #{words[0]}\n"
338 # Copy source to destination
339 eval_expr words, @RETURN_REG
340 emit "mov #{target_ref}, #{@RETURN_REG}\n"
346 if words.length == 3 && binop?(op)
348 binop op, target, words[1], words[2]
350 # Not a binary operation
351 eval_expr words, @RETURN_REG
352 target_ref = load_value target, @BX
353 emit "mov #{target_ref}, #{@RETURN_REG}\n"
358 # Set the byte at _base_ + _offset_ to _value_
359 def set_byte base, offset, value
360 emit "; set-byte #{base} #{offset} #{value}\n"
361 value_ref = load_value value, @RETURN_REG
362 addr_ref = load_address base, offset, 1
363 emit "mov byte #{addr_ref}, #{value_ref}\n"
366 # Set the word at _base_ + _offset_ * +@WORDSIZE+ to _value_
367 def set_word base, offset, value
368 emit "; set-word #{base} #{offset} #{value}\n"
369 value_ref = load_value value, @RETURN_REG
370 addr_ref = load_address base, offset, @WORDSIZE
371 emit "mov dword #{addr_ref}, #{value_ref}\n"
375 # == Binary Operations
378 # Emit code for a binary operation
379 def binop op, target, x, y
382 elsif symmetric_operation?(op) && y == target
385 # Cases that are handled specially
386 return div(target, x, y) if op == :div
387 return mod(target, x, y) if op == :mod
388 return mul(target, x, y) if op == :mul
390 target_ref = load_value target, @AX
391 x_ref = load_value x, @DX
392 y_ref = load_value y, @BX
394 if memory_operand?(target_ref)
395 if memory_operand?(x_ref) || memory_operand?(y_ref)
396 emit "mov #{@CX}, #{x_ref}\n"
397 emit "#{op} #{@CX}, #{y_ref}\n"
398 emit "mov #{target_ref}, #{@CX}\n"
400 emit "mov #{@WORD_NAME} #{target_ref}, #{x_ref}\n"
401 emit "#{op} #{@WORD_NAME} #{target_ref}, #{y_ref}\n"
404 raise "Can't happen: target_ref is #{target_ref.inspect}"
409 # Emit code for a binary operation where the first operand
411 def binop2 op, target, y
412 # Cases that are handled specially
413 return div2(target, target, y) if op == :div
414 return mod2(target, y) if op == :mod
415 return mul2(target, y) if op == :mul
417 target_ref = load_value target, @BX
418 y_ref = load_value y, @DX
419 if memory_operand?(target_ref) && memory_operand?(y_ref)
420 emit "mov #{@AX}, #{y_ref}\n"
421 emit "#{op} #{target_ref}, #{@AX}\n"
423 emit "#{op} #{@WORD_NAME} #{target_ref}, #{y_ref}\n"
427 # Divide x by y and store the quotient in target
430 target_ref = load_value target, @BX
431 emit "mov #{target_ref}, #{@AX}\n"
434 # Divide target by x and store the quotient in target
436 div target, target, x
439 # Divide x by y and store the remainder in target
442 target_ref = load_value target, @BX
443 emit "mov #{target_ref}, #{@DX}\n"
446 # Divide target by x and store the remainder in target
448 mod target, target, x
451 # Multiply x by y and store the result in target
454 target_ref = load_value target, @BX
455 emit "mov #{target_ref}, #{@RETURN_REG}\n"
458 # Multiply target by x and store the result in target
460 mul target, target, x
468 # The quotient is stored in @AX, the remainder in @DX.
470 x_ref = load_value_into_register x, @AX
471 y_ref = load_value y, @SCRATCH_REG
473 if immediate_operand?(y_ref)
474 set_register @BX, y_ref
477 emit "idiv #{@WORD_NAME} #{y_ref}\n"
481 # Evaluate an expression.
482 # The result is stored in _register_ (@RETURN_REG by default).
483 def eval_expr words, register = @RETURN_REG
486 emit "xor #{register}, #{register}\n"
488 load_value_into_register words[0], register
496 eval_div words[1], words[2]
497 set_register register, @AX
500 set_register register, 0
501 # Get address reference
502 address_ref = load_address words[1], words[2], 1
503 # Load byte from address
506 set_register 'al', address_ref
508 set_register 'bl', address_ref
510 set_register 'cl', address_ref
512 set_register 'dl', address_ref
515 set_register 'bl', address_ref
516 set_register register, @BX
519 address_ref = load_address words[1], words[2], @WORDSIZE
520 set_register register, address_ref
522 eval_div words[1], words[2]
523 set_register register, @DX
525 eval_mul words[1], words[2], register
527 load_value_into_register words[1], register
528 emit "not #{register}\n"
531 x_ref = load_value words[1], @DX
532 y_ref = load_value words[2], @BX
533 emit "mov #{register}, #{x_ref}\n"
534 emit "#{op} #{register}, #{y_ref}\n"
536 raise "Not a magic word: #{words[0]}"
543 # The result is stored in @AX by default, but
544 # a different register can be specified by passing
546 def eval_mul x, y, register = @AX
547 x_ref = load_value x, @DX
548 y_ref = load_value y, @BX
550 if immediate_operand? x_ref
551 if immediate_operand? y_ref
552 set_register register, x_ref * y_ref
554 emit "imul #{register}, #{y_ref}, #{x_ref}\n"
556 elsif immediate_operand? y_ref
557 emit "imul #{register}, #{x_ref}, #{y_ref}\n"
559 emit "mov #{register}, #{x_ref}\n"
560 emit "imul #{register}, #{y_ref}\n"
568 # Start a conditional using the specified branch instruction
569 # after the comparison.
570 def common_if branch, x, y = nil
571 load_value_into_register y, @DX if y
572 load_value_into_register x, @AX
573 truelabel = @environment.gensym
574 falselabel = @environment.gensym
575 @if_labels.push falselabel
577 emit "cmp #{@AX}, #{@DX}\n"
578 emit "#{branch} #{truelabel}\n"
579 emit "jmp #{falselabel}\n"
580 emit "#{truelabel}:\n"
585 label = @if_labels.pop
589 # Start the false path of a conditional.
592 newlabel = @environment.gensym
593 emit "jmp #{newlabel}\n"
594 label = @if_labels.pop
596 @if_labels.push newlabel
599 # Test if x is equal to y
601 emit "; ifeq #{x} #{y}\n"
605 # Test if x is greater than or equal to y
607 emit "; ifge #{x} #{y}\n"
608 common_if 'jge', x, y
611 # Test if x is strictly greater than y
613 emit "; ifgt #{x} #{y}\n"
617 # Test if x is less than or equal to y
619 emit "; ifle #{x} #{y}\n"
620 common_if 'jle', x, y
623 # Test if x is strictly less than y
625 emit "; iflt #{x} #{y}\n"
629 # Test if x different from y
631 emit "; ifne #{x} #{y}\n"
632 common_if 'jne', x, y
644 # Load a value into a register
645 def load_value_into_register value, register
646 value_ref = load_value value, register
647 set_register register, value_ref
650 # Set a register to a value.
651 # The value must be a valid operand to the mov instruction.
652 def set_register register, value_ref
657 emit "xor #{register}, #{register}\n"
659 emit "mov #{register}, #{value_ref}\n"
667 # Write generated code to the given IO object.
669 io.puts "bits #{@WORDSIZE * 8}\n\n"
670 @sections.each do |section,code|
674 section_name = '.text'
676 section_name = '.data'
678 section_name = '.text'
680 section_name = section.to_s
682 io.puts "section #{section_name}"