made save-frame only save registers not yet saved in the frame
[voodoo-lang.git] / lib / voodoo / generators / i386_nasm_generator.rb
blob57c8bfe8c44460e6fc73d634fef07bc72b291cea
1 require 'voodoo/generators/common_code_generator'
2 require 'voodoo/generators/nasm_generator'
4 module Voodoo
5   # = i386 NASM Code Generator
6   #
7   # The i386 NASM code generator generates i386 assembly code for use with
8   # the {Netwide Assembler}[http://www.nasm.us/].
9   #
10   # == Calling Convention
11   #
12   # Function arguments are pushed on the stack in reverse order, so that
13   # the first argument is pushed last. Each argument occupies one word
14   # of stack space. These arguments are removed from the stack by the
15   # caller after the called function returns.
16   #
17   # The return value is passed in +eax+.
18   #
19   # == Call Frames
20   #
21   # Call frames have the following layout:
22   #
23   #   argn
24   #   :
25   #   arg1
26   #   arg0   <-- ebp + 8
27   #   oldeip <-- ebp + 4
28   #   oldebp <-- ebp
29   #   local0 <-- ebp - 4
30   #   local1 <-- ebp - 8
31   #   :
32   #   localn <-- esp
33   #
34   # == Callee-Save Registers
35   #
36   # +ebp+, +ebx+, +edi+, +esi+, and +esp+ are callee-save registers.
37   #
38   # All other registers are caller-save registers.
39   #
40   class I386NasmGenerator < NasmGenerator
41     WORDSIZE = 4
43     def initialize params = {}
44       # Number of bytes in a word
45       @WORDSIZE_BITS = 2
46       @WORDSIZE = 1 << @WORDSIZE_BITS
47       # Word name in NASM lingo
48       @WORD_NAME = 'dword'
49       # Default alignment for code
50       @CODE_ALIGNMENT = 0
51       # Default alignment for data
52       @DATA_ALIGNMENT = @WORDSIZE
53       # Default alignment for functions
54       @FUNCTION_ALIGNMENT = 16
55       # Stack alingment
56       @STACK_ALIGNMENT_BITS = @WORDSIZE_BITS
57       @STACK_ALIGNMENT = 1 << @STACK_ALIGNMENT_BITS
58       # Register used for return values
59       @RETURN_REG = :eax
60       # Accumulator index
61       @AX = :eax
62       # Base index
63       @BX = :ebx
64       # Count index
65       @CX = :ecx
66       # Data index
67       @DX = :edx
68       # Base pointer
69       @BP = :ebp
70       # Stack pointer
71       @SP = :esp
72       # Registers used to store locals
73       @LOCAL_REGISTERS = []
74       @NLOCAL_REGISTERS = @LOCAL_REGISTERS.length
75       @LOCAL_REGISTERS_SET = Set.new @LOCAL_REGISTERS
76       @SAVE_FRAME_REGISTERS = [:ebx, :edi, :esi, :esp, :ebp]
77       @SAVED_FRAME_LAYOUT = {}
78       @SAVE_FRAME_REGISTERS.each_with_index { |r,i| @SAVED_FRAME_LAYOUT[r] = i }
79       @TEMPORARIES = [:ebx]
80       super params
81       @saved_registers = []
82       @features.merge! \
83         :'bits-per-word' => '32',
84         :'byte-order' => 'little-endian',
85         :'bytes-per-word' => '4'
86     end
88     # Returns the offset of the nth argument.
89     def arg_offset n
90       8 + (n * @WORDSIZE)
91     end
93     # Emits function preamble and declare +formals+ as function arguments.
94     def begin_function formals, nlocals
95       environment = Environment.new @environment
96       @environment = environment
97       emit "push #{@BP}\nmov #{@BP}, #{@SP}\n"
98       formals.each_with_index do |arg,i|
99         environment.add_arg arg, arg_offset(i)
100       end
101       emit "sub #{@SP}, #{nlocals * @WORDSIZE}\n"      
102     end
104     # Calls a function.
105     def call func, *args
106       revargs = args.reverse
107       revargs.each { |arg| push arg }
108       use_value "call", func
109       if args.length > 0
110         emit "add esp, #{@WORDSIZE * args.length}\n"
111       end
112     end
114     # If the nth local is stored in a register, returns that register.
115     # Otherwise, returns the offset from the frame pointer.
116     def local_offset_or_register n
117       (n + 1) * -@WORDSIZE
118     end
120     # Push a word on the stack
121     def push value
122       value_ref = load_value value, "ebx"
123       emit "push dword #{value_ref}\n"
124     end
126     # Call a function, re-using the current call frame if possible
127     def tail_call fun, *args
128       if args.length > @environment.args
129         # Not enough space to do proper tail call; do normal call instead
130         emit "; not enough space for proper tail call; changed to regular call\n"
131         ret :call, fun, *args
132       else
133         # If any arguments are going to be overwritten before they are
134         # used, save them to new local variables and use those instead.
135         (args.length - 1).downto(0) do |i|
136           arg = args[i]
137           next unless symbol?(arg)
138           old_arg_offset = @environment[arg]
139           next if old_arg_offset == nil || old_arg_offset < 0
140           # arg is an argument that was passed on the stack.
141           new_arg_offset = arg_offset i
142           next unless old_arg_offset > new_arg_offset
143           # arg will be overwritten before it is used.
144           # Save it in a newly created temporary variable,
145           # then use that instead.
146           newsym = @environment.gensym
147           let newsym, arg
148           args[i] = newsym
149         end
151         # Same for the function we will be calling.
152         if symbol?(fun)
153           offset = @environment[fun]
154           if offset != nil && offset > 0
155             newsym = @environment.gensym
156             let newsym, fun
157             func = newsym
158           end
159         end
161         # Set arguments
162         if args.length > 0
163           (args.length - 1).downto(0).each do |i|
164             arg = args[i]
165             
166             value_ref = load_value arg, :eax
167             newarg_ref = "[ebp + #{arg_offset i}]"
168             # Elide code if source is same as destination
169             unless value_ref == newarg_ref
170               if memory_operand?(value_ref)
171                 emit "mov eax, #{value_ref}\n"
172                 value_ref = :eax
173               end
174               emit "mov #{@WORD_NAME} #{newarg_ref}, #{value_ref}\n"
175             end
176           end
177         end
179         # Tail call
180         emit "mov esp, ebp\npop ebp\n"
181         use_value "jmp", fun
182       end
183     end
185     def use_value operation, value
186       value_ref = load_value value, :eax
187       emit "#{operation} #{value_ref}\n"
188     end
190     # Define a machine word with the given value
191     def word value
192       emit "dd #{value}\n"
193     end
195   end
197   # Register class
198   Voodoo::CodeGenerator.register_generator I386NasmGenerator,
199                                            :architecture => :i386,
200                                            :format => :nasm