Made top-level blocks in i386 and AMD64 set EBP/RBP, so BP-relative
[voodoo-lang.git] / lib / voodoo / generators / i386_nasm_generator.rb
blobb0e7e9224cce04f45422f80e496a31b2bb819aad
1 require 'voodoo/generators/common_code_generator'
3 module Voodoo
4   # = i386 NASM Code Generator
5   #
6   # The i386 NASM code generator generates i386 assembly code for use with
7   # the {Netwide Assembler}[http://www.nasm.us/].
8   #
9   # == Calling Convention
10   #
11   # Function arguments are pushed on the stack in reverse order, so that
12   # the first argument is pushed last. Each argument occupies one word
13   # of stack space. These arguments are removed from the stack by the
14   # caller after the called function returns.
15   #
16   # The return value is passed in +eax+.
17   #
18   # == Call Frames
19   #
20   # Call frames have the following layout:
21   #
22   #   argn
23   #   :
24   #   arg1
25   #   arg0   <-- ebp + 8
26   #   oldeip <-- ebp + 4
27   #   oldebp <-- ebp
28   #   local0 <-- ebp - 4
29   #   local1 <-- ebp - 8
30   #   :
31   #   localn <-- esp
32   #
33   class I386NasmGenerator < NasmGenerator
34     WORDSIZE = 4
36     def initialize params = {}
37       # Number of bytes in a word
38       @WORDSIZE = 4
39       # Word name in NASM lingo
40       @WORD_NAME = 'dword'
41       # Default alignment for code
42       @CODE_ALIGNMENT = 0
43       # Default alignment for data
44       @DATA_ALIGNMENT = @WORDSIZE
45       # Default alignment for functions
46       @FUNCTION_ALIGNMENT = 16
47       # Register used for return values
48       @RETURN_REG = 'eax'
49       # Register used as scratch register
50       @SCRATCH_REG = 'ebx'
51       # Accumulator index
52       @AX = 'eax'
53       # Base index
54       @BX = 'ebx'
55       # Count index
56       @CX = 'ecx'
57       # Data index
58       @DX = 'edx'
59       # Base pointer
60       @BP = 'ebp'
61       # Stack pointer
62       @SP = 'esp'
63       super params
64     end
66     # Call a function
67     def call func, *args
68       emit "; call #{func} #{args.join ' '}\n"
69       revargs = args.reverse
70       revargs.each { |arg| push arg }
71       use_value "call", func
72       if args.length > 0
73         emit "add esp, #{WORDSIZE * args.length}\n"
74       end
75     end
77     # Emit function prologue.
78     def emit_function_prologue formals = []
79       emit "push ebp\nmov ebp, esp\n"
80     end
82     # Load the value of the nth argument
83     def load_arg n, reg = @SCRATCH_REG
84       "[ebp + #{n * @WORDSIZE + 8}]"
85     end
87     # Load the value of the nth local variable
88     def load_local n, reg = @SCRATCH_REG
89       "[ebp - #{(n + 1) * @WORDSIZE}]"
90     end
92     # Introduce a new local variable
93     def let symbol, *words
94       emit "; let #{symbol} #{words.join ' '}\n"
95       @environment.add_local symbol
96       eval_expr words
97       emit "push eax\n"
98     end
100     # Push a word on the stack
101     def push value
102       #emit "; push #{value}\n"
103       value_ref = load_value value, "ebx"
104       emit "push dword #{value_ref}\n"
105     end
107     # Call a function, re-using the current call fram if possible
108     def tail_call fun, *args
109       emit "; tail-call #{fun} #{args.join ' '}\n"
110       if args.length > @environment.args
111         # Not enough space to do proper tail call; do normal call instead
112         emit "; not enough space for proper tail call; changed to regular call\n"
113         ret :call, fun, *args
114       else
115         # Any value in the current frame that is passed to the called
116         # function must be copied to a local variable if it would otherwise
117         # be overwritten before it is used
118         i = args.length - 1
119         while i >= -1
120           arg = (i >= 0) ? args[i] : fun
122           if symbol?(arg)
123             x = @environment[arg]
124             if x && x[0] == :arg && x[1] < args.length && x[1] > i &&
125                 (i >= 0 || fun != args[x[1]])
126               # Save value
127               newsym = @environment.gensym
128               let newsym, arg
129               # Change reference
130               if i >= 0
131                 args[i] = newsym
132               else
133                 fun = newsym
134               end
135             end
136           end
137           i = i - 1
138         end
140         # Set arguments
141         if args.length > 0
142           (args.length - 1).downto(0).each do |i|
143             arg = args[i]
144             
145             value_ref = load_value arg, "eax"
146             newarg_ref = "[ebp + #{(i + 2) * WORDSIZE}]"
147             # Elide code if source is same as destination
148             unless value_ref == newarg_ref
149               if memory_operand?(value_ref)
150                 emit "mov eax, #{value_ref}\n"
151                 value_ref = "eax"
152               end
153               emit "mov #{@WORD_NAME} [ebp + #{(i + 2) * WORDSIZE}], " +
154                     "#{value_ref}\n"
155             end
156           end
157         end
159         # Tail call
160         emit "mov esp, ebp\npop ebp\n"
161         use_value "jmp", fun
162       end
163     end
165     def use_value operation, value
166       value_ref = load_value value, "eax"
167       emit "#{operation} #{value_ref}\n"
168     end
170     # Define a machine word with the given value
171     def word value
172       emit "dd #{value}\n"
173     end
175   end
177   # Register class
178   Voodoo::CodeGenerator.register_generator I386NasmGenerator,
179                                            :architecture => :i386,
180                                            :format => :nasm