require 'voodoo/generators/common_code_generator'

module Voodoo
  # = i386 NASM Code Generator
  #
  # The i386 NASM code generator generates i386 assembly code for use with
  # the {Netwide Assembler}[http://www.nasm.us/].
  #
  # == Calling Convention
  #
  # Function arguments are pushed on the stack in reverse order, so that
  # the first argument is pushed last. Each argument occupies one word
  # of stack space. These arguments are removed from the stack by the
  # caller after the called function returns.
  #
  # The return value is passed in +eax+.
  #
  # == Call Frames
  #
  # Call frames have the following layout:
  #
  #   argn
  #   :
  #   arg1
  #   arg0   <-- ebp + 8
  #   oldeip <-- ebp + 4
  #   oldebp <-- ebp
  #   local0 <-- ebp - 4
  #   local1 <-- ebp - 8
  #   :
  #   localn <-- esp
  #
  class I386NasmGenerator < NasmGenerator
    WORDSIZE = 4

    def initialize params = {}
      # Number of bytes in a word
      @WORDSIZE = 4
      # Word name in NASM lingo
      @WORD_NAME = 'dword'
      # 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 = 'eax'
      # Register used as scratch register
      @SCRATCH_REG = 'ebx'
      # Accumulator index
      @AX = 'eax'
      # Base index
      @BX = 'ebx'
      # Count index
      @CX = 'ecx'
      # Data index
      @DX = 'edx'
      super params
    end

    # Call a function
    def call func, *args
      emit "; call #{func} #{args.join ' '}\n"
      revargs = args.reverse
      revargs.each { |arg| push arg }
      use_value "call", func
      if args.length > 0
        emit "add esp, #{WORDSIZE * args.length}\n"
      end
    end

    # Emit function prologue.
    def emit_function_prologue formals = []
      emit "push ebp\nmov ebp, esp\n"
    end

    # Load the value of the nth argument
    def load_arg n, reg = @SCRATCH_REG
      "[ebp + #{n * @WORDSIZE + 8}]"
    end

    # Load the value of the nth local variable
    def load_local n, reg = @SCRATCH_REG
      "[ebp - #{(n + 1) * @WORDSIZE}]"
    end

    # Introduce a new local variable
    def let symbol, *words
      emit "; let #{symbol} #{words.join ' '}\n"
      @environment.add_local symbol
      eval_expr words
      emit "push eax\n"
    end

    # Push a word on the stack
    def push value
      #emit "; push #{value}\n"
      value_ref = load_value value, "ebx"
      emit "push dword #{value_ref}\n"
    end

    # Call a function, re-using the current call fram if possible
    def tail_call fun, *args
      emit "; tail-call #{fun} #{args.join ' '}\n"
      if args.length > @environment.args
        # Not enough space to do proper tail call; do normal call instead
        emit "; not enough space for proper tail call; changed to regular call\n"
        ret :call, fun, *args
      else
        # Any value in the current frame that is passed to the called
        # function must be copied to a local variable if it would otherwise
        # be overwritten before it is used
        i = args.length - 1
        while i >= -1
          arg = (i >= 0) ? args[i] : fun

          if symbol?(arg)
            x = @environment[arg]
            if x && x[0] == :arg && x[1] < args.length && x[1] > i &&
                (i >= 0 || fun != args[x[1]])
              # Save value
              newsym = @environment.gensym
              let newsym, arg
              # Change reference
              if i >= 0
                args[i] = newsym
              else
                fun = newsym
              end
            end
          end
          i = i - 1
        end

        # Set arguments
        if args.length > 0
          (args.length - 1).downto(0).each do |i|
            arg = args[i]
            
            value_ref = load_value arg, "eax"
            newarg_ref = "[ebp + #{(i + 2) * WORDSIZE}]"
            # Elide code if source is same as destination
            unless value_ref == newarg_ref
              if memory_operand?(value_ref)
                emit "mov eax, #{value_ref}\n"
                value_ref = "eax"
              end
              emit "mov #{@WORD_NAME} [ebp + #{(i + 2) * WORDSIZE}], " +
                    "#{value_ref}\n"
            end
          end
        end

        # Tail call
        emit "mov esp, ebp\npop ebp\n"
        use_value "jmp", fun
      end
    end

    def use_value operation, value
      value_ref = load_value value, "eax"
      emit "#{operation} #{value_ref}\n"
    end

    # Define a machine word with the given value
    def word value
      emit "dd #{value}\n"
    end

  end

  # Register class
  Voodoo::CodeGenerator.register_generator I386NasmGenerator,
                                           :architecture => :i386,
                                           :format => :nasm
end
