diff options
author | Leo Tenenbaum <pommicket@gmail.com> | 2020-02-25 21:29:04 -0500 |
---|---|---|
committer | Leo Tenenbaum <pommicket@gmail.com> | 2020-02-25 21:29:04 -0500 |
commit | d91b3e813885c6814e5ad1b0f1b1c564213ea95f (patch) | |
tree | 746105d31093d0225e1d76df04566bde6f658d6b | |
parent | 408fa20aee0cd08d0035fdf100f34c34bf3f3729 (diff) |
much better circular dependency checking system for structs
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | eval.c | 112 | ||||
-rw-r--r-- | main.c | 2 | ||||
-rw-r--r-- | parse.c | 1 | ||||
-rw-r--r-- | test.toc | 18 | ||||
-rw-r--r-- | toc.c | 12 | ||||
-rw-r--r-- | types.c | 185 | ||||
-rw-r--r-- | types.h | 10 |
8 files changed, 192 insertions, 150 deletions
@@ -30,7 +30,7 @@ See `docs` for more information (in progress). To compile the compiler on a Unix-y system, just run `./build.sh release`. You can supply a compiler by running `CC=tcc ./build.sh release`, or build it in debug mode without the `release`. To disable compile time foreign function support (which you will need to do if you don't have ffcall/dl), prefix this with `COMPILE_TIME_FOREIGN_FN_SUPPORT=no`. -On other systems, you can just compile main.c with a C compiler. `toc` uses several C99 and a couple of C11 features, so it might not work on all compilers. But it does compile on quite a few, including `clang`, `gcc`, and `tcc`. It can also be compiled as if it were C++, so, MSVC and `g++` can also compile it (it does rely on implicit casting of `void *` though). The *outputted* code should be C99-compliant. +On other systems, you can just compile main.c with a C compiler. `toc` uses several C99 and a couple of C11 features, so it might not work on all compilers. But it does compile on quite a few, including `clang`, `gcc`, and `tcc`. It can also be compiled as if it were C++, so and `g++` can also compile it (it does rely on implicit casting of `void *` though). MSVC can also compile toc. The *outputted* code should be C99-compliant. #### Why it compiles to C @@ -26,116 +26,6 @@ static inline void *evalr_calloc(Evaluator *ev, size_t n, size_t bytes) { return allocr_calloc(ev->allocr, n, bytes); } - -static size_t compiler_sizeof_builtin(BuiltinType b) { - switch (b) { - case BUILTIN_I8: return sizeof(I8); - case BUILTIN_U8: return sizeof(U8); - case BUILTIN_I16: return sizeof(I16); - case BUILTIN_U16: return sizeof(U16); - case BUILTIN_I32: return sizeof(I32); - case BUILTIN_U32: return sizeof(U32); - case BUILTIN_I64: return sizeof(I64); - case BUILTIN_U64: return sizeof(U64); - case BUILTIN_F32: return sizeof(F32); - case BUILTIN_F64: return sizeof(F64); - case BUILTIN_CHAR: return sizeof(char); /* = 1 */ - case BUILTIN_BOOL: return sizeof(bool); - case BUILTIN_TYPE: return sizeof(Type *); - case BUILTIN_NMS: return sizeof(Namespace *); - } - assert(0); - return 0; -} - -/* finds offsets and size */ -static void eval_struct_find_offsets(StructDef *s) { - if (!(s->flags & STRUCT_DEF_FOUND_OFFSETS)) { - size_t bytes = 0; - size_t total_align = 0; - arr_foreach(s->fields, Field, f) { - size_t falign = compiler_alignof(&f->type); - if (falign > total_align) - total_align = falign; - /* align */ - bytes += ((falign - bytes) % falign + falign) % falign; /* = -bytes mod falign */ - assert(bytes % falign == 0); - f->offset = bytes; - /* add size */ - bytes += compiler_sizeof(&f->type); - } - bytes += ((total_align - bytes) % total_align + total_align) % total_align; /* = -bytes mod align */ - s->size = bytes; - s->align = total_align; - s->flags |= STRUCT_DEF_FOUND_OFFSETS; - } -} - -static size_t compiler_alignof(Type *t) { - Value v; - assert(t->flags & TYPE_IS_RESOLVED); - switch (t->kind) { - case TYPE_BUILTIN: - return compiler_sizeof_builtin(t->builtin); - case TYPE_VOID: - return 1; - case TYPE_FN: - return sizeof v.fn; - case TYPE_PTR: - return sizeof v.ptr; - case TYPE_TUPLE: - return sizeof v.tuple; - case TYPE_ARR: - return compiler_alignof(t->arr.of); - case TYPE_SLICE: - if (sizeof(void *) > sizeof(size_t)) - return sizeof(void *); - else - return sizeof(size_t); - case TYPE_STRUCT: { - /* assume the align of a struct is (at most) the greatest align out of its children's */ - eval_struct_find_offsets(t->struc); - return t->struc->align; - } - case TYPE_UNKNOWN: - case TYPE_EXPR: - break; - } - assert(0); - return 0; -} - -/* size of a type at compile time */ -static size_t compiler_sizeof(Type *t) { - Value v; - assert(t->flags & TYPE_IS_RESOLVED); - switch (t->kind) { - case TYPE_BUILTIN: - return compiler_sizeof_builtin(t->builtin); - case TYPE_FN: - return sizeof v.fn; - case TYPE_PTR: - return sizeof v.ptr; - case TYPE_ARR: - return t->arr.n * compiler_sizeof(t->arr.of); - case TYPE_TUPLE: - return sizeof v.tuple; - case TYPE_SLICE: - return sizeof v.slice; - case TYPE_STRUCT: { - eval_struct_find_offsets(t->struc); - return t->struc->size; - } break; - case TYPE_VOID: - case TYPE_UNKNOWN: - return 0; - case TYPE_EXPR: - break; - } - assert(0); - return 0; -} - static bool builtin_truthiness(Value *v, BuiltinType b) { switch (b) { case BUILTIN_I8: return v->i8 != 0; @@ -796,8 +686,6 @@ static bool eval_ptr_to_struct_field(Evaluator *ev, Expression *dot_expr, void * struct_type = struct_type->ptr; } if (struct_type->kind == TYPE_STRUCT) { - eval_struct_find_offsets(struct_type->struc); - Value struc; if (!eval_expr(ev, dot_expr->binary.lhs, &struc)) return NULL; @@ -38,6 +38,8 @@ do we need was_expr? (now that, presumably, we have struct arguments) any odd number of "s for a string make sure futurely/currently-declared types are only used by pointer/slice allow omission of trailing ; in foo ::= fn() {...} or foo ::= nms {...} or foo ::= struct { ... }? +consider- should #sizeof always take a Type? it would be more verbose, but we might not actually need + #sizeof that much, given that we have new. */ @@ -659,6 +659,7 @@ static bool parse_type(Parser *p, Type *type) { Type *ftype = field_decl.type.kind == TYPE_TUPLE ? &field_decl.type.tuple[idx] : &field_decl.type; Field *f = parser_arr_add(p, &struc->fields); f->name = *fident; + f->where = field_decl.where; f->type = *ftype; ++idx; } @@ -2,18 +2,18 @@ // #include "std/io.toc"; // }; -// p ::= struct(x::Type,y::=3) { -// b:int; -// }; - -foo ::= fn(x::int) Type { - [x]int +FOO ::= fn(t::Type) Type { + &ll(int) }; -z ::= fn(a::=,b:foo(a)) { +ll ::= struct(t::Type) { + head: t; + tail: FOO(t); }; main ::= fn() { - n:foo(3); - z(n); + l:ll(int); + m:ll(i16); + x::=#alignof(l); + y::=#alignof(m); };
\ No newline at end of file @@ -33,14 +33,24 @@ #endif #endif -#if __STDC_VERSION__ >= 201112 + +/* use toc_alignof only for non-structs. it may be incorrect for pre-C(++)11. */ +#if __STDC_VERSION__ >= 201112 || __cplusplus >= 201103L +#include <stdalign.h> +#define toc_alignof alignof + #ifdef __GNUC__ /* GCC supports non-string literals as the message for a static assertion */ #define possibly_static_assert(cond) static_assert(cond, "Assertion " #cond " failed.") #else #define possibly_static_assert(cond) static_assert(cond, "Assertion failed") #endif + + #else + +#define toc_alignof sizeof + #define possibly_static_assert(cond) assert(cond) #endif @@ -29,6 +29,149 @@ static inline void typer_block_exit(Typer *tr) { tr->block = *(Block **)arr_last(tr->blocks); } + +static size_t compiler_sizeof_builtin(BuiltinType b) { + switch (b) { + case BUILTIN_I8: return sizeof(I8); + case BUILTIN_U8: return sizeof(U8); + case BUILTIN_I16: return sizeof(I16); + case BUILTIN_U16: return sizeof(U16); + case BUILTIN_I32: return sizeof(I32); + case BUILTIN_U32: return sizeof(U32); + case BUILTIN_I64: return sizeof(I64); + case BUILTIN_U64: return sizeof(U64); + case BUILTIN_F32: return sizeof(F32); + case BUILTIN_F64: return sizeof(F64); + case BUILTIN_CHAR: return sizeof(char); /* = 1 */ + case BUILTIN_BOOL: return sizeof(bool); + case BUILTIN_TYPE: return sizeof(Type *); + case BUILTIN_NMS: return sizeof(Namespace *); + } + assert(0); + return 0; +} +static size_t compiler_alignof_builtin(BuiltinType b) { + switch (b) { + case BUILTIN_I8: return toc_alignof(I8); + case BUILTIN_U8: return toc_alignof(U8); + case BUILTIN_I16: return toc_alignof(I16); + case BUILTIN_U16: return toc_alignof(U16); + case BUILTIN_I32: return toc_alignof(I32); + case BUILTIN_U32: return toc_alignof(U32); + case BUILTIN_I64: return toc_alignof(I64); + case BUILTIN_U64: return toc_alignof(U64); + case BUILTIN_F32: return toc_alignof(F32); + case BUILTIN_F64: return toc_alignof(F64); + case BUILTIN_CHAR: return toc_alignof(char); + case BUILTIN_BOOL: return toc_alignof(bool); + case BUILTIN_TYPE: return toc_alignof(Type *); + case BUILTIN_NMS: return toc_alignof(Namespace *); + } + assert(0); + return 0; +} + +/* finds offsets and size */ +static bool struct_find_offsets(StructDef *s) { + /* assume the align of a struct is the greatest align out of its children's */ + if (!(s->flags & STRUCT_DEF_FOUND_OFFSETS)) { + if (s->flags & STRUCT_DEF_FINDING_OFFSETS) { + err_print(s->where, "Circular dependency in struct!"); + return false; + } + s->flags |= STRUCT_DEF_FINDING_OFFSETS; + size_t bytes = 0; + size_t total_align = 0; + arr_foreach(s->fields, Field, f) { + size_t size = compiler_sizeof(&f->type); + if (size == SIZE_MAX) { + info_print(f->where, "... while descending into this field of a struct."); + return false; + } + size_t falign = compiler_alignof(&f->type); + if (falign > total_align) + total_align = falign; + /* align */ + bytes += ((falign - bytes) % falign + falign) % falign; /* = -bytes mod falign */ + assert(bytes % falign == 0); + f->offset = bytes; + /* add size */ + bytes += size; + } + bytes += ((total_align - bytes) % total_align + total_align) % total_align; /* = -bytes mod align */ + s->size = bytes; + s->align = total_align; + s->flags |= STRUCT_DEF_FOUND_OFFSETS; + } + return true; +} + +static size_t compiler_alignof(Type *t) { + Value v; + assert(t->flags & TYPE_IS_RESOLVED); + switch (t->kind) { + case TYPE_BUILTIN: + return compiler_sizeof_builtin(t->builtin); + case TYPE_VOID: + return 1; + case TYPE_FN: + return toc_alignof v.fn; + case TYPE_PTR: + return toc_alignof v.ptr; + case TYPE_TUPLE: + return toc_alignof v.tuple; + case TYPE_ARR: + return compiler_alignof(t->arr.of); + case TYPE_SLICE: + if (sizeof(void *) > sizeof(size_t)) + return toc_alignof(void *); + else + return toc_alignof(size_t); + case TYPE_STRUCT: + if (!struct_find_offsets(t->struc)) + return SIZE_MAX; + return t->struc->align; + case TYPE_UNKNOWN: + case TYPE_EXPR: + break; + } + assert(0); + return 0; +} + +/* size of a type at compile time */ +static size_t compiler_sizeof(Type *t) { + Value v; + assert(t->flags & TYPE_IS_RESOLVED); + switch (t->kind) { + case TYPE_BUILTIN: + return compiler_sizeof_builtin(t->builtin); + case TYPE_FN: + return sizeof v.fn; + case TYPE_PTR: + return sizeof v.ptr; + case TYPE_ARR: + return t->arr.n * compiler_sizeof(t->arr.of); + case TYPE_TUPLE: + return sizeof v.tuple; + case TYPE_SLICE: + return sizeof v.slice; + case TYPE_STRUCT: { + if (!struct_find_offsets(t->struc)) + return SIZE_MAX; + return t->struc->size; + } break; + case TYPE_VOID: + case TYPE_UNKNOWN: + return 0; + case TYPE_EXPR: + break; + } + assert(0); + return 0; +} + + #define typer_arr_add(tr, a) typer_arr_add_(tr, (void **)(a), sizeof **(a)) static bool type_eq(Type *a, Type *b) { @@ -431,9 +574,7 @@ static bool type_of_ident(Typer *tr, Location where, Identifier *ident, Type *t) err_print(where, "Variables cannot be captured into inner functions (but constants can)."); return false; } - if (arr_len(tr->is_reference_stack) - && *(bool *)arr_last(tr->is_reference_stack) - && (d->flags & DECL_HAS_EXPR) && (d->expr.kind == EXPR_TYPE)) { + if ((d->flags & DECL_HAS_EXPR) && (d->expr.kind == EXPR_TYPE)) { /* allow using a type before declaring it */ t->kind = TYPE_BUILTIN; t->builtin = BUILTIN_TYPE; @@ -519,7 +660,8 @@ static bool type_of_ident(Typer *tr, Location where, Identifier *ident, Type *t) return true; } -static bool type_resolve_(Typer *tr, Type *t, Location where, bool is_reference) { +/* fixes the type (replaces [5+3]int with [8]int, etc.) */ +static bool type_resolve(Typer *tr, Type *t, Location where) { Evaluator *ev = tr->evalr; if (t->flags & TYPE_IS_RESOLVED) return true; t->was_expr = NULL; @@ -554,34 +696,34 @@ static bool type_resolve_(Typer *tr, Type *t, Location where, bool is_reference) size = val_to_u64(&val, n_expr->type.builtin); } t->arr.n = (U64)size; - if (!type_resolve_(tr, t->arr.of, where, is_reference)) + if (!type_resolve(tr, t->arr.of, where)) return false; } break; case TYPE_FN: arr_foreach(t->fn.types, Type, child_type) { - if (!type_resolve_(tr, child_type, where, true)) + if (!type_resolve(tr, child_type, where)) return false; } break; case TYPE_TUPLE: arr_foreach(t->tuple, Type, child_type) { - if (!type_resolve_(tr, child_type, where, is_reference)) + if (!type_resolve(tr, child_type, where)) return false; } break; case TYPE_PTR: - if (!type_resolve_(tr, t->ptr, where, true)) + if (!type_resolve(tr, t->ptr, where)) return false; break; case TYPE_SLICE: - if (!type_resolve_(tr, t->slice, where, true)) + if (!type_resolve(tr, t->slice, where)) return false; break; case TYPE_STRUCT: { if (!(t->struc->flags & STRUCT_DEF_RESOLVED)) { typer_block_enter(tr, &t->struc->scope); arr_foreach(t->struc->fields, Field, f) { - if (!type_resolve_(tr, &f->type, where, is_reference)) { + if (!type_resolve(tr, &f->type, where)) { typer_block_exit(tr); return false; } @@ -594,10 +736,8 @@ static bool type_resolve_(Typer *tr, Type *t, Location where, bool is_reference) } break; case TYPE_EXPR: { Value typeval; - *(bool *)arr_add(&tr->is_reference_stack) = is_reference; - bool success = types_expr(tr, t->expr); - arr_remove_last(&tr->is_reference_stack); - if (!success) return false; + if (!types_expr(tr, t->expr)) + return false; if (t->expr->type.kind == TYPE_UNKNOWN && tr->err_ctx->have_errored) return false; /* silently fail (e.g. if a function couldn't be typed) */ if (!type_is_builtin(&t->expr->type, BUILTIN_TYPE)) { @@ -616,25 +756,27 @@ static bool type_resolve_(Typer *tr, Type *t, Location where, bool is_reference) return false; } } + if (!(t->flags & TYPE_IS_RESOLVED)) { + /* this can happen with functions returning parameterized structs */ + if (!type_resolve(tr, t, where)) + return false; + } t->was_expr = expr; - assert(t->flags & TYPE_IS_RESOLVED); } break; case TYPE_UNKNOWN: case TYPE_VOID: case TYPE_BUILTIN: break; } + if (t->kind == TYPE_STRUCT && !!(t->struc->params) == !!(t->struc->instance_id)) { /* don't want it to try to deal with templates */ + if (!struct_find_offsets(t->struc)) + return false; + } assert(t->kind != TYPE_EXPR); t->flags |= TYPE_IS_RESOLVED; return true; } -/* fixes the type (replaces [5+3]int with [8]int, etc.) */ -static bool type_resolve(Typer *tr, Type *t, Location where) { - return type_resolve_(tr, t, where, false); -} - - static bool type_can_be_truthy(Type *t) { assert(t->flags & TYPE_IS_RESOLVED); @@ -2773,7 +2915,6 @@ static void typer_create(Typer *tr, Evaluator *ev, ErrCtx *err_ctx, Allocator *a tr->in_exprs = NULL; tr->allocr = allocr; tr->globals = idents; - tr->is_reference_stack = NULL; *(Block **)arr_adda(&tr->blocks, allocr) = NULL; } @@ -445,6 +445,7 @@ typedef struct Type { /* field of a struct */ typedef struct Field { + Location where; Identifier name; Type type; size_t offset; /* offset during compile time */ @@ -468,9 +469,10 @@ typedef struct Block { enum { STRUCT_DEF_FOUND_OFFSETS = 0x01, - STRUCT_DEF_CGEN_DECLARED = 0x02, - STRUCT_DEF_CGEN_DEFINED = 0x04, - STRUCT_DEF_RESOLVED = 0x08 + STRUCT_DEF_FINDING_OFFSETS = 0x02, + STRUCT_DEF_CGEN_DECLARED = 0x04, + STRUCT_DEF_CGEN_DEFINED = 0x08, + STRUCT_DEF_RESOLVED = 0x10 }; typedef struct StructDef { Field *fields; @@ -907,8 +909,6 @@ typedef struct Typer { Block **blocks; /* dyn array of all the block's we're in ([0] = NULL for global scope) */ FnExpr *fn; /* the function we're currently parsing. */ ErrCtx *err_ctx; - /* for checking for problematic struct circular dependencies */ - bool *is_reference_stack; ParsedFile *parsed_file; Namespace *nms; } Typer; |