implemented save-locals and restore-locals on i386
[voodoo-lang.git] / lib / voodoo / generators / i386_nasm_generator.rb
blobc687c1f79045c8db463affdbe09cd4a7780b1fb3
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       # Register used as scratch register
61       @SCRATCH_REG = :ebx
62       # Accumulator index
63       @AX = :eax
64       # Base index
65       @BX = :ebx
66       # Count index
67       @CX = :ecx
68       # Data index
69       @DX = :edx
70       # Base pointer
71       @BP = :ebp
72       # Stack pointer
73       @SP = :esp
74       # Registers used to store locals
75       @LOCAL_REGS = []
76       @NLOCAL_REGS = @LOCAL_REGS.length
77       @LOCAL_REGISTERS_SET = Set.new @LOCAL_REGS
78       @SAVED_FRAME_LAYOUT = { :ebx => 0, :edi => 1, :esi => 2, :esp => 3, :ebp => 4 }
79       @SAVE_FRAME_REGISTERS = @SAVED_FRAME_LAYOUT.keys
80       super params
81       @features.merge! \
82         :'bits-per-word' => '32',
83         :'byte-order' => 'little-endian',
84         :'bytes-per-word' => '4'
85     end
87     # Returns the offset of the nth argument.
88     def arg_offset n
89       8 + (n * @WORDSIZE)
90     end
92     # Emits function preamble and declare +formals+ as function arguments.
93     def begin_function formals, nlocals
94       environment = Environment.new @environment
95       @environment = environment
96       emit "push #{@BP}\nmov #{@BP}, #{@SP}\n"
97       formals.each_with_index do |arg,i|
98         environment.add_arg arg, arg_offset(i)
99       end
100       emit "sub #{@SP}, #{nlocals * @WORDSIZE}\n"      
101     end
103     # Calls a function.
104     def call func, *args
105       revargs = args.reverse
106       revargs.each { |arg| push arg }
107       use_value "call", func
108       if args.length > 0
109         emit "add esp, #{@WORDSIZE * args.length}\n"
110       end
111     end
113     # If the nth local is stored in a register, returns that register.
114     # Otherwise, returns the offset from the frame pointer.
115     def local_offset_or_register n
116       (n + 1) * -@WORDSIZE
117     end
119     # Push a word on the stack
120     def push value
121       value_ref = load_value value, "ebx"
122       emit "push dword #{value_ref}\n"
123     end
125     # Call a function, re-using the current call frame if possible
126     def tail_call fun, *args
127       if args.length > @environment.args
128         # Not enough space to do proper tail call; do normal call instead
129         emit "; not enough space for proper tail call; changed to regular call\n"
130         ret :call, fun, *args
131       else
132         # If any arguments are going to be overwritten before they are
133         # used, save them to new local variables and use those instead.
134         (args.length - 1).downto(0) do |i|
135           arg = args[i]
136           next unless symbol?(arg)
137           old_arg_offset = @environment[arg]
138           next if old_arg_offset == nil || old_arg_offset < 0
139           # arg is an argument that was passed on the stack.
140           new_arg_offset = arg_offset i
141           next unless old_arg_offset > new_arg_offset
142           # arg will be overwritten before it is used.
143           # Save it in a newly created temporary variable,
144           # then use that instead.
145           newsym = @environment.gensym
146           let newsym, arg
147           args[i] = newsym
148         end
150         # Same for the function we will be calling.
151         if symbol?(fun)
152           offset = @environment[fun]
153           if offset != nil && offset > 0
154             newsym = @environment.gensym
155             let newsym, fun
156             func = newsym
157           end
158         end
160         # Set arguments
161         if args.length > 0
162           (args.length - 1).downto(0).each do |i|
163             arg = args[i]
164             
165             value_ref = load_value arg, :eax
166             newarg_ref = "[ebp + #{arg_offset i}]"
167             # Elide code if source is same as destination
168             unless value_ref == newarg_ref
169               if memory_operand?(value_ref)
170                 emit "mov eax, #{value_ref}\n"
171                 value_ref = :eax
172               end
173               emit "mov #{@WORD_NAME} #{newarg_ref}, #{value_ref}\n"
174             end
175           end
176         end
178         # Tail call
179         emit "mov esp, ebp\npop ebp\n"
180         use_value "jmp", fun
181       end
182     end
184     def use_value operation, value
185       value_ref = load_value value, :eax
186       emit "#{operation} #{value_ref}\n"
187     end
189     # Define a machine word with the given value
190     def word value
191       emit "dd #{value}\n"
192     end
194   end
196   # Register class
197   Voodoo::CodeGenerator.register_generator I386NasmGenerator,
198                                            :architecture => :i386,
199                                            :format => :nasm