harec

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 91f8b24c598103677925cb0633f24cee5ae436ed
parent 485ad4fff22a12f9c5185ec6b738f5ee8c886758
Author: Bor Grošelj Simić <bor.groseljsimic@telemach.net>
Date:   Sun,  5 Dec 2021 03:08:55 +0100

rewrite declaration resolution

See docs/declaration_solver.txt for an overview of the approach.

Signed-off-by: Bor Grošelj Simić <bor.groseljsimic@telemach.net>

Diffstat:
Adocs/declaration_solver.txt | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minclude/check.h | 31++++++++++++++++++++++++++++++-
Minclude/expr.h | 2+-
Minclude/scope.h | 3++-
Minclude/type_store.h | 6++++--
Minclude/types.h | 6++++++
Msrc/check.c | 1005+++++++++++++++++++++++++++++++++----------------------------------------------
Msrc/eval.c | 6+++---
Msrc/gen.c | 3++-
Msrc/type_store.c | 483++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
10 files changed, 827 insertions(+), 787 deletions(-)

diff --git a/docs/declaration_solver.txt b/docs/declaration_solver.txt @@ -0,0 +1,69 @@ +First thing harec does after parsing the inputs is making sure all the global +declarations are valid. A constraint unique to this compilation step is that +unlike with sequential bindings in a compound expression, there is no explicit +order in which the declarations should be traversed to validate them, the +correct traversal order is given by the contents of declarations themselves. To +make things even more complex, interdependent declarations may reside in +different subunits, so there is quite a bit of scope juggling involved to +achieve correct scope shadowing. + +Declarations come in five flavours - functions, constants, type aliases, global +variables and enum fields. + +First, the constants defined upon harec invocation are checked and put into a +dedicated scope. Then the imports for each subunit are loaded. This step requires +the command line defines to already be in place. After the imports of a +subunit are loaded, all of its declarations are marked incomplete and put into +the unit scope. Duplicate declarations are caught and reported at this step. +Special care needs to be taken with enums, by creating a scope for each of them +and inserting all the fields into it in the correct order. + +At this point the defines can be copied from the dedicated scope into the unit +scope, shadowing the declarations from the source files. + +With everything but the declarations in place, the core part of the algorithm +is started: + +For each incomplete declaration: + (1) Save previous enum and subunit context, and load subunit context for + current declaration. + (2) If this declaration is marked as in-progress, error out because the + declaration part of a dependency cycle, otherwise mark this + declaration as in progress. + (3) Disambiguate between different declaration flavours: + - for functions, constants and globals and enum fields: + Check and evaluate as if all the dependencies are already resolved. + For enum fields, the relevant enum context has to be loaded and + implicit values need to be taken care of. + + If an incomplete dependency is encountered along the way, first + resolve that dependency recursively and then continue with + resolution. When the check is done, insert the declaration into the + unit scope. Declaration is now considered complete. + - for type aliases: + Types have two distinct properties that both have to be computed + before a type is complete, their dimensions and their + representation. The approach taken can be summarized as follows: + + (a) Compute secondary type's dimensions by traversing just enough + of its constituent types to be able to do so. If an incomplete + type alias is encountered, first compute that type's dimensions + recursively and then continue. + (b) Insert the complete alias into the type store and into the unit + scope. + (c) Compute secondary type's representation by traversing all of + its constituent types and repeating this entire process on + those that are still incomplete. + + A valid type alias never references itself during (a), because such + a type would not be storable in memory. Types may reference + themselves during (c). Such types are called self-referential + types. Self-references during (c) require no special treatment, + because from step (b) onwards the type we are currently declaring + can be treated as a regular complete type. + (4) Remove the in progress mark + (5) Restore the saved subunit and enum context + +At this point all the incomplete declarations in the unit scope are shadowed by +their complete counterparts. From here on, no special considerations regarding +incomplete declarations apply and check can proceed accordingly. diff --git a/include/check.h b/include/check.h @@ -2,11 +2,11 @@ #define HARE_CHECK_H #include <stdbool.h> #include "identifier.h" +#include "scope.h" #include "types.h" #include "type_store.h" struct expression; -struct scope; #define MODCACHE_BUCKETS 256 @@ -41,6 +41,7 @@ struct context { bool is_test; struct scope *unit; struct scope *scope; + struct scope *resolving_enum; bool deferring; int id; struct errors *errors; @@ -106,6 +107,34 @@ struct unit { struct imports *imports; }; +enum idecl_type { + IDECL_DECL, + IDECL_ENUM, +}; + +// Keeps track of enum specific context required for enum field resolution +struct incomplete_enum_field { + struct ast_enum_field *field; + struct ast_type *type; + struct scope *enum_scope; +}; + +// Keeps track of context required to resolve a declaration or an enum field +// Extends the scope_object struct so it can be inserted into a scope +struct incomplete_declaration { + const struct scope_object obj; + struct scope *imports; // the scope of this declaration's subunit + enum idecl_type type; + bool in_progress; + union { + struct ast_decl decl; + struct incomplete_enum_field *field; + }; +}; + +const struct scope_object *scan_decl_finish(struct context *ctx, + const struct scope_object *obj, struct dimensions *dim); + struct scope *check(struct type_store *ts, bool is_test, struct define *defines, diff --git a/include/expr.h b/include/expr.h @@ -271,7 +271,7 @@ struct expression_measure { enum measure_operator op; union { struct expression *value; - const struct type *type; + struct dimensions dimensions; // TODO: Field selection }; }; diff --git a/include/scope.h b/include/scope.h @@ -9,6 +9,7 @@ enum object_type { O_BIND, O_CONST, O_DECL, + O_SCAN, O_TYPE, }; @@ -52,7 +53,7 @@ struct scope { struct yield *yields; // Linked list in insertion order - // Used for function parameters, where order matters + // Used for function parameters and enum values, where order matters struct scope_object *objects; struct scope_object **next; diff --git a/include/type_store.h b/include/type_store.h @@ -26,6 +26,9 @@ struct ast_type; const struct type *type_store_lookup_atype( struct type_store *store, const struct ast_type *atype); +struct dimensions type_store_lookup_dimensions( + struct type_store *store, const struct ast_type *atype); + const struct type *builtin_type_for_storage( enum type_storage storage, bool is_const); @@ -42,8 +45,7 @@ const struct type *type_store_lookup_slice(struct type_store *store, const struct type *members); const struct type *type_store_lookup_alias(struct type_store *store, - const struct identifier *ident, const struct type *secondary, - bool exported); + const struct type *secondary, bool exported); const struct type *type_store_lookup_tagged(struct type_store *store, struct type_tagged_union *tags); diff --git a/include/types.h b/include/types.h @@ -107,6 +107,7 @@ struct struct_field { char *name; const struct type *type; size_t offset; + size_t size; struct struct_field *next; }; @@ -153,6 +154,11 @@ struct type { }; }; +struct dimensions { + size_t size; + size_t align; +}; + const struct type *type_dereference(const struct type *type); const struct type *type_dealias(const struct type *type); const struct struct_field *type_get_field( diff --git a/src/check.c b/src/check.c @@ -128,17 +128,28 @@ check_expr_access(struct context *ctx, expr->type = EXPR_ACCESS; expr->access.type = aexpr->access.type; - const struct scope_object *obj; + const struct scope_object *obj = NULL; switch (expr->access.type) { case ACCESS_IDENTIFIER: - obj = scope_lookup(ctx->scope, &aexpr->access.ident); - char buf[1024]; - identifier_unparse_static(&aexpr->access.ident, buf, sizeof(buf)); + if (ctx->resolving_enum) { + obj = scope_lookup(ctx->resolving_enum, &aexpr->access.ident); + } + if (!obj) { + obj = scope_lookup(ctx->scope, &aexpr->access.ident); + } if (!obj) { + char buf[1024]; + identifier_unparse_static(&aexpr->access.ident, + buf, sizeof(buf)); error(ctx, aexpr->loc, expr, "Unknown object '%s'", buf); return; } + if (obj->otype == O_SCAN) { + // complete the declaration first + obj = scan_decl_finish(ctx, obj, NULL); + } + switch (obj->otype) { case O_CONST: // Lower constants @@ -159,6 +170,8 @@ check_expr_access(struct context *ctx, expr->type = EXPR_CONSTANT; expr->result = obj->type; break; + case O_SCAN: + assert(0); // handled above } break; case ACCESS_INDEX: @@ -1974,7 +1987,7 @@ check_expr_measure(struct context *ctx, } break; case M_SIZE: - expr->measure.type = type_store_lookup_atype( + expr->measure.dimensions = type_store_lookup_dimensions( ctx->store, aexpr->measure.type); break; case M_OFFSET: @@ -2338,6 +2351,10 @@ check_expr_struct(struct context *ctx, "Unknown type alias"); return; } + + // Do we want globals without type hints? + assert(obj->otype != O_SCAN); + if (obj->otype != O_TYPE) { error(ctx, aexpr->loc, expr, "Name does not refer to a type"); @@ -2868,7 +2885,6 @@ check_const(struct context *ctx, struct declaration *decl = xcalloc(1, sizeof(struct declaration)); const struct scope_object *obj = scope_lookup( ctx->unit, &adecl->ident); - assert(obj && obj->otype == O_CONST); decl->type = DECL_CONST; decl->constant.type = type; decl->constant.value = obj->value; @@ -2892,7 +2908,6 @@ check_function(struct context *ctx, }; const struct type *fntype = type_store_lookup_atype( ctx->store, &fn_atype); - assert(fntype); // Invariant ctx->fntype = fntype; expect(&adecl->loc, @@ -3017,8 +3032,19 @@ check_type(struct context *ctx, decl->type = DECL_TYPE; const struct type *type = type_store_lookup_atype(ctx->store, adecl->type.type); + struct type _alias = { + .storage = STORAGE_ALIAS, + .alias = { + .ident = decl->ident, + .type = type, + .exported = adecl->exported, + }, + .size = type->size, + .align = type->align, + .flags = type->flags, + }; const struct type *alias = - type_store_lookup_alias(ctx->store, &decl->ident, type, decl->exported); + type_store_lookup_alias(ctx->store, &_alias, true); decl->_type = alias; return decl; } @@ -3080,406 +3106,124 @@ check_declarations(struct context *ctx, return next; } -// Set is_static = true if you intend to evaluate this expression and -// statically allocate the result right away. -static bool expr_is_specified(struct context *ctx, - const struct ast_expression *aexpr, bool is_static); - -static bool -type_is_specified(const struct type *type, bool is_static) { - assert(type); - switch (type->storage) { - case STORAGE_BOOL: - case STORAGE_CHAR: - case STORAGE_F32: - case STORAGE_F64: - case STORAGE_FCONST: - case STORAGE_I16: - case STORAGE_I32: - case STORAGE_I64: - case STORAGE_I8: - case STORAGE_ICONST: - case STORAGE_INT: - case STORAGE_NULL: - case STORAGE_RUNE: - case STORAGE_SIZE: - case STORAGE_STRING: - case STORAGE_U16: - case STORAGE_U32: - case STORAGE_U64: - case STORAGE_U8: - case STORAGE_UINT: - case STORAGE_UINTPTR: - case STORAGE_VOID: - return true; - case STORAGE_ALIAS: - if (type->alias.type == NULL) { - return false; - } - return type_is_specified(type->alias.type, is_static); - case STORAGE_ARRAY: - return type_is_specified(type->array.members, is_static); - case STORAGE_SLICE: - if (!is_static) { - return true; - } - return type_is_specified(type->array.members, is_static); - case STORAGE_ENUM: - return true; - case STORAGE_FUNCTION: - for (struct type_func_param *param = type->func.params; - param; param = param->next) { - if (!type_is_specified(param->type, is_static)) { - return false; - } - } - return type_is_specified(type->func.result, is_static); - case STORAGE_POINTER: - return true; - case STORAGE_STRUCT: - case STORAGE_UNION: - for (const struct struct_field *field = - type->struct_union.fields; - field; field = field->next) { - if (!type_is_specified(field->type, is_static)) { - return false; - } - } - return true; - case STORAGE_TAGGED: - for (const struct type_tagged_union *ttype = &type->tagged; - ttype; ttype = ttype->next) { - if (!type_is_specified(ttype->type, is_static)) { - return false; - } - } - return true; - case STORAGE_TUPLE: - for (const struct type_tuple *ttype = &type->tuple; - ttype; ttype = ttype->next) { - if (!type_is_specified(ttype->type, is_static)) { - return false; - } - } - return true; - } - abort(); // Unreachable +static struct incomplete_declaration * +incomplete_declaration_create(struct context *ctx, struct location loc, + struct scope *scope, const struct identifier *ident, + const struct identifier *name) +{ + struct incomplete_declaration *idecl = + (struct incomplete_declaration *)scope_lookup(scope, ident); + if (idecl) { + error(ctx, loc, NULL, "Duplicate global identifier '%s'", + identifier_unparse(ident)); + return idecl; + } + idecl = xcalloc(1, sizeof(struct incomplete_declaration)); + + scope_object_init((struct scope_object *)idecl, O_SCAN, + ident, name, NULL, NULL); + scope_insert_from_object(scope, (struct scope_object *)idecl); + return idecl; } -static bool -atype_is_specified(struct context *ctx, - const struct ast_type *atype, bool is_static) +static void +incomplete_enum_field_create(struct context *ctx, struct scope *imports, + struct scope *enum_scope, struct ast_type *etype, + struct ast_enum_field *f) { - if (!atype) { - return true; - } + // We have to process the last field first + // This way, objects in enum_scope will have lnext pointing to + // the previous element, which is important for implicit enum values. + if (f->next) { + incomplete_enum_field_create(ctx, imports, enum_scope, + etype, f->next); + } + assert(etype->storage == STORAGE_ALIAS); + struct incomplete_enum_field *field = xcalloc(1, sizeof(struct ast_type)); + *field = (struct incomplete_enum_field){ + .field = f, + .type = etype, + .enum_scope = enum_scope, + }; - const struct scope_object *object; - switch (atype->storage) { - case STORAGE_BOOL: - case STORAGE_CHAR: - case STORAGE_F32: - case STORAGE_F64: - case STORAGE_FCONST: - case STORAGE_I16: - case STORAGE_I32: - case STORAGE_I64: - case STORAGE_I8: - case STORAGE_ICONST: - case STORAGE_INT: - case STORAGE_NULL: - case STORAGE_RUNE: - case STORAGE_SIZE: - case STORAGE_STRING: - case STORAGE_U16: - case STORAGE_U32: - case STORAGE_U64: - case STORAGE_U8: - case STORAGE_UINT: - case STORAGE_UINTPTR: - case STORAGE_VOID: - return true; - case STORAGE_ALIAS: - object = scope_lookup(ctx->scope, &atype->alias); - if (object == NULL) { - return false; - } - assert(object->otype == O_TYPE); - const struct type *secondary = object->type; - assert(secondary->storage == STORAGE_ALIAS); - return type_is_specified(secondary->alias.type, is_static); - case STORAGE_ARRAY: - return atype_is_specified(ctx, atype->array.members, is_static) - && expr_is_specified(ctx, atype->array.length, is_static); - case STORAGE_SLICE: - if (!is_static) { - return true; - } - return atype_is_specified(ctx, atype->array.members, is_static); - case STORAGE_ENUM: - for (struct ast_enum_field *field = atype->_enum.values; - field; field = field->next) { - if (!expr_is_specified(ctx, field->value, is_static)) { - return false; - } - } - return true; - case STORAGE_FUNCTION: - for (struct ast_function_parameters *param = atype->func.params; - param; param = param->next) { - if (!atype_is_specified(ctx, param->type, is_static)) { - return false; - } - } - return atype_is_specified(ctx, atype->func.result, is_static); - case STORAGE_POINTER: - return true; - case STORAGE_STRUCT: - case STORAGE_UNION: - for (const struct ast_struct_union_type *stype = - &atype->struct_union; - stype; stype = stype->next) { - if (!expr_is_specified(ctx, stype->offset, is_static)) { - return false; - } - if (!atype_is_specified(ctx, stype->type, is_static)) { - return false; - } - } - return true; - case STORAGE_TAGGED: - for (const struct ast_tagged_union_type *ttype = - &atype->tagged_union; - ttype; ttype = ttype->next) { - if (!atype_is_specified(ctx, ttype->type, is_static)) { - return false; - } - } - return true; - case STORAGE_TUPLE: - for (const struct ast_tuple_type *ttype = &atype->tuple; - ttype; ttype = ttype->next) { - if (!atype_is_specified(ctx, ttype->type, is_static)) { - return false; - } - } - return true; - } - abort(); // Unreachable + struct identifier name = { + .name = strdup(f->name), + }; + struct incomplete_declaration *fld = + incomplete_declaration_create(ctx, etype->loc, enum_scope, + &name, &name); + fld->type = IDECL_ENUM; + fld->imports = imports; + fld->field = field; + + struct identifier name_ns = { + .name = etype->alias.name, + .ns = etype->alias.ns, + }; + struct identifier ident = { + .name = strdup(f->name), + .ns = &name_ns, + }; + struct identifier _ident = {0}; + mkident(ctx, &_ident, &etype->alias); + struct identifier vident = { + .name = strdup(f->name), + .ns = &_ident, + }; + fld = incomplete_declaration_create(ctx, etype->loc, ctx->scope, + &ident, &vident); + fld->type = IDECL_ENUM, + fld->imports = imports, + fld->field = field; + free(name.name); } -static bool -expr_is_specified(struct context *ctx, - const struct ast_expression *aexpr, - bool is_static) +static void +incomplete_types_create(struct context *ctx, struct scope *imp, struct ast_decl *decl) { - if (!aexpr) { - return true; - } - - switch (aexpr->type) { - case EXPR_ACCESS: - switch (aexpr->access.type) { - case ACCESS_IDENTIFIER: - // XXX: Is this right? - //return scope_lookup(ctx->scope, &aexpr->access.ident); - return true; - case ACCESS_INDEX: - return expr_is_specified(ctx, aexpr->access.array, is_static) - && expr_is_specified(ctx, aexpr->access.index, is_static); - case ACCESS_FIELD: - return expr_is_specified(ctx, aexpr->access._struct, is_static); - case ACCESS_TUPLE: - return expr_is_specified(ctx, aexpr->access.tuple, is_static) - && expr_is_specified(ctx, aexpr->access.value, is_static); - } - assert(0); - case EXPR_ALLOC: - return expr_is_specified(ctx, aexpr->alloc.expr, is_static) - && expr_is_specified(ctx, aexpr->alloc.cap, is_static); - case EXPR_APPEND: - for (const struct ast_append_values *value = - aexpr->append.values; - value; value = value->next) { - if (!expr_is_specified(ctx, value->expr, is_static)) { - return false; - } - } - return expr_is_specified(ctx, aexpr->append.expr, is_static) - && expr_is_specified(ctx, aexpr->append.variadic, is_static); - case EXPR_ASSERT: - return expr_is_specified(ctx, aexpr->assert.cond, is_static) - && expr_is_specified(ctx, aexpr->assert.message, is_static); - case EXPR_ASSIGN: - return expr_is_specified(ctx, aexpr->assign.object, is_static) - && expr_is_specified(ctx, aexpr->assign.value, is_static); - case EXPR_BINARITHM: - return expr_is_specified(ctx, aexpr->binarithm.lvalue, is_static) - && expr_is_specified(ctx, aexpr->binarithm.rvalue, is_static); - case EXPR_BINDING: - for (const struct ast_expression_binding *binding = - &aexpr->binding; - binding; binding = binding->next) { - if (!expr_is_specified(ctx, binding->initializer, is_static)) { - return false; - } - if (!atype_is_specified(ctx, binding->type, is_static)) { - return false; - } - } - return true; - case EXPR_COMPOUND: - for (const struct ast_expression_list *list = &aexpr->compound.list; - list; list = list->next) { - if (!expr_is_specified(ctx, list->expr, is_static)) { - return false; - } - } - return true; - case EXPR_BREAK: - case EXPR_CONTINUE: - return true; - case EXPR_CALL: - for (struct ast_call_argument *arg = aexpr->call.args; arg; - arg = arg->next) { - if (!expr_is_specified(ctx, arg->value, is_static)) { - return false; - } - } - return expr_is_specified(ctx, aexpr->call.lvalue, is_static); - case EXPR_CAST: - return expr_is_specified(ctx, aexpr->cast.value, is_static) - && atype_is_specified(ctx, aexpr->cast.type, is_static); - case EXPR_CONSTANT: - if (aexpr->constant.storage == STORAGE_ARRAY) { - for (struct ast_array_constant *aconst = - aexpr->constant.array; - aconst; aconst = aconst->next) { - if (!expr_is_specified(ctx, aconst->value, is_static)) { - return false; - } - } - } - return true; - case EXPR_DEFER: - return expr_is_specified(ctx, aexpr->defer.deferred, is_static); - case EXPR_DELETE: - return expr_is_specified(ctx, aexpr->delete.expr, is_static); - case EXPR_FOR: - return expr_is_specified(ctx, aexpr->_for.bindings, is_static) - && expr_is_specified(ctx, aexpr->_for.cond, is_static) - && expr_is_specified(ctx, aexpr->_for.afterthought, is_static) - && expr_is_specified(ctx, aexpr->_for.body, is_static); - case EXPR_FREE: - return expr_is_specified(ctx, aexpr->free.expr, is_static); - case EXPR_IF: - return expr_is_specified(ctx, aexpr->_if.cond, is_static) - && expr_is_specified(ctx, aexpr->_if.true_branch, is_static) - && expr_is_specified(ctx, aexpr->_if.false_branch, is_static); - case EXPR_INSERT: - assert(0); // TODO - case EXPR_MATCH: - for (struct ast_match_case *mcase = aexpr->match.cases; mcase; - mcase = mcase->next) { - if (!atype_is_specified(ctx, mcase->type, is_static)) { - return false; - } - for (const struct ast_expression_list *list = &mcase->exprs; - list; list = list->next) { - if (!expr_is_specified(ctx, list->expr, is_static)) { - return false; - } - } - } - return expr_is_specified(ctx, aexpr->match.value, is_static); - case EXPR_MEASURE: - switch (aexpr->measure.op) { - case M_LEN: - return expr_is_specified(ctx, aexpr->measure.value, is_static); - case M_SIZE: - return atype_is_specified(ctx, aexpr->measure.type, is_static); - case M_OFFSET: - return expr_is_specified(ctx, aexpr->measure.value, is_static); - } - assert(0); - case EXPR_PROPAGATE: - return expr_is_specified(ctx, aexpr->propagate.value, is_static); - case EXPR_RETURN: - return expr_is_specified(ctx, aexpr->_return.value, is_static); - case EXPR_SLICE: - return expr_is_specified(ctx, aexpr->slice.object, is_static) - && expr_is_specified(ctx, aexpr->slice.start, is_static) - && expr_is_specified(ctx, aexpr->slice.end, is_static); - case EXPR_STRUCT: - for (struct ast_field_value *field = aexpr->_struct.fields; - field; field = field->next) { - if (field->type && !atype_is_specified(ctx, field->type, is_static)) { - return false; - } - if (!expr_is_specified(ctx, field->initializer, is_static)) { - return false; - } - } - if (aexpr->_struct.type.name) { - if (!scope_lookup(ctx->scope, &aexpr->_struct.type)) { - return false; - } - } - return true; - case EXPR_SWITCH: - for (struct ast_switch_case *scase = aexpr->_switch.cases; - scase; scase =scase->next) { - for (struct ast_case_option *opt = scase->options; - opt; opt = opt->next) { - if (!expr_is_specified(ctx, opt->value, is_static)) { - return false; - } - } - for (const struct ast_expression_list *list = &scase->exprs; - list; list = list->next) { - if (!expr_is_specified(ctx, list->expr, is_static)) { - return false; - } - } - } - return expr_is_specified(ctx, aexpr->_switch.value, is_static); - case EXPR_TUPLE: - for (const struct ast_expression_tuple *tuple = &aexpr->tuple; - tuple; tuple = tuple->next) { - if (!expr_is_specified(ctx, tuple->expr, is_static)) { - return false; - } + for (struct ast_type_decl *t = &decl->type; t; t = t->next) { + struct identifier with_ns = {0}; + mkident(ctx, &with_ns, &t->ident); + struct incomplete_declaration *idecl = + incomplete_declaration_create(ctx, decl->loc, ctx->scope, + &with_ns, &t->ident); + idecl->type = IDECL_DECL; + idecl->decl = (struct ast_decl){ + .decl_type = AST_DECL_TYPE, + .loc = decl->loc, + .type = *t, + .exported = decl->exported, + }; + idecl->imports = imp; + if (t->type->storage == STORAGE_ENUM) { + struct ast_type *etype = xcalloc(1, sizeof(struct ast_type)); + *etype = (struct ast_type){ + .loc = t->type->loc, + .storage = STORAGE_ALIAS, + .flags = t->type->flags, + .unwrap = false, + }; + identifier_dup(&etype->alias, &t->ident); + struct scope *enum_scope = NULL; + scope_push(&enum_scope, SCOPE_ENUM); + incomplete_enum_field_create(ctx, imp, enum_scope, + etype, t->type->_enum.values); + scope_push(&enum_scope, SCOPE_ENUM); } - return true; - case EXPR_UNARITHM: - return expr_is_specified(ctx, aexpr->unarithm.operand, is_static); - case EXPR_YIELD: - return expr_is_specified(ctx, aexpr->control.value, is_static); } - assert(0); // Unreachable } -static bool +const struct scope_object * scan_const(struct context *ctx, const struct ast_global_decl *decl) { - // TODO: Get rid of this once the type store bubbles up errors - for (const struct ast_global_decl *d = decl; d; d = d->next) { - if (!atype_is_specified(ctx, d->type, true) - || !expr_is_specified(ctx, d->init, true)) { - return false; - } - } assert(!decl->symbol); // Invariant const struct type *type = type_store_lookup_atype( ctx->store, decl->type); + expect(&decl->type->loc, type != NULL, "Unable to resolve type"); // TODO: Free the initializer - struct expression *initializer = - xcalloc(1, sizeof(struct expression)); + struct expression *initializer = xcalloc(1, sizeof(struct expression)); check_expression(ctx, decl->init, initializer, type); - if (ctx->errors != NULL) { - return false; - } expect(&decl->init->loc, type_is_assignable(type, initializer->result), "Constant type is not assignable from initializer type"); @@ -3493,29 +3237,20 @@ scan_const(struct context *ctx, const struct ast_global_decl *decl) struct identifier ident = {0}; mkident(ctx, &ident, &decl->ident); - scope_insert(ctx->unit, O_CONST, &ident, &decl->ident, type, value); - return true; + return scope_insert(ctx->unit, O_CONST, &ident, + &decl->ident, type, value); } -static bool +const struct scope_object * scan_function(struct context *ctx, const struct ast_function_decl *decl) { - if ((decl->flags & FN_TEST) && !ctx->is_test) { - return true; - } const struct ast_type fn_atype = { .storage = STORAGE_FUNCTION, .flags = TYPE_CONST, .func = decl->prototype, }; - // TODO: Get rid of this once the type store bubbles up errors - // TODO: Do we want to do something on !expr_is_specified(ctx, decl->body)? - if (!atype_is_specified(ctx, &fn_atype, false)) { - return false; - } const struct type *fntype = type_store_lookup_atype( ctx->store, &fn_atype); - assert(fntype); if (!(decl->flags & FN_TEST)) { struct identifier ident = {0}; @@ -3526,26 +3261,17 @@ scan_function(struct context *ctx, const struct ast_function_decl *decl) } else { ident = decl->ident; } - scope_insert(ctx->unit, O_DECL, &ident, &decl->ident, fntype, NULL); + return scope_insert(ctx->unit, O_DECL, &ident, + &decl->ident, fntype, NULL); } - - char buf[1024]; - identifier_unparse_static(&decl->ident, buf, sizeof(buf)); - return true; + return NULL; } -static bool +const struct scope_object * scan_global(struct context *ctx, const struct ast_global_decl *decl) { - // TODO: Get rid of this once the type store bubbles up errors - if (!atype_is_specified(ctx, decl->type, true) - || !expr_is_specified(ctx, decl->init, true)) { - return false; - } - const struct type *type = type_store_lookup_atype( ctx->store, decl->type); - assert(type); if (decl->type->storage == STORAGE_ARRAY && decl->type->array.contextual) { @@ -3553,9 +3279,6 @@ scan_global(struct context *ctx, const struct ast_global_decl *decl) struct expression *initializer = xcalloc(1, sizeof(struct expression)); check_expression(ctx, decl->init, initializer, type); - if (ctx->errors != NULL) { - return false; - } expect(&decl->init->loc, initializer->result->storage == STORAGE_ARRAY, "Cannot infer array length from non-array type"); @@ -3571,140 +3294,267 @@ scan_global(struct context *ctx, const struct ast_global_decl *decl) } else { mkident(ctx, &ident, &decl->ident); } - scope_insert(ctx->unit, O_DECL, &ident, &decl->ident, type, NULL); - return true; + return scope_insert(ctx->unit, O_DECL, &ident, &decl->ident, type, NULL); } -static bool -scan_type(struct context *ctx, const struct ast_type_decl *decl, bool exported) +const struct scope_object * +scan_enum_field(struct context *ctx, struct incomplete_declaration *idecl) { - // TODO: Get rid of this once the type store bubbles up errors - if (!atype_is_specified(ctx, decl->type, false)) { - return false; + assert(ctx->resolving_enum == NULL); + assert(idecl->type == IDECL_ENUM); + + struct identifier name = { + .name = idecl->obj.ident.name + }; + struct identifier ident = idecl->obj.ident, vident = idecl->obj.name; + + idecl = (struct incomplete_declaration *)scope_lookup( + idecl->field->enum_scope, &name); + if (idecl->obj.otype != O_SCAN) { + return &idecl->obj; } + + ctx->resolving_enum = idecl->field->enum_scope; + // TODO set this type's contents to the correct builtin type const struct type *type = - type_store_lookup_atype(ctx->store, decl->type); + type_store_lookup_atype(ctx->store, idecl->field->type); - struct identifier ident = {0}; - if (!decl->ident.ns) { - mkident(ctx, &ident, &decl->ident); - } else { - ident = decl->ident; - } + struct expression *value = + xcalloc(1, sizeof(struct expression)); + value->result = type; + if (idecl->field->field->value) { // explicit value + // TODO: negative values in unsigned enums, too big values in + // signed enums + const struct type *builtin = builtin_type_for_storage( + type->alias.type->_enum.storage, false); - const struct type *alias = - type_store_lookup_alias(ctx->store, &ident, type, exported); - scope_insert(ctx->unit, O_TYPE, &ident, &decl->ident, alias, NULL); - - if (type->storage == STORAGE_ENUM) { - for (struct type_enum_value *value = type->_enum.values; value; - value = value->next) { - struct ast_type atype = { - .loc = decl->type->loc, - .storage = STORAGE_ALIAS, - .flags = 0, - .unwrap = false, - .alias = decl->ident, - }; - const struct type *alias = - type_store_lookup_atype(ctx->store, &atype); + struct expression *initializer = + xcalloc(1, sizeof(struct expression)); + check_expression(ctx, idecl->field->field->value, + initializer, builtin); - struct expression *expr = - xcalloc(sizeof(struct expression), 1); - expr->type = EXPR_CONSTANT; - expr->result = alias; - if (type_is_signed(alias)) { - expr->constant.ival = value->ival; + handle_errors(ctx->errors); + expect(&idecl->field->field->value->loc, + type_is_assignable(builtin, initializer->result), + "Enum value type is not assignable from initializer type"); + + initializer = lower_implicit_cast(type, initializer); + enum eval_result r = eval_expr(ctx, initializer, value); + expect(&idecl->field->field->value->loc, r == EVAL_OK, + "Unable to evaluate constant initializer at compile time"); + } else { // implicit value + const struct scope_object *obj = NULL; + // find previous enum value + if (idecl->obj.lnext && idecl->obj.lnext->otype == O_SCAN) { + obj = scope_lookup(idecl->field->enum_scope, + &idecl->obj.lnext->name); + if (obj->otype == O_SCAN) { + // complete previous value first + obj = scan_decl_finish(ctx, obj, NULL); + } + } + value->type = EXPR_CONSTANT; + if (type_is_signed(type_dealias(type))) { + if (obj == NULL) { + value->constant.ival = 0; } else { - expr->constant.uval = value->uval; + value->constant.ival = obj->value->constant.ival + 1; + } + } else { + if (obj == NULL) { + value->constant.ival = 0; + } else { + value->constant.uval = obj->value->constant.uval + 1; } - - struct identifier name_ns = { - .name = decl->ident.name, - .ns = decl->ident.ns, - }; - struct identifier name = { - .name = value->name, - .ns = &name_ns, - }; - struct identifier vident = { - .name = value->name, - .ns = &ident, - }; - scope_insert(ctx->unit, O_CONST, &name, &vident, alias, expr); } } - return true; + ctx->resolving_enum = NULL; + + // TODO: allow specifying values by their long name + scope_insert(idecl->field->enum_scope, O_CONST, &name, &name, type, value); + return scope_insert(ctx->scope, O_CONST, &ident, &vident, type, value); } -static bool -scan_declaration(struct context *ctx, const struct ast_decl *decl) +static const struct scope_object * +scan_type(struct context *ctx, struct ast_type_decl *decl, bool exported, + struct dimensions dim) +{ + struct identifier ident = {0}; + mkident(ctx, &ident, &decl->ident); + + struct type _alias = { + .storage = STORAGE_ALIAS, + .alias = { + .ident = ident, + .type = NULL, + .exported = exported, + }, + .size = dim.size, + .align = dim.align, + .flags = decl->type->flags, + }; + + const struct type *alias = type_store_lookup_alias(ctx->store, &_alias, true); + const struct scope_object *ret = + scope_insert(ctx->scope, O_TYPE, &ident, &decl->ident, alias, NULL); + ((struct type *)ret->type)->alias.type = + type_store_lookup_atype(ctx->store, decl->type); + return ret; +} + +static void +scan_decl_start(struct context *ctx, struct scope *imports, struct ast_decl *decl) { switch (decl->decl_type) { case AST_DECL_CONST: - for (const struct ast_global_decl *c = &decl->constant; c; - c = c->next) { - if (!scan_const(ctx, c)) { - return false; - } + for (struct ast_global_decl *g = &decl->constant; g; g = g->next) { + struct identifier with_ns = {0}; + mkident(ctx, &with_ns, &g->ident); + struct incomplete_declaration *idecl = + incomplete_declaration_create(ctx, decl->loc, + ctx->scope, &with_ns, &g->ident); + idecl->type = IDECL_DECL; + idecl->decl = (struct ast_decl){ + .decl_type = AST_DECL_CONST, + .loc = decl->loc, + .constant = *g, + .exported = decl->exported, + }; + idecl->imports = imports; } - return true; - case AST_DECL_FUNC: - return scan_function(ctx, &decl->function); + break; case AST_DECL_GLOBAL: - for (const struct ast_global_decl *g = &decl->global; g; - g = g->next) { - if (!scan_global(ctx, g)) { - return false; + for (struct ast_global_decl *g = &decl->global; g; g = g->next) { + struct identifier with_ns = {0}; + mkident(ctx, &with_ns, &g->ident); + struct incomplete_declaration *idecl = + incomplete_declaration_create(ctx, decl->loc, + ctx->scope, &with_ns, &g->ident); + idecl->type = IDECL_DECL; + idecl->decl = (struct ast_decl){ + .decl_type = AST_DECL_GLOBAL, + .loc = decl->loc, + .global = *g, + .exported = decl->exported, + }; + idecl->imports = imports; + } + break; + case AST_DECL_FUNC: + if ((decl->function.flags & FN_TEST)) { + if (ctx->is_test) { + scan_function(ctx, &decl->function); } + return; } - return true; + struct ast_function_decl *func = &decl->function; + struct identifier with_ns = {0}; + mkident(ctx, &with_ns, &func->ident); + struct incomplete_declaration *idecl = + incomplete_declaration_create(ctx, decl->loc, + ctx->scope, &with_ns, &func->ident); + idecl->type = IDECL_DECL; + idecl->decl = (struct ast_decl){ + .decl_type = AST_DECL_FUNC, + .loc = decl->loc, + .function = *func, + .exported = decl->exported, + }; + idecl->imports = imports; + break; case AST_DECL_TYPE: - return scan_type(ctx, &decl->type, decl->exported); + incomplete_types_create(ctx, imports, decl); + break; } - assert(0); // Unreachable } -static struct ast_decls * -scan_declarations(struct context *ctx, const struct ast_decls *decls) +const struct scope_object * +scan_decl_finish(struct context *ctx, const struct scope_object *obj, + struct dimensions *dim) { - struct ast_decls *next = NULL; - bool found = false; - while (decls || next) { - if (!decls) { - if (!found) { - return next; - } - decls = next; - next = NULL; - found = false; - } - if (scan_declaration(ctx, &decls->decl)) { - found = true; + // save current subunit and enum context + struct scope *enum_scope = ctx->resolving_enum; + ctx->resolving_enum = NULL; + struct scope *subunit = ctx->unit->parent; + ctx->unit->parent = NULL; + + // ensure this declaration wasn't already scanned + struct incomplete_declaration *idecl = (struct incomplete_declaration *)obj; + obj = scope_lookup(ctx->scope, &idecl->obj.ident); + if (obj && obj->otype != O_SCAN) { + goto exit; + } + + // load this declaration's subunit context + ctx->unit->parent = idecl->imports; + + // TODO handle circular enum dependencies + if (idecl->type == IDECL_ENUM) { + obj = scan_enum_field(ctx, idecl); + goto exit; + } + + // resolving a declaration that is already in progress -> cycle + expect(&idecl->decl.loc, !idecl->in_progress, + "Circular dependency for '%s'\n", + identifier_unparse(&obj->ident)); + idecl->in_progress = true; + + switch (idecl->decl.decl_type) { + case AST_DECL_CONST: + obj = scan_const(ctx, &idecl->decl.constant); + break; + case AST_DECL_GLOBAL: + obj = scan_global(ctx, &idecl->decl.global); + break; + case AST_DECL_FUNC: + obj = scan_function(ctx, &idecl->decl.function); + break; + case AST_DECL_TYPE: + if (dim) { + // the caller is only interested in this type's dimensions + *dim = type_store_lookup_dimensions(ctx->store, + idecl->decl.type.type); + obj = NULL; } else { - // TODO: Free this - struct ast_decls *cur = - xcalloc(sizeof(struct ast_decls), 1); - memcpy(cur, decls, sizeof(struct ast_decls)); - cur->next = next; - next = cur; + // 1. compute type dimensions + struct dimensions _dim = + type_store_lookup_dimensions(ctx->store, + idecl->decl.type.type); + + handle_errors(ctx->errors); + idecl->in_progress = false; + + // 2. compute type representation and store it + obj = scan_type(ctx, &idecl->decl.type, + idecl->decl.exported, _dim); } - decls = decls->next; + break; } - return next; +exit: + // load stored context + ctx->unit->parent = subunit; + ctx->resolving_enum = enum_scope; + idecl->in_progress = false; + return obj; } static void load_import(struct context *ctx, struct ast_imports *import, struct type_store *ts, struct scope *scope) { + struct context *old_ctx = ctx->store->check_context; struct scope *mod = module_resolve(ctx->modcache, ctx->defines, &import->ident, ts); + ctx->store->check_context = old_ctx; switch (import->mode) { case AST_IMPORT_IDENTIFIER: for (struct scope_object *obj = mod->objects; obj; obj = obj->lnext) { + if (obj->otype == O_SCAN) { + continue; + } scope_insert(scope, obj->otype, &obj->ident, &obj->name, obj->type, obj->value); if (obj->name.ns && obj->name.ns->ns) { @@ -3733,6 +3583,9 @@ load_import(struct context *ctx, struct ast_imports *import, case AST_IMPORT_ALIAS: for (struct scope_object *obj = mod->objects; obj; obj = obj->lnext) { + if (obj->otype == O_SCAN) { + continue; + } struct identifier ns = { .name = obj->name.ns->name, .ns = import->alias, @@ -3772,6 +3625,9 @@ load_import(struct context *ctx, struct ast_imports *import, }; for (struct scope_object *o = mod->objects; o; o = o->lnext) { + if (obj->otype == O_SCAN) { + continue; + } if (!identifier_eq(o->name.ns, &ident)) { continue; }; @@ -3801,9 +3657,9 @@ check_internal(struct type_store *ts, ctx.is_test = is_test; ctx.store = ts; ctx.store->check_context = &ctx; - ctx.modcache = cache; ctx.defines = defines; ctx.next = &ctx.errors; + ctx.modcache = cache; // Top-level scope management involves: // @@ -3813,9 +3669,11 @@ check_internal(struct type_store *ts, // // Further down the call frame, subsequent functions will create // sub-scopes for each declaration, expression-list, etc. - ctx.unit = scope_push(&ctx.scope, SCOPE_UNIT); - // Install defines (-D on the command line) + struct scope *def_scope = NULL; + scope_push(&def_scope, SCOPE_UNIT); + + // Put defines into a temporary scope (-D on the command line) // XXX: This duplicates a lot of code with scan_const for (struct define *def = defines; def; def = def->next) { struct location loc = { @@ -3837,29 +3695,29 @@ check_internal(struct type_store *ts, enum eval_result r = eval_expr(&ctx, initializer, value); expect(&loc, r == EVAL_OK, "Unable to evaluate constant initializer at compile time"); - scope_insert(ctx.unit, O_CONST, + scope_insert(def_scope, O_CONST, &def->ident, &def->ident, type, value); } - - struct scopes *subunit_scopes; + struct scopes *subunit_scopes = NULL; struct scopes **next = &subunit_scopes; + struct scope *su_scope = NULL; struct imports **inext = &unit->imports; - struct unresolveds { - struct scope *scope; - const struct ast_decls *unresolved; - struct unresolveds *next; - }; - struct unresolveds *cur = NULL, *unresolved = NULL; + ctx.scope = NULL; + ctx.unit = scope_push(&ctx.scope, SCOPE_UNIT); - // First pass populates the imports + // Populate the imports and put declarations into a scope. + // Each declaration holds a reference to its subunit's imports + // A scope gets us: + // a) duplicate detection for free + // b) a way to find declaration's definition when it's refered to for (const struct ast_subunit *su = &aunit->subunits; su; su = su->next) { - scope_push(&ctx.scope, SCOPE_SUBUNIT); - + su_scope = NULL; + scope_push(&su_scope, SCOPE_SUBUNIT); for (struct ast_imports *imports = su->imports; imports; imports = imports->next) { - load_import(&ctx, imports, ts, ctx.scope); + load_import(&ctx, imports, ts, su_scope); bool found = false; for (struct imports *uimports = unit->imports; @@ -3877,89 +3735,50 @@ check_internal(struct type_store *ts, } } - struct unresolveds *new = - xcalloc(sizeof(struct unresolveds), 1); - new->unresolved = su->decls; - new->next = cur; - cur = new; + for (struct ast_decls *d = su->decls; d; d = d->next) { + scan_decl_start(&ctx, su_scope, &d->decl); + }; *next = xcalloc(1, sizeof(struct scopes)); - new->scope = (*next)->scope = scope_pop(&ctx.scope); + (*next)->scope = su_scope; next = &(*next)->next; } - // Second pass populates the type graph - bool found = false; - while (cur || unresolved) { - if (!cur) { - if (!found) { - handle_errors(ctx.errors); - error(&ctx, unresolved->unresolved->decl.loc, NULL, - "Undefined identifier used in declaration"); - handle_errors(ctx.errors); - } - cur = unresolved; - unresolved = NULL; - found = false; - } - struct errors *error = ctx.errors; - while (error) { - struct errors *next = error->next; - free(error); - error = next; - } - ctx.errors = NULL; - ctx.next = &ctx.errors; - ctx.scope = cur->scope; - ctx.store->check_context = &ctx; - struct ast_decls *left = - scan_declarations(&ctx, cur->unresolved); - if (left) { - struct unresolveds *new = - xcalloc(sizeof(struct unresolveds), 1); - new->scope = cur->scope; - new->unresolved = left; - new->next = unresolved; - unresolved = new; - } - - size_t old_len = 0, new_len = 0; - for (const struct ast_decls *old = cur->unresolved; old; - old = old->next) { - old_len++; - } - for (struct ast_decls *new = left; new; - new = new->next) { - new_len++; - } - if (new_len < old_len) { - found = true; - } + // Put defines into unit scope + // We have to insert them *after* declarations, because this way they + // shadow declarations, not the other way around + // + // XXX: shadowed declarations are not checked for consistency + for (const struct scope_object *obj = def_scope->objects; + obj; obj = obj->lnext) { + scope_insert(ctx.unit, O_CONST, &obj->ident, &obj->name, + obj->type, obj->value); + } + scope_free(def_scope); - struct unresolveds *tmp = cur; - cur = tmp->next; - free(tmp); + // Perform actual declaration resolution + ctx.resolving_enum = NULL; + for (const struct scope_object *obj = ctx.scope->objects; + obj && obj->otype == O_SCAN; obj = obj->lnext) { + scan_decl_finish(&ctx, obj, NULL); + assert(ctx.resolving_enum == NULL); } + handle_errors(ctx.errors); + if (scan_only) { + ctx.store->check_context = NULL; + ctx.unit->parent = NULL; return ctx.unit; } - struct errors *error = ctx.errors; - while (error) { - struct errors *next = error->next; - free(error); - error = next; - } - ctx.errors = NULL; - ctx.next = &ctx.errors; - - // Third pass populates the expression graph + // Populate the expression graph struct scopes *scope = subunit_scopes; struct declarations **next_decl = &unit->declarations; for (const struct ast_subunit *su = &aunit->subunits; su; su = su->next) { - ctx.scope = scope->scope; + // subunit scope has to be *behind* unit scope + ctx.scope->parent = scope->scope; next_decl = check_declarations(&ctx, su->decls, next_decl); scope = scope->next; } @@ -3969,6 +3788,8 @@ check_internal(struct type_store *ts, exit(EXIT_FAILURE); } + ctx.store->check_context = NULL; + ctx.unit->parent = NULL; return ctx.unit; } diff --git a/src/eval.c b/src/eval.c @@ -378,7 +378,7 @@ eval_cast(struct context *ctx, struct expression *in, struct expression *out) // XXX: We should also be able to handle expressions which use // symbols/identifiers out->type = EXPR_CONSTANT; - out->result = to; + out->result = in->result; const struct type *subtype; switch (to->storage) { @@ -391,6 +391,7 @@ eval_cast(struct context *ctx, struct expression *in, struct expression *out) || from->storage == STORAGE_UINTPTR); out->constant.uval = val.constant.uval; return EVAL_OK; + case STORAGE_ENUM: case STORAGE_I16: case STORAGE_I32: case STORAGE_I64: @@ -445,7 +446,6 @@ eval_cast(struct context *ctx, struct expression *in, struct expression *out) } return EVAL_OK; case STORAGE_CHAR: - case STORAGE_ENUM: case STORAGE_NULL: case STORAGE_ALIAS: assert(0); // Handled above @@ -473,7 +473,7 @@ eval_measurement(struct context *ctx, struct expression *in, struct expression * case M_LEN: assert(0); // TODO case M_SIZE: - out->constant.uval = in->measure.type->size; + out->constant.uval = in->measure.dimensions.size; return EVAL_OK; case M_OFFSET: if (in->measure.value->access.type == ACCESS_FIELD) { diff --git a/src/gen.c b/src/gen.c @@ -245,6 +245,7 @@ gen_access_ident(struct gen_context *ctx, const struct expression *expr) }; case O_CONST: case O_TYPE: + case O_SCAN: abort(); // Invariant } abort(); // Invariant @@ -2232,7 +2233,7 @@ gen_expr_measure(struct gen_context *ctx, const struct expression *expr) return (struct gen_value){ .kind = GV_CONST, .type = &builtin_type_size, - .lval = expr->measure.type->size, + .lval = expr->measure.dimensions.size, }; case M_OFFSET: if (expr->measure.value->access.type == ACCESS_FIELD) { diff --git a/src/type_store.c b/src/type_store.c @@ -10,6 +10,17 @@ #include "type_store.h" #include "util.h" +// XXX: This needs to be updated on updates to type_flags (types.h) +const unsigned int typeflags[] = { + 0, + TYPE_CONST, + TYPE_ERROR, + TYPE_ERROR | TYPE_CONST, +}; + +static struct dimensions _type_store_lookup_atype(struct type_store *store, + const struct type **type, const struct ast_type *atype); + static void error(struct context *ctx, const struct location loc, char *fmt, ...) { @@ -28,17 +39,6 @@ error(struct context *ctx, const struct location loc, char *fmt, ...) ctx->next = &next->next; } -static char * -gen_typename(const struct type *type) -{ - size_t sz = 0; - char *ptr = NULL; - FILE *f = open_memstream(&ptr, &sz); - emit_type(type, f); - fclose(f); - return ptr; -} - static size_t ast_array_len(struct type_store *store, const struct ast_type *atype) { @@ -131,16 +131,6 @@ builtin_type_for_storage(enum type_storage storage, bool is_const) } static const struct type * -builtin_for_atype(const struct ast_type *atype) -{ - if (atype->flags & TYPE_ERROR) { - return NULL; - } - bool is_const = (atype->flags & TYPE_CONST) != 0; - return builtin_type_for_storage(atype->storage, is_const); -} - -static const struct type * builtin_for_type(const struct type *type) { if (type->flags & TYPE_ERROR) { @@ -153,7 +143,7 @@ builtin_for_type(const struct type *type) static struct struct_field * struct_insert_field(struct type_store *store, struct struct_field **fields, enum type_storage storage, size_t *size, size_t *usize, size_t *align, - const struct ast_struct_union_type *atype, bool *ccompat) + const struct ast_struct_union_type *atype, bool *ccompat, bool size_only) { while (*fields && (!atype->name || !(*fields)->name || strcmp((*fields)->name, atype->name) < 0)) { fields = &(*fields)->next; @@ -164,6 +154,7 @@ struct_insert_field(struct type_store *store, struct struct_field **fields, "Duplicate struct/union member '%s'", atype->name); return NULL; } + // XXX: leaks if size_only *fields = xcalloc(1, sizeof(struct struct_field)); (*fields)->next = field; field = *fields; @@ -171,8 +162,13 @@ struct_insert_field(struct type_store *store, struct struct_field **fields, if (atype->name) { field->name = strdup(atype->name); } - field->type = type_store_lookup_atype(store, atype->type); - if (field->type->size == 0) { + struct dimensions dim = {0}; + if (size_only) { + dim = _type_store_lookup_atype(store, NULL, atype->type); + } else { + dim = _type_store_lookup_atype(store, &field->type, atype->type); + } + if (dim.size == 0) { error(store->check_context, atype->type->loc, "Struct field size cannot be zero"); return NULL; @@ -198,21 +194,22 @@ struct_insert_field(struct type_store *store, struct struct_field **fields, } } else { size_t offs = *size; - if (offs % field->type->align) { - offs += field->type->align - (offs % field->type->align); + if (offs % dim.align) { + offs += dim.align - (offs % dim.align); } field->offset = offs; - assert(field->offset % field->type->align == 0); + assert(field->offset % dim.align == 0); } - if (field->type->size == SIZE_UNDEFINED || *size == SIZE_UNDEFINED) { + if (dim.size == SIZE_UNDEFINED || *size == SIZE_UNDEFINED) { *size = SIZE_UNDEFINED; } else if (storage == STORAGE_STRUCT) { - *size = field->offset + field->type->size; + *size = field->offset + dim.size; } else { - *usize = field->type->size > *usize ? field->type->size : *usize; + *usize = dim.size > *usize ? dim.size : *usize; } - *align = field->type->align > *align ? field->type->align : *align; + *align = dim.align > *align ? dim.align : *align; + field->size = dim.size; return field; } @@ -276,15 +273,15 @@ shift_fields(struct type_store *store, struct struct_field *parent) static void struct_init_from_atype(struct type_store *store, enum type_storage storage, size_t *size, size_t *align, struct struct_field **fields, - const struct ast_struct_union_type *atype, bool *ccompat) + const struct ast_struct_union_type *atype, bool *ccompat, bool size_only) { // TODO: fields with size SIZE_UNDEFINED size_t usize = 0; assert(storage == STORAGE_STRUCT || storage == STORAGE_UNION); while (atype) { struct struct_field *field = struct_insert_field(store, fields, - storage, size, &usize, align, atype, ccompat); - if (!field->name) { + storage, size, &usize, align, atype, ccompat, size_only); + if (!field->name && !size_only) { // We need to shift the embedded struct/union's fields // so that their offsets are from the start of the // parent type. This is a bit of a hack, but it makes @@ -317,16 +314,56 @@ sum_tagged_memb(struct type_store *store, return nmemb; } +// get next member of an incomplete tagged union without completing it +static void +tagged_or_atagged_member(struct type_store *store, + const struct ast_type **atype, const struct type **type) +{ + const struct ast_type *_atype = *atype; + while (_atype->storage == STORAGE_ALIAS && _atype->unwrap) { + const struct scope_object *obj = scope_lookup( + store->check_context->scope, &_atype->alias); + if (obj->otype != O_SCAN) { + if (obj->otype == O_TYPE) { + *type = type_dealias(obj->type); + return; + } else { + error(store->check_context, _atype->loc, + "Object '%s' is not a type", + identifier_unparse(&obj->ident)); + *type = &builtin_type_void; + return; + } + } + struct incomplete_declaration *idecl = + (struct incomplete_declaration *)obj; + if (idecl->type != IDECL_DECL + || idecl->decl.decl_type != AST_DECL_TYPE) { + error(store->check_context, _atype->loc, + "Object '%s' is not a type", + identifier_unparse(&obj->ident)); + *type = &builtin_type_void; + return; + } + _atype = idecl->decl.type.type; + } + *type = NULL; + *atype = _atype; +} + static size_t sum_atagged_memb(struct type_store *store, const struct ast_tagged_union_type *u) { size_t nmemb = 0; for (; u; u = u->next) { - const struct type *type = - type_store_lookup_atype(store, u->type); - if (type->storage == STORAGE_TAGGED) { + const struct type *type = NULL; + const struct ast_type *atype = u->type; + tagged_or_atagged_member(store, &atype, &type); + if (type != NULL && type->storage == STORAGE_TAGGED) { nmemb += sum_tagged_memb(store, &type->tagged); + } else if (atype->storage == STORAGE_TAGGED) { + nmemb += sum_atagged_memb(store, &atype->tagged_union); } else { ++nmemb; } @@ -400,20 +437,6 @@ tagged_init(struct type_store *store, struct type *type, } nmemb = nmemb_dedup; - if (tu[0]->type->size == SIZE_UNDEFINED) { - char *type = gen_typename(tu[0]->type); - // TODO - struct location loc = { - .path = "<unknown>", - .lineno = 0, - .colno = 0, - }; - error(store->check_context, loc, - "Cannot create tagged union from type %s, which has undefined size\n", - type); - free(type); - } - // First one free type->tagged = *tu[0]; free(tu[0]); @@ -423,27 +446,12 @@ tagged_init(struct type_store *store, struct type *type, struct type_tagged_union **next = &type->tagged.next; for (size_t i = 1; i < nmemb; ++i) { - if (tu[i]->type->size == SIZE_UNDEFINED) { - char *type = gen_typename(tu[i]->type); - // TODO - struct location loc = { - .path = "<unknown>", - .lineno = 0, - .colno = 0, - }; - error(store->check_context, loc, - "Cannot create tagged union from type %s, which has undefined size\n", - type); - free(type); - } - if (tu[i]->type->size > type->size) { type->size = tu[i]->type->size; } if (tu[i]->type->align > type->align) { type->align = tu[i]->type->align; } - *next = tu[i]; next = &tu[i]->next; } @@ -453,7 +461,6 @@ tagged_init(struct type_store *store, struct type *type, } type->size += builtin_type_uint.size % type->align + builtin_type_uint.align; - return nmemb; } @@ -474,43 +481,116 @@ tagged_init_from_atype(struct type_store *store, } } -static void +static struct dimensions +_tagged_size(struct type_store *store, const struct ast_tagged_union_type *u) +{ + struct dimensions dim = {0}; + for (; u; u = u->next) { + struct dimensions memb = {0}; + const struct type *type = NULL; + const struct ast_type *atype = u->type; + tagged_or_atagged_member(store, &atype, &type); + if (type != NULL && type->storage == STORAGE_TAGGED) { + for (const struct type_tagged_union *u = &type->tagged; + u; u = u->next) { + if (memb.size < u->type->size) { + memb.size = u->type->size; + } + if (memb.align < u->type->align) { + memb.align = u->type->align; + } + } + } else if (atype->storage == STORAGE_TAGGED) { + memb = _tagged_size(store, &atype->tagged_union); + } else { + memb = _type_store_lookup_atype(store, NULL, atype); + } + if (dim.size < memb.size) { + dim.size = memb.size; + } + if (dim.align < memb.align) { + dim.align = memb.align; + } + } + return dim; +} + +// compute the dimensions of an incomplete tagged union without completing it +static struct dimensions +tagged_size(struct type_store *store, const struct ast_type *atype) +{ + struct dimensions dim = _tagged_size(store, &atype->tagged_union); + if (dim.align < builtin_type_uint.align) { + dim.align = builtin_type_uint.align; + } + dim.size += builtin_type_uint.size % dim.align + + builtin_type_uint.align; + return dim; +} + + +static struct dimensions tuple_init_from_atype(struct type_store *store, struct type *type, const struct ast_type *atype) { - type->size = 0, type->align = 0; const struct ast_tuple_type *atuple = &atype->tuple; - struct type_tuple *cur = &type->tuple; + struct type_tuple *cur = NULL; + if (type) { + type->size = 0, type->align = 0; + cur = &type->tuple; + } + struct dimensions dim = {0}; while (atuple) { - cur->type = type_store_lookup_atype(store, atuple->type); - if (cur->type->size == 0) { + struct dimensions memb = {0}; + if (type) { + memb = _type_store_lookup_atype(store, &cur->type, atuple->type); + cur->offset = dim.size % memb.align + dim.size; + } else { + memb = _type_store_lookup_atype(store, NULL, atuple->type); + } + if (memb.size == 0) { error(store->check_context, atuple->type->loc, "Type of size zero cannot be a tuple member"); continue; } - cur->offset = type->size % cur->type->align + type->size; - type->size += type->size % cur->type->align + cur->type->size; - if (type->align < cur->type->align) { - type->align = cur->type->align; + dim.size += dim.size % memb.align + memb.size; + if (dim.align < memb.align) { + dim.align = memb.align; } atuple = atuple->next; - if (atuple) { + if (atuple && type) { cur->next = xcalloc(1, sizeof(struct type_tuple)); cur = cur->next; } } + if (type) { + type->size = dim.size; + type->align = dim.align; + } + return dim; } -static void +static const struct type * +type_store_lookup_type(struct type_store *store, const struct type *type); + +static struct dimensions type_init_from_atype(struct type_store *store, struct type *type, const struct ast_type *atype) { + struct type tmp = {0}; + bool size_only = false; + if (type == NULL) { + type = &tmp; + size_only = true; + } + type->storage = atype->storage; type->flags = atype->flags; - const struct scope_object *obj; + // TODO: Use a dedicated error type instead of void for errors + const struct scope_object *obj = NULL; const struct identifier *ident; const struct type *builtin; struct identifier temp; @@ -556,55 +636,66 @@ type_init_from_atype(struct type_store *store, &atype->alias); } if (!obj) { - if (atype->unwrap) { - error(store->check_context, atype->loc, - "Cannot unwrap unknown type alias %s", - identifier_unparse(ident)); + error(store->check_context, atype->loc, + "Unresolvable identifier '%s'", + identifier_unparse(ident)); + *type = builtin_type_void; + return (struct dimensions){0}; + } + + + if (obj->otype == O_SCAN) { + // an incomplete declaration was encountered + struct dimensions dim = {0}; + if (size_only) { + scan_decl_finish(store->check_context, obj, &dim); + type->size = dim.size; + type->align = dim.align; + break; } - identifier_dup(&type->alias.ident, ident); - type->alias.type = NULL; - type->size = SIZE_UNDEFINED; - type->align = SIZE_UNDEFINED; - return; + // complete it first and then proceed normally + obj = scan_decl_finish(store->check_context, obj, NULL); } if (obj->otype != O_TYPE) { error(store->check_context, atype->loc, "Object '%s' is not a type", identifier_unparse(&obj->ident)); - type->alias.type = NULL; - type->size = SIZE_UNDEFINED; - type->align = SIZE_UNDEFINED; - return; + *type = builtin_type_void; + return (struct dimensions){0}; } if (atype->unwrap) { *type = *type_dealias(obj->type); break; } - identifier_dup(&type->alias.ident, &obj->ident); - type->alias.type = obj->type; - type->size = type->alias.type->size; - type->align = type->alias.type->align; + type->alias.type = obj->type->alias.type; + type->size = obj->type->size; + type->align = obj->type->align; break; case STORAGE_ARRAY: type->array.length = ast_array_len(store, atype); - type->array.members = type_store_lookup_atype( - store, atype->array.members); - if (type->array.members->size == SIZE_UNDEFINED) { - type->align = SIZE_UNDEFINED; - type->size = SIZE_UNDEFINED; + struct dimensions memb = {0}; + if (size_only) { + memb = _type_store_lookup_atype(store, + NULL, atype->array.members); + } else { + memb = _type_store_lookup_atype(store, + &type->array.members, atype->array.members); + } + if (memb.size == SIZE_UNDEFINED) { error(store->check_context, atype->loc, "Array member must have defined size"); - return; + *type = builtin_type_void; + return (struct dimensions){0}; } - type->align = type->array.members->align; + type->align = memb.align; if (type->array.length == SIZE_UNDEFINED) { type->size = SIZE_UNDEFINED; } else { - type->size = type->array.members->size * type->array.length; + type->size = memb.size * type->array.length; } break; case STORAGE_ENUM: @@ -614,12 +705,14 @@ type_init_from_atype(struct type_store *store, if (!type_is_integer(storage)) { error(store->check_context, atype->loc, "Enum storage must be an integer"); - // TODO: Use a dedicated error type *type = builtin_type_void; - return; + return (struct dimensions){0}; } type->size = storage->size; type->align = storage->size; + if (size_only) { + break; + } struct scope *scope = scope_push( &store->check_context->scope, SCOPE_ENUM); @@ -684,9 +777,12 @@ type_init_from_atype(struct type_store *store, break; case STORAGE_FUNCTION: type->size = SIZE_UNDEFINED; - type->align = SIZE_UNDEFINED; - type->func.result = - type_store_lookup_atype(store, atype->func.result); + type->align = ALIGN_UNDEFINED; + if (size_only) { + break; + } + type->func.result = type_store_lookup_atype(store, + atype->func.result); type->func.variadism = atype->func.variadism; type->func.flags = atype->func.flags; struct type_func_param *param, **next = &type->func.params; @@ -705,6 +801,9 @@ type_init_from_atype(struct type_store *store, case STORAGE_POINTER: type->size = 8; // XXX: ARCH type->align = 8; + if (size_only) { + break; + } type->pointer.flags = atype->pointer.flags; type->pointer.referent = type_store_lookup_atype( store, atype->pointer.referent); @@ -712,6 +811,9 @@ type_init_from_atype(struct type_store *store, case STORAGE_SLICE: type->size = 24; // XXX: ARCH type->align = 8; + if (size_only) { + break; + } type->array.members = type_store_lookup_atype( store, atype->array.members); type->array.length = SIZE_UNDEFINED; @@ -721,53 +823,59 @@ type_init_from_atype(struct type_store *store, type->struct_union.c_compat = true; struct_init_from_atype(store, type->storage, &type->size, &type->align, &type->struct_union.fields, - &atype->struct_union, &type->struct_union.c_compat); + &atype->struct_union, &type->struct_union.c_compat, + size_only); if (!type->struct_union.c_compat) { // Recompute size type->size = 0; for (struct struct_field *f = type->struct_union.fields; f; f = f->next) { - if (f->offset + f->type->size > type->size) { - type->size = f->offset + f->type->size; + if (f->type) assert(f->type->size == f->size); + if (f->offset + f->size > type->size) { + type->size = f->offset + f->size; } } } break; case STORAGE_TAGGED: - tagged_init_from_atype(store, type, atype); + if (size_only) { + struct dimensions tagged = tagged_size(store, atype); + type->size = tagged.size; + type->align = tagged.align; + } else { + tagged_init_from_atype(store, type, atype); + } break; case STORAGE_TUPLE: - tuple_init_from_atype(store, type, atype); + if (size_only) { + struct dimensions tup; + tup = tuple_init_from_atype(store, NULL, atype); + type->size = tup.size; + type->align = tup.align; + } else { + tuple_init_from_atype(store, type, atype); + } break; } + return (struct dimensions){ .size = type->size, .align = type->align }; +} + +static void add_padding(size_t *size, size_t align) { + if (*size != SIZE_UNDEFINED && *size != 0 && *size % align != 0) { + *size += align - (*size - align) % align; + } } static const struct type * _type_store_lookup_type( struct type_store *store, - const struct type *type, - bool recur) + const struct type *type) { const struct type *builtin = builtin_for_type(type); if (builtin) { return builtin; } - struct type psuedotype = *type; - if (type->storage == STORAGE_ALIAS && !recur) { - // References to type aliases always inherit the flags that the - // alias was defined with - // - // TODO: This is likely problematic for forward references - // TODO: Does this need a spec update? - const struct scope_object *obj = scope_lookup( - store->check_context->scope, &type->alias.ident); - if (obj) { - psuedotype.flags |= obj->type->flags; - type = &psuedotype; - } - } - uint32_t hash = type_hash(type); struct type_bucket **next = &store->buckets[hash % TYPE_STORE_BUCKETS], *bucket = NULL; @@ -775,6 +883,9 @@ _type_store_lookup_type( while (*next) { bucket = *next; if (bucket->type.id == hash) { + if (bucket->type.storage == STORAGE_ALIAS) { + bucket->type.alias.type = type->alias.type; + } return &bucket->type; } next = &bucket->next; @@ -783,30 +894,56 @@ _type_store_lookup_type( bucket = *next = xcalloc(1, sizeof(struct type_bucket)); bucket->type = *type; bucket->type.id = hash; - if (type->size != SIZE_UNDEFINED - && type->size != 0 - && type->size % type->align != 0) { - bucket->type.size += type->align - (type->size - type->align) % type->align; - } + add_padding(&bucket->type.size, type->align); return &bucket->type; } static const struct type * type_store_lookup_type(struct type_store *store, const struct type *type) { - return _type_store_lookup_type(store, type, false); + if (type->storage != STORAGE_ALIAS) { + return _type_store_lookup_type(store, type); + } + // References to type aliases always inherit the flags that the + // alias was defined with + struct type psuedotype = *type; + const struct scope_object *obj = scope_lookup( + store->check_context->scope, &type->alias.ident); + psuedotype.flags |= obj->type->flags; + return type_store_lookup_alias(store, &psuedotype, true); +} + +static struct dimensions +_type_store_lookup_atype(struct type_store *store, const struct type **type, const struct ast_type *atype) +{ + struct type temp = {0}; + struct dimensions dim = {0}; + if (type) { + dim = type_init_from_atype(store, &temp, atype); + *type = type_store_lookup_type(store, &temp); + } else { + dim = type_init_from_atype(store, NULL, atype); + } + add_padding(&dim.size, dim.align); + return dim; } const struct type * type_store_lookup_atype(struct type_store *store, const struct ast_type *atype) { - const struct type *builtin = builtin_for_atype(atype); - if (builtin) { - return builtin; - } - struct type type = {0}; - type_init_from_atype(store, &type, atype); - return type_store_lookup_type(store, &type); + struct type temp = {0}; + const struct type *type = &temp; + _type_store_lookup_atype(store, &type, atype); + return type; +} + +// Compute dimensions of an incomplete type without completing it +struct dimensions +type_store_lookup_dimensions(struct type_store *store, const struct ast_type *atype) +{ + struct dimensions dim = type_init_from_atype(store, NULL, atype); + add_padding(&dim.size, dim.align); + return dim; } const struct type * @@ -818,7 +955,7 @@ type_store_lookup_with_flags(struct type_store *store, } struct type new = *type; new.flags = flags; - return type_store_lookup_type(store, &new); + return _type_store_lookup_type(store, &new); } const struct type * @@ -870,54 +1007,28 @@ type_store_lookup_slice(struct type_store *store, const struct type *members) } const struct type * -type_store_lookup_alias(struct type_store *store, - const struct identifier *ident, const struct type *secondary, - bool exported) +type_store_lookup_alias(struct type_store *store, const struct type *type, bool exported) { - struct type alias = { - .storage = STORAGE_ALIAS, - .alias = { - .ident = *ident, - .type = secondary, - .exported = exported, - }, - .size = secondary->size, - .align = secondary->align, - .flags = secondary->flags, - }; - // XXX: This needs to be updated on updates to type_flags (types.h) - unsigned int flags[] = { - 0, - TYPE_CONST, - TYPE_ERROR, - TYPE_ERROR | TYPE_CONST, - }; - for (size_t i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - if ((flags[i] & secondary->flags) != secondary->flags) { + struct type tmp = *type; + const struct type *ret = NULL; + for (size_t i = 0; i < sizeof(typeflags) / sizeof(typeflags[0]); i++) { + if ((typeflags[i] & type->flags) != type->flags) { continue; } - const struct type *flagged = type_store_lookup_with_flags(store, - secondary, flags[i]); - alias.flags = flagged->flags; - alias.alias.type = flagged; - struct type *falias = - (struct type *)type_store_lookup_type(store, &alias); - if (falias->alias.type == NULL) { - // Finish filling in forward referenced type - falias->alias.type = type_store_lookup_with_flags(store, - secondary, secondary->flags | flags[i]); - falias->alias.exported = exported; - falias->size = secondary->size; - falias->align = secondary->align; - falias->flags = secondary->flags | flags[i]; + if (type->alias.type) { + tmp.alias.type = type_store_lookup_with_flags( + store, type->alias.type, typeflags[i]); + } + tmp.flags = typeflags[i]; + const struct type *alias = _type_store_lookup_type(store, &tmp); + if (typeflags[i] == type->flags) { + ret = alias; } } - alias.flags = secondary->flags; - alias.alias.type = secondary; - alias.alias.exported = exported; - return type_store_lookup_type(store, &alias); + return ret; } + const struct type * type_store_lookup_tagged(struct type_store *store, struct type_tagged_union *tags)