-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathtypecheck.h
386 lines (323 loc) · 9.72 KB
/
typecheck.h
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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
#ifndef TYPECHECK_H
#define TYPECHECK_H
#include "alloc.h"
#include "buf.h"
#include "common.h"
#include "hash_table.h"
template<typename T> using HashTable = __HashTable<T, MEM::TYPECHECK>;
// Sort of like combining a Buf and a HashTable
template<typename T>
struct SerializedHashTable {
HashTable<T> table;
T *serialized;
size_t len;
size_t cap; // For debugging
SerializedHashTable() {
table.reserve(0);
serialized = NULL;
len = 0;
cap = 0;
}
inline bool insert(const char *id, T v) {
if (len == cap) return false;
// Insert into HT
if (table.insert(id, v)) {
// Insert into serialized data
serialized[len] = v;
len = len + 1;
return true;
}
return false;
}
inline void reserve(size_t n) {
if (n == 0) {
return;
}
cap = n;
len = 0;
serialized = (T *) allocate(cap * sizeof(T), MEM::TYPECHECK);
assert(serialized);
table.reserve(cap);
}
inline T find(const char *key) {
return table.find(key);
}
inline T* begin() { return this->serialized; }
inline T* end() { return (this->serialized + len); }
T& operator[](size_t i) { return serialized[i]; }
};
struct IdType;
struct Type;
struct VirtualTable;
struct Method;
// A SerializedHashTable with just hardcoded the primitive types.
struct TypeTable {
Type *undefined_type;
Type *bool_type;
Type *int_type;
Type *int_arr_type;
IdType *main_cls_type;
Method *main_method;
SerializedHashTable<IdType*> type_table;
Buf<IdType*> could_not_be_inserted;
TypeTable() { }
// Note: The type table is initialized with a static
// size, that is the number of type declarations.
// In the most common case, the number of type
// declarations match the number of types used (or
// the latter is less). However, there is the case
// that the user uses an undefined type and then
// it is possible that the hash table does not
// have space. We insert into an auxiliary
// buffer and issue a message in the end of Pass 1.
// Check definition of insert.
void insert(const char *id, IdType* v);
void initialize(size_t n);
size_t len_inserted() const {
return type_table.len;
}
inline IdType* find(const char *key) {
return type_table.find(key);
}
inline IdType** begin() { return type_table.begin(); }
inline IdType** end() { return type_table.end(); }
void compute_and_print_offsets_for_type(IdType *type, size_t start_fields, size_t start_methods, VirtualTable *vtable);
void offset_computation();
};
/* Pass 1 Visitor
*/
struct Goal;
struct MainClass;
struct TypeDeclaration;
struct LocalDeclaration;
struct MethodDeclaration;
struct Typespec;
struct Expression;
struct BinaryExpression;
struct BlockStatement;
struct AssignmentStatement;
struct ArrayAssignmentStatement;
struct IfStatement;
struct WhileStatement;
struct PrintStatement;
struct Local;
struct Method;
struct DeclarationVisitor {
TypeTable type_table;
DeclarationVisitor(size_t ntype_decls);
void visit(Goal *g);
void visit(MainClass *main_class);
void visit(TypeDeclaration *type_decl);
Local* visit(LocalDeclaration *local_decl);
Method* visit(MethodDeclaration *method_decl);
// Helper functions
const char *gen_id();
IdType *id_to_type(const char *id, location_t loc);
Type *typespec_to_type(Typespec tspec, location_t loc);
};
/* Pass 2 Visitor
*/
struct expr_result_t;
struct MainTypeCheckVisitor {
TypeTable type_table;
Method *curr_method;
IdType *curr_class;
MainTypeCheckVisitor(TypeTable ttable) :
type_table(ttable), curr_method(NULL), curr_class(NULL) { }
void visit(Goal *g);
void visit(MainClass *main_class);
void visit(IdType *type);
void visit(Method *method, const char *class_name);
Type* visit(Expression *expr);
// Statements
void visit(BlockStatement *block_stmt);
void visit(AssignmentStatement *asgn_stmt);
void visit(ArrayAssignmentStatement *arr_asgn_stmt);
void visit(IfStatement *if_stmt);
void visit(WhileStatement *while_stmt);
void visit(PrintStatement *print_stmt);
};
/* Types
*/
struct TypeCheckCustomAllocation {
static void *operator new(size_t size) {
return allocate_zero(size, MEM::TYPECHECK);
}
};
enum class TY {
UNDEFINED,
INT,
ARR,
BOOL,
ID,
};
// TODO: It may be a good idea to add a location_t
// field (in all the structs below?) to redirect the user when
// type errors occur for a type.
// For example:
// - Type with id: X was declared again in location: Y
// - Type with id: X does not have field Y, check declaration in: Z
// - In class X, the field with id: Y has been redeclared in Z
struct IdType;
struct Type : public TypeCheckCustomAllocation {
location_t loc;
TY kind;
Type() : kind(TY::UNDEFINED) { }
Type(TY _kind) : kind(_kind) { }
bool is_defined() const { return kind != TY::UNDEFINED; }
const char *name() const;
IdType *is_IdType() {
if (kind == TY::ID) {
return (IdType *) this;
}
return NULL;
};
};
struct LLType {
const char *id;
int num_pointers = 0;
};
enum class LLVALUE {
UNDEFINED,
CONST,
REG,
};
struct llvalue_t {
LLVALUE kind = LLVALUE::UNDEFINED;
LLType ty;
union {
long reg;
int val;
};
};
enum class LOCAL_KIND {
UNDEFINED,
PARAM,
FIELD,
VAR
};
// TODO: Check if `id` fields are actually ever used for Locals
// and Methods
// TODO: We could save the size of arrays if it is known at compile-time
// (i.e. it is constant) and then if we index it with a constant index
// and it is out of bounds, we can issue an error at compile-time. That
// requires a little bit of thinking because we need 4 more bytes in each
// local. Those are not very much, but they will be wasted for all other
// locals. Also, this scenario is not very possible.
struct Local : public TypeCheckCustomAllocation {
const char *id;
Type *type;
union {
// TODO:
// In practice, this wastes bytes. That is, we only need
// either the `reg` or the `val` from an llvalue and know
// either if it is const or not. Note that the latter is
// 1 bit of info, but we're getting from `kind`. And because
// it is an `enum`, it will take 8 bytes. We assume that
// the locals won't be very much, so for now, we can
// live with it.
llvalue_t llval;
// Offset for fields.
size_t offset;
};
size_t index;
// We have to use an `int` instead of LOCAL_KIND to suppress
// stupid warning.
int8_t kind;
Local() : id(NULL) { }
Local(const char *_id, Type *_type) : id(_id), type(_type) { }
size_t offsetof_() const {
// +8 for the virtual pointer.
return offset + 8;
}
};
using Var = Local;
using Field = Local;
using Param = Local;
struct MethodDeclaration;
struct LocalDeclaration;
struct Statement;
struct Expression;
struct Method : public TypeCheckCustomAllocation {
const char *id;
Type *ret_type;
size_t param_len; // To know where params
// end (and vars start).
SerializedHashTable<Local*> locals;
// Offset in the virtual table
size_t offset;
location_t loc;
// Copied from the MethodDeclaration
Buf<Statement*> stmts;
Expression *ret_expr;
Method() = delete;
Method(MethodDeclaration *method_decl);
void accept(MainTypeCheckVisitor *v, const char *class_name) {
v->visit(this, class_name);
}
size_t offsetof_() const {
return offset;
}
static Method *construct_main_method(DeclarationVisitor *visitor, MainClass *main_class);
};
#include <new>
struct IdType : public Type {
const char *id;
SerializedHashTable<Local*> fields;
SerializedHashTable<Method*> methods;
ssize_t vmethods_len = -1;
IdType *parent;
size_t __sizeof;
// Useful only for the virtual table generation
// (during the offset computation)
Array<IdType *, MEM::CHILDREN> *children;
// For type we've not processed yet (so, we know
// only its id). But, clarify that it's undefined.
IdType(const char *_id) : Type(TY::UNDEFINED), id(_id), parent(NULL) {
create_children();
}
// For type we're currently processing.
IdType(const char *_id, size_t nfields, size_t nmethods) :
Type(TY::ID), id(_id), parent(NULL)
{
this->set_sizes(nfields, nmethods);
create_children();
}
void set_sizes(size_t nfields, size_t nmethods) {
kind = TY::ID;
fields.reserve(nfields);
methods.reserve(nmethods);
}
void set_parent(IdType *_parent) {
parent = _parent;
}
void accept(MainTypeCheckVisitor *v) {
v->visit(this);
}
size_t sizeof_() const {
// +8 for the virtual pointer
return __sizeof + 8;
}
void create_children() {
// Every type can have at most 32 children. That can change,
// but it's generally realistic.
constexpr int max_children = 32;
using ArrType = Array<IdType *, MEM::CHILDREN>;
void *mem = allocate(sizeof(ArrType), MEM::CHILDREN);
children = new (mem) ArrType(max_children);
}
void add_child(IdType *child) {
assert(children);
children->push(child);
}
void invalidate_children() {
// We don't need to free, as we will free them all when MEM::VTABLE
// is free'd. We want to invalidate it though so that we don't
// use it accidentally.
children = NULL;
}
};
/* Prototypes
*/
bool typecheck(Goal *goal);
#endif