vidarh / writing-a-compiler-in-ruby

Code from my series on writing a Ruby compiler in Ruby

This URL has Read+Write access

writing-a-compiler-in-ruby / emitter.rb
100644 192 lines (154 sloc) 3.394 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
class Emitter
  PTR_SIZE = 4 # Point size in bytes. We are evil and assume 32bit arch. for now
 
  attr_accessor :seq
 
  def initialize
    @seq = 0
  end
 
  def comment(str)
    puts "\t# #{str}"
  end
 
  def export(label,type=nil)
    puts ".globl #{label}"
    puts "\t.type\t#{label}, @#{type.to_s}"
  end
 
  def rodata
    emit(".section",".rodata")
    yield
  end
 
  def bss
    emit(".section",".bss")
    yield
  end
 
  def local_arg(aparam)
    "#{PTR_SIZE*(aparam+2)}(%ebp)"
  end
 
  def local_var(aparam)
    # +2 instead of +1 because %ecx is pushed onto the stack in main,
    # In any variadic function we push :numargs from %ebx into -4(%ebp)
    "-#{PTR_SIZE*(aparam+2)}(%ebp)"
  end
 
  def label(l)
    puts "#{l.to_s}:"
    l
  end
 
  def local(l=nil)
    l = get_local if !l
    label(l)
  end
 
  def int_value param
    return "$#{param.to_i}"
  end
 
  def addr_value param
    return "$#{param.to_s}"
  end
 
  def result_value
    return :eax
  end
 
  def to_operand_value src
    return int_value(src) if src.is_a?(Fixnum)
    return "%#{src.to_s}" if src.is_a?(Symbol)
    return src.to_s
  end
 
  # Store "param" in stack slot "i"
  def save_to_stack param,i
    movl(param,"#{i>0 ? i*4 : ""}(%esp)")
  end
 
  def save_result(param)
    movl(param,:eax) if param != :eax
  end
 
  def load_arg(aparam)
    movl(local_arg(aparam),:eax)
  end
 
  def load_arg_address(aparam)
    leal(local_arg(aparam),:eax)
  end
 
  def load_local_var(aparam)
    movl(local_var(aparam),:eax)
  end
 
  def save_to_local_var(arg,aparam)
    movl(arg,local_var(aparam))
  end
 
  def save_to_arg(arg,aparam)
    movl(arg,local_arg(aparam))
  end
 
  def load_address(label)
    save_result(addr_value(label))
  end
 
  def with_local(args)
    # FIXME: The "+1" is a hack because main contains a pushl %ecx
    with_stack(args+1) { yield }
  end
 
  def with_stack(args,numargs=false)
    # gcc does 4 bytes regardless of arguments, and then jumps up 16 at a time
    # We will do the same, but assume its tied to pointer size
    adj = PTR_SIZE + (((args+0.5)*PTR_SIZE/(4.0*PTR_SIZE)).round) * (4*PTR_SIZE)
    subl(adj,:esp)
    movl(args,:ebx) if numargs
    yield
    addl(adj,:esp)
  end
 
  def emit op,*args
    puts "\t#{op}\t"+args.collect{|a|to_operand_value(a)}.join(', ')
  end
 
  def method_missing(sym,*args)
    emit(sym,*args)
  end
 
  def call loc
    if loc.is_a?(Symbol)
      emit(:call,"*"+to_operand_value(loc))
    else
      emit(:call,loc)
    end
  end
    
  def string l,str
    local(l)
    emit(".string","\"#{str}\"")
  end
 
  def bsslong l
    label(l)
    emit(".long 0")
  end
 
  def jmp_on_false(label,op=:eax)
    testl(op,op)
    je(label)
  end
 
  def get_local
    @seq +=1
    ".L#{@seq-1}"
  end
 
  def loop
    br = get_local
    l = local
    yield(br)
    jmp(l)
    local(br)
  end
 
  def func name, save_numargs = false
    export(name,:function) if name.to_s[0] != ?.
    label(name)
    pushl(:ebp)
    movl(:esp,:ebp)
    pushl(:ebx) if save_numargs
    yield
    leave
    ret
    emit(".size",name.to_s, ".-#{name}")
  end
 
  def main
    puts ".text"
    export(:main,:function)
    label(:main)
    leal("4(%esp)",:ecx)
    andl(-16,:esp)
    pushl("-4(%ecx)")
    pushl(:ebp)
    movl(:esp,:ebp)
    pushl(:ecx)
 
    yield
 
    popl(:ecx)
    popl(:ebp)
    leal("-4(%ecx)",:esp)
    ret()
    emit(".size","main",".-main")
  end
end