i386_nasm_generator: fixed tail_call.
[voodoo-lang.git] / lib / voodoo / generators / i386_nasm_generator.rb
blobe2b4291ca1439915cb1dd79a0fd86abadb550ff2
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       super params
60     end
62     # Call a function
63     def call func, *args
64       emit "; call #{func} #{args.join ' '}\n"
65       revargs = args.reverse
66       revargs.each { |arg| push arg }
67       use_value "call", func
68       if args.length > 0
69         emit "add esp, #{WORDSIZE * args.length}\n"
70       end
71     end
73     # Emit function prologue.
74     def emit_function_prologue formals = []
75       emit "push ebp\nmov ebp, esp\n"
76     end
78     # Load the value of the nth argument
79     def load_arg n, reg = @SCRATCH_REG
80       "[ebp + #{n * @WORDSIZE + 8}]"
81     end
83     # Load the value of the nth local variable
84     def load_local n, reg = @SCRATCH_REG
85       "[ebp - #{(n + 1) * @WORDSIZE}]"
86     end
88     # Introduce a new local variable
89     def let symbol, *words
90       emit "; let #{symbol} #{words.join ' '}\n"
91       @environment.add_local symbol
92       eval_expr words
93       emit "push eax\n"
94     end
96     # Push a word on the stack
97     def push value
98       #emit "; push #{value}\n"
99       value_ref = load_value value, "ebx"
100       emit "push dword #{value_ref}\n"
101     end
103     # Call a function, re-using the current call fram if possible
104     def tail_call fun, *args
105       emit "; tail-call #{fun} #{args.join ' '}\n"
106       if args.length > @environment.args
107         # Not enough space to do proper tail call; do normal call instead
108         emit "; not enough space for proper tail call; changed to regular call\n"
109         ret :call, fun, *args
110       else
111         # Any value in the current frame that is passed to the called
112         # function must be copied to a local variable if it would otherwise
113         # be overwritten before it is used
114         i = args.length - 1
115         while i >= -1
116           arg = (i >= 0) ? args[i] : fun
118           if symbol?(arg)
119             x = @environment[arg]
120             if x && x[0] == :arg && x[1] < args.length && x[1] > i &&
121                 (i >= 0 || fun != args[x[1]])
122               # Save value
123               newsym = @environment.gensym
124               let newsym, arg
125               # Change reference
126               if i >= 0
127                 args[i] = newsym
128               else
129                 fun = newsym
130               end
131             end
132           end
133           i = i - 1
134         end
136         # Set arguments
137         if args.length > 0
138           (args.length - 1).downto(0).each do |i|
139             arg = args[i]
140             
141             value_ref = load_value arg, "eax"
142             newarg_ref = "[ebp + #{(i + 2) * WORDSIZE}]"
143             # Elide code if source is same as destination
144             unless value_ref == newarg_ref
145               if memory_operand?(value_ref)
146                 emit "mov eax, #{value_ref}\n"
147                 value_ref = "eax"
148               end
149               emit "mov #{@WORD_NAME} [ebp + #{(i + 2) * WORDSIZE}], " +
150                     "#{value_ref}\n"
151             end
152           end
153         end
155         # Tail call
156         emit "mov esp, ebp\npop ebp\n"
157         use_value "jmp", fun
158       end
159     end
161     def use_value operation, value
162       value_ref = load_value value, "eax"
163       emit "#{operation} #{value_ref}\n"
164     end
166     # Define a machine word with the given value
167     def word value
168       emit "dd #{value}\n"
169     end
171   end
173   # Register class
174   Voodoo::CodeGenerator.register_generator I386NasmGenerator,
175                                            :architecture => :i386,
176                                            :format => :nasm