-
Notifications
You must be signed in to change notification settings - Fork 0
/
uCCompiler.py
298 lines (243 loc) · 10.2 KB
/
uCCompiler.py
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
'''
All projects: Compiler for the uC language.
Subject:
MC921 - Construction of Compilers
Authors:
Victor Ferreira Ferrari - RA 187890
Vinicius Couto Espindola - RA 188115
University of Campinas - UNICAMP - 2020
Last Modified: 09/07/2020.
'''
#!/usr/bin/env python3
# ============================================================
# uCCompiler -- uC (a.k.a. micro C) language compiler
#
# This is the main program for the uC compiler, which just
# parses command-line options, figures out which source files
# to read and write to, and invokes the different stages of
# the compiler proper.
# ============================================================
import sys
import argparse
from contextlib import contextmanager
from uCLexer import uCLexer
from uCParser import uCParser
from uCSemantic import uCSemanticCheck
from uCGenerate import uCIRGenerator
from uCInterpreter import uCIRInterpreter
from uCBlock import uCIRCFG
from uCDFA import uCIRDFA
from uCOptimize import uCIROptimizer
from uCBuild import uCIRBuilder
"""
One of the most important (and difficult) parts of writing a compiler
is reliable reporting of error messages back to the user. This file
defines some generic functionality for dealing with errors throughout
the compiler project. Error handling is based on a subscription/logging
based approach.
To report errors in uc compiler, we use the error() function. For example:
error(lineno, "Some kind of compiler error message")
where lineno is the line number on which the error occurred.
Error handling is based on a subscription based model using context-managers
and the subscribe_errors() function. For example, to route error messages to
standard output, use this:
with subscribe_errors(print):
run_compiler()
To send messages to standard error, you can do this:
import sys
from functools import partial
with subscribe_errors(partial(print, file=sys.stderr)):
run_compiler()
To route messages to a logger, you can do this:
import logging
log = logging.getLogger("somelogger")
with subscribe_errors(log.error):
run_compiler()
To collect error messages for the purpose of unit testing, do this:
errs = []
with subscribe_errors(errs.append):
run_compiler()
# Check errs for specific errors
The utility function errors_reported() returns the total number of
errors reported so far. Different stages of the compiler might use
this to decide whether or not to keep processing or not.
Use clear_errors() to clear the total number of errors.
"""
_subscribers = []
_num_errors = 0
def error(lineno, message, filename=None):
""" Report a compiler error to all subscribers """
global _num_errors
if not filename:
if not lineno:
errmsg = "{}".format(message)
else:
errmsg = "{}: {}".format(lineno, message)
else:
if not lineno:
errmsg = "{}: {}".format(filename, message)
else:
errmsg = "{}:{}: {}".format(filename, lineno, message)
for subscriber in _subscribers:
subscriber(errmsg)
_num_errors += 1
def errors_reported():
""" Return number of errors reported. """
return _num_errors
def clear_errors():
""" Clear the total number of errors reported. """
global _num_errors
_num_errors = 0
@contextmanager
def subscribe_errors(handler):
""" Context manager that allows monitoring of compiler error messages.
Use as follows where handler is a callable taking a single argument
which is the error message string:
with subscribe_errors(handler):
... do compiler ops ...
"""
_subscribers.append(handler)
try:
yield
finally:
_subscribers.remove(handler)
class Compiler:
""" This object encapsulates the compiler and serves as a
facade interface to the 'meat' of the compiler underneath.
"""
def __init__(self, cl_args):
self.code = None
self.total_errors = 0
self.total_warnings = 0
self.args = cl_args
def _parse(self):
""" Parses the source code. If ast_file != None,
prints out the abstract syntax tree.
"""
self.lexer = uCLexer(None)
self.lexer.build()
self.parser = uCParser(self.lexer)
self.parser.build()
self.ast = self.parser.parse(self.code, self.args.debug)
def _sema(self):
""" Decorate AST with semantic actions. If ast_file != None,
prints out the abstract syntax tree. """
try:
self.sema = uCSemanticCheck(self.parser)
self.sema.visit(self.ast)
if not self.args.susy and self.ast_file is not None:
self.ast.show(buf=self.ast_file, showcoord=True)
except AssertionError as e:
error(None, e)
def _codegen(self):
self.gen = uCIRGenerator(self.sema)
self.gen.visit(self.ast)
self.gencode = self.gen.code
self.cfg = uCIRCFG(self.gen)
self.cfg.build_cfg(self.gencode)
if not self.args.susy and self.ir_file is not None:
if self.args.cfg:
self.cfg.view(f=ir_file.name)
else:
self.gen.show(buf=self.ir_file)
def _opt(self):
dfa = uCIRDFA(self.cfg)
self.opt = uCIROptimizer(dfa)
self.opt.optimize(not self.args.debug, True, True, False)
self.optcode = self.opt.code
if not self.args.susy and self.opt_file is not None:
self.opt.show(buf=self.opt_file)
def _llvm(self):
self.llvm = uCIRBuilder(None)
code = self.optcode if self.args.opt else self.gencode
self.llvm.build_ir(code)
if not self.args.susy and self.llvm_file is not None:
self.llvm.show(self.args.cfg, self.llvm_file)
if self.run:
self.llvm.execute_ir(self.args.llvm_opt, self.llvm_opt_file)
def _do_compile(self):
""" Compiles the code to the given source file. """
self._parse()
if not errors_reported():
self._sema()
if not errors_reported():
self._codegen()
if self.args.opt:
self._opt()
if self.args.llvm:
self._llvm()
def compile(self):
""" Compiles the given filename """
if self.args.filename[-3:] == '.uc':
filename = self.args.filename
else:
filename = self.args.filename + '.uc'
open_files = []
self.ast_file = None
if self.args.ast and not self.args.susy:
ast_filename = filename[:-3] + '.ast'
sys.stderr.write("Outputting the AST to %s.\n" % ast_filename)
self.ast_file = open(ast_filename, 'w')
open_files.append(self.ast_file)
self.ir_file = None
if self.args.ir and not self.args.susy:
ir_filename = filename[:-3] + '.ir'
sys.stderr.write("Outputting the uCIR to %s.\n" % ir_filename)
self.ir_file = open(ir_filename, 'w')
open_files.append(self.ir_file)
self.opt_file = None
if self.args.opt and not self.args.susy:
opt_filename = filename[:-3] + '.opt'
sys.stderr.write("Outputting the optimized uCIR to %s.\n" % opt_filename)
self.opt_file = open(opt_filename, 'w')
open_files.append(self.opt_file)
self.llvm_file = None
if self.args.llvm and not self.args.susy:
llvm_filename = filename[:-3] + '.ll'
sys.stderr.write("Outputting the LLVM IR to %s.\n" % llvm_filename)
self.llvm_file = open(llvm_filename, 'w')
open_files.append(self.llvm_file)
self.llvm_opt_file = None
if self.args.llvm_opt and not self.args.susy:
llvm_opt_filename = filename[:-3] + '.opt.ll'
sys.stderr.write("Outputting the optimized LLVM IR to %s.\n" % llvm_opt_filename)
self.llvm_opt_file = open(llvm_opt_filename, 'w')
open_files.append(self.llvm_opt_file)
source = open(filename, 'r')
self.code = source.read()
source.close()
self.run = not self.args.no_run
with subscribe_errors(lambda msg: sys.stderr.write(msg+"\n")):
self._do_compile()
if errors_reported():
sys.stderr.write("{} error(s) encountered.".format(errors_reported()))
elif not self.args.llvm:
if self.args.opt:
speedup = len(self.gencode) / len(self.optcode)
sys.stderr.write("original = %d, optimized = %d, speedup = %.2f\n" %
(len(self.gencode), len(self.optcode), speedup))
if self.run and not self.args.cfg:
vm = uCIRInterpreter(self.gen)
if self.args.opt:
vm.run(self.optcode)
else:
vm.run(self.gencode)
for f in open_files:
f.close()
return 0
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("filename")
parser.add_argument("-s", "--susy", help="run in the susy machine", action='store_true')
parser.add_argument("-a", "--ast", help="dump the AST in the 'filename'.ast", action='store_true')
parser.add_argument("-i", "--ir", help="dump the uCIR in the 'filename'.ir", action='store_true')
parser.add_argument("-n", "--no-run", help="do not execute the program", action='store_true')
parser.add_argument("-c", "--cfg", help="show the CFG for each function in pdf format", action='store_true')
parser.add_argument("-o", "--opt", help="optimize the uCIR with const prop and dce", action='store_true')
parser.add_argument("-d", "--debug", help="print in the stderr some debug informations", action='store_true')
parser.add_argument("-l", "--llvm", help="generate LLVM IR code in the 'filename'.ll", action='store_true')
parser.add_argument("-p", "--llvm-opt", choices=['ctm', 'dce', 'cfg', 'all'],
help="specify which llvm pass optimizations is enabled")
args = parser.parse_args()
retval = Compiler(args).compile()
sys.exit(retval)