harec

[hare] Hare compiler, written in C11 for POSIX OSs
Log | Files | Refs | README | LICENSE

commit d5d992c355e13cde1a2306d9902561c9e0d7d39a
parent 52bbbdf5fdb73c5d21f4870ebf3fa391b3196455
Author: Drew DeVault <sir@cmpwn.com>
Date:   Tue, 20 Dec 2022 12:58:48 +0100

Implement @packed structs

Implements: https://todo.sr.ht/~sircmpwn/hare/783
Signed-off-by: Drew DeVault <sir@cmpwn.com>

Diffstat:
Minclude/ast.h | 9+++++++--
Minclude/lex.h | 1+
Minclude/type_store.h | 6+++++-
Minclude/types.h | 4+++-
Msrc/check.c | 19++++++++++---------
Msrc/lex.c | 1+
Msrc/parse.c | 9+++++++--
Msrc/qtype.c | 4+++-
Msrc/type_store.c | 108++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Msrc/typedef.c | 5+++--
Msrc/types.c | 12++++++++++++
11 files changed, 121 insertions(+), 57 deletions(-)

diff --git a/include/ast.h b/include/ast.h @@ -71,13 +71,18 @@ struct ast_tuple_type { struct ast_tuple_type *next; }; -struct ast_struct_union_type { - struct ast_struct_union_type *next; +struct ast_struct_union_field { + struct ast_struct_union_field *next; struct ast_expression *offset; char *name; struct ast_type *type; }; +struct ast_struct_union_type { + struct ast_struct_union_field fields; + bool packed; +}; + struct ast_type { struct location loc; enum type_storage storage; diff --git a/include/lex.h b/include/lex.h @@ -13,6 +13,7 @@ enum lexical_token { T_ATTR_INIT, T_ATTR_NORETURN, T_ATTR_OFFSET, + T_ATTR_PACKED, T_ATTR_SYMBOL, T_ATTR_TEST, T_ATTR_THREADLOCAL, diff --git a/include/type_store.h b/include/type_store.h @@ -46,8 +46,12 @@ const struct type *type_store_lookup_array(struct type_store *store, const struct type *type_store_lookup_slice(struct type_store *store, struct location loc, const struct type *members); +// Looks up a type alias, which may be incomplete. If the dimensions of the +// type are known, provide them as a hint in the dims argument (which can be +// NULL otherwise). This is used as a hint to skip adding padding to packed +// struct types. const struct type *type_store_lookup_alias(struct type_store *store, - const struct type *secondary); + const struct type *secondary, const struct dimensions *dims); const struct type *type_store_lookup_tagged(struct type_store *store, struct location loc, struct type_tagged_union *tags); diff --git a/include/types.h b/include/types.h @@ -116,13 +116,14 @@ struct struct_field { }; struct type_struct_union { + struct struct_field *fields; // c_compat is false if explicit offsets are used, or if the type is a // union. The latter is actually still C-compatible, but this is an // implementation detail which changes the way the QBE IL is generated, // and in the case of unions, the altered behavior for explicit-offset // structs is also correct for all cases of unions. bool c_compat; - struct struct_field *fields; + bool packed; }; struct type_tuple { @@ -190,6 +191,7 @@ uint32_t type_hash(const struct type *type); const struct type *promote_const(const struct type *a, const struct type *b); bool type_is_assignable(const struct type *to, const struct type *from); bool type_is_castable(const struct type *to, const struct type *from); +bool type_is_complete(const struct type *type); const struct type *type_create_const(enum type_storage storage, intmax_t min, intmax_t max); diff --git a/src/check.c b/src/check.c @@ -2577,8 +2577,8 @@ check_expr_struct(struct context *ctx, .storage = STORAGE_STRUCT, .flags = TYPE_CONST, }; - struct ast_struct_union_type *tfield = &satype.struct_union; - struct ast_struct_union_type **tnext = &tfield->next; + struct ast_struct_union_field *tfield = &satype.struct_union.fields; + struct ast_struct_union_field **tnext = &tfield->next; struct expr_struct_field *sexpr, **snext = &expr->_struct.fields; expr->_struct.autofill = aexpr->_struct.autofill; if (stype == NULL && expr->_struct.autofill) { @@ -2648,7 +2648,7 @@ check_expr_struct(struct context *ctx, } else { expr->result = type_store_lookup_atype(ctx->store, &satype); - tfield = &satype.struct_union; + tfield = &satype.struct_union.fields; sexpr = expr->_struct.fields; while (tfield) { const struct struct_field *field = type_get_field( @@ -2668,8 +2668,8 @@ check_expr_struct(struct context *ctx, sexpr->field = field; sexpr->value = lower_implicit_cast(field->type, sexpr->value); - struct ast_struct_union_type *next = tfield->next; - if (tfield != &satype.struct_union) { + struct ast_struct_union_field *next = tfield->next; + if (tfield != &satype.struct_union.fields) { free(tfield); } tfield = next; @@ -3376,7 +3376,7 @@ check_type(struct context *ctx, .align = type->align, .flags = type->flags, }; - decl->_type = type_store_lookup_alias(ctx->store, &_alias); + decl->_type = type_store_lookup_alias(ctx->store, &_alias, NULL); } return decl; } @@ -3860,8 +3860,8 @@ resolve_type(struct context *ctx, struct incomplete_declaration *idecl) } // 1. compute type dimensions - struct dimensions dim = type_store_lookup_dimensions(ctx->store, - idecl->decl.type.type); + struct dimensions dim = type_store_lookup_dimensions( + ctx->store, idecl->decl.type.type); handle_errors(ctx->errors); idecl->in_progress = false; @@ -3880,7 +3880,8 @@ resolve_type(struct context *ctx, struct incomplete_declaration *idecl) .flags = idecl->decl.type.type->flags, }; - const struct type *alias = type_store_lookup_alias(ctx->store, &_alias); + const struct type *alias = type_store_lookup_alias( + ctx->store, &_alias, &dim); idecl->obj.otype = O_TYPE; idecl->obj.type = alias; ((struct type *)alias)->alias.type = diff --git a/src/lex.c b/src/lex.c @@ -20,6 +20,7 @@ static const char *tokens[] = { [T_ATTR_INIT] = "@init", [T_ATTR_NORETURN] = "@noreturn", [T_ATTR_OFFSET] = "@offset", + [T_ATTR_PACKED] = "@packed", [T_ATTR_SYMBOL] = "@symbol", [T_ATTR_TEST] = "@test", [T_ATTR_THREADLOCAL] = "@threadlocal", diff --git a/src/parse.c b/src/parse.c @@ -474,10 +474,15 @@ parse_struct_union_type(struct lexer *lexer) { struct token tok = {0}; struct ast_type *type = mktype(&lexer->loc); - struct ast_struct_union_type *next = &type->struct_union; + struct ast_struct_union_field *next = &type->struct_union.fields; switch (lex(lexer, &tok)) { case T_STRUCT: type->storage = STORAGE_STRUCT; + if (lex(lexer, &tok) == T_ATTR_PACKED) { + type->struct_union.packed = true; + } else { + unlex(lexer, &tok); + } break; case T_UNION: type->storage = STORAGE_UNION; @@ -541,7 +546,7 @@ parse_struct_union_type(struct lexer *lexer) if (lex(lexer, &tok) != T_RBRACE) { unlex(lexer, &tok); next->next = xcalloc(1, - sizeof(struct ast_struct_union_type)); + sizeof(struct ast_struct_union_field)); next = next->next; } break; diff --git a/src/qtype.c b/src/qtype.c @@ -78,7 +78,9 @@ aggregate_lookup(struct gen_context *ctx, const struct type *type) def->type.base = type; def->type.name = name; - assert(type->size == SIZE_UNDEFINED + const struct type *final = type_dealias(type); + assert((final->storage == STORAGE_STRUCT && final->struct_union.packed) + || type->size == SIZE_UNDEFINED || type->size == 0 || type->size % type->align == 0); diff --git a/src/type_store.c b/src/type_store.c @@ -150,16 +150,17 @@ 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, bool size_only, - bool last) + const struct ast_struct_union_type *atype, + const struct ast_struct_union_field *afield, + bool *ccompat, bool size_only, bool last) { - while (*fields && (!atype->name || !(*fields)->name || strcmp((*fields)->name, atype->name) < 0)) { + while (*fields && (!afield->name || !(*fields)->name || strcmp((*fields)->name, afield->name) < 0)) { fields = &(*fields)->next; } struct struct_field *field = *fields; - if (field != NULL && atype->name && field->name && strcmp(field->name, atype->name) == 0) { - error(store->check_context, atype->type->loc, - "Duplicate struct/union member '%s'", atype->name); + if (field != NULL && afield->name && field->name && strcmp(field->name, afield->name) == 0) { + error(store->check_context, afield->type->loc, + "Duplicate struct/union member '%s'", afield->name); return NULL; } // XXX: leaks if size_only @@ -167,32 +168,32 @@ struct_insert_field(struct type_store *store, struct struct_field **fields, (*fields)->next = field; field = *fields; - if (atype->name) { - field->name = strdup(atype->name); + if (afield->name) { + field->name = strdup(afield->name); } struct dimensions dim = {0}; if (size_only) { - dim = lookup_atype_with_dimensions(store, NULL, atype->type); + dim = lookup_atype_with_dimensions(store, NULL, afield->type); } else { - dim = lookup_atype_with_dimensions(store, &field->type, atype->type); + dim = lookup_atype_with_dimensions(store, &field->type, afield->type); } if (dim.size == 0) { - error(store->check_context, atype->type->loc, + error(store->check_context, afield->type->loc, "Type of size 0 is not a valid struct/union member"); return NULL; } if (!last && dim.size == SIZE_UNDEFINED) { - error(store->check_context, atype->type->loc, + error(store->check_context, afield->type->loc, "Type of undefined size is not a valid struct/union member"); return NULL; } assert(dim.align != ALIGN_UNDEFINED); assert(dim.align != 0); - if (atype->offset) { + if (afield->offset) { *ccompat = false; struct expression in, out; - check_expression(store->check_context, atype->offset, &in, NULL); + check_expression(store->check_context, afield->offset, &in, NULL); field->offset = 0; enum eval_result r = eval_expr(store->check_context, &in, &out); if (r != EVAL_OK) { @@ -207,6 +208,8 @@ struct_insert_field(struct type_store *store, struct struct_field **fields, } else { field->offset = (size_t)out.constant.uval; } + } else if (atype->packed) { + field->offset = *size; } else { size_t offs = *size; if (offs % dim.align) { @@ -232,13 +235,13 @@ static const struct type *type_store_lookup_type(struct type_store *store, const void shift_fields(struct type_store *store, - const struct ast_struct_union_type *atype, struct struct_field *parent) + const struct ast_struct_union_field *afield, struct struct_field *parent) { if (parent->type->storage == STORAGE_ALIAS && type_dealias(parent->type)->storage != STORAGE_STRUCT && type_dealias(parent->type)->storage != STORAGE_UNION) { - assert(atype); - error(store->check_context, atype->type->loc, + assert(afield); + error(store->check_context, afield->type->loc, "Cannot embed non-struct non-union alias"); return; } @@ -258,6 +261,7 @@ shift_fields(struct type_store *store, .size = type->size, .align = type->align, .struct_union.c_compat = type->struct_union.c_compat, + .struct_union.packed = type->struct_union.packed, }; struct struct_field **next = &new.struct_union.fields; for (struct struct_field *field = type->struct_union.fields; field; @@ -289,10 +293,12 @@ struct_init_from_atype(struct type_store *store, enum type_storage storage, // TODO: fields with size SIZE_UNDEFINED size_t usize = 0; assert(storage == STORAGE_STRUCT || storage == STORAGE_UNION); - while (atype) { + const struct ast_struct_union_field *afield = &atype->fields; + while (afield) { + bool last = afield->next == NULL; struct struct_field *field = struct_insert_field(store, fields, - storage, size, &usize, align, atype, ccompat, size_only, - atype->next == NULL); + storage, size, &usize, align, atype, afield, + ccompat, size_only, last); if (field == NULL) { return; } @@ -303,9 +309,9 @@ struct_init_from_atype(struct type_store *store, enum type_storage storage, // type_get_field far easier to implement and doesn't // cause any trouble in gen since offsets are only used // there for sorting fields. - shift_fields(store, atype, field); + shift_fields(store, afield, field); } - atype = atype->next; + afield = afield->next; } if (storage == STORAGE_UNION) { @@ -633,6 +639,14 @@ tuple_init_from_atype(struct type_store *store, static const struct type * type_store_lookup_type(struct type_store *store, const struct type *type); +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 struct dimensions type_init_from_atype(struct type_store *store, struct type *type, @@ -819,7 +833,8 @@ type_init_from_atype(struct type_store *store, break; case STORAGE_STRUCT: case STORAGE_UNION: - type->struct_union.c_compat = true; + type->struct_union.c_compat = !atype->struct_union.packed; + type->struct_union.packed = atype->struct_union.packed; struct_init_from_atype(store, type->storage, &type->size, &type->align, &type->struct_union.fields, &atype->struct_union, &type->struct_union.c_compat, @@ -856,19 +871,30 @@ type_init_from_atype(struct type_store *store, } 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; + bool packed = false; + if (type_is_complete(type)) { + const struct type *final = type_dealias(type); + if (final->storage == STORAGE_STRUCT) { + packed = final->struct_union.packed; + } } + + struct dimensions dim = { + .size = type->size, + .align = type->align, + }; + if (!packed) { + add_padding(&dim.size, dim.align); + } + return dim; } static const struct type * _type_store_lookup_type( struct type_store *store, - const struct type *type) + const struct type *type, + const struct dimensions *dims) { const struct type *builtin = builtin_for_type(type); if (builtin) { @@ -893,7 +919,11 @@ _type_store_lookup_type( bucket = *next = xcalloc(1, sizeof(struct type_bucket)); bucket->type = *type; bucket->type.id = hash; - add_padding(&bucket->type.size, type->align); + + if (dims == NULL) { + add_padding(&bucket->type.size, type->align); + } + return &bucket->type; } @@ -901,7 +931,7 @@ static const struct type * type_store_lookup_type(struct type_store *store, const struct type *type) { if (type->storage != STORAGE_ALIAS) { - return _type_store_lookup_type(store, type); + return _type_store_lookup_type(store, type, NULL); } // References to type aliases always inherit the flags that the // alias was defined with @@ -909,7 +939,7 @@ type_store_lookup_type(struct type_store *store, const struct type *type) const struct scope_object *obj = scope_lookup( store->check_context->scope, &type->alias.name); psuedotype.flags |= obj->type->flags; - return type_store_lookup_alias(store, &psuedotype); + return type_store_lookup_alias(store, &psuedotype, NULL); } static struct dimensions @@ -923,7 +953,6 @@ lookup_atype_with_dimensions(struct type_store *store, const struct type **type, } else { dim = type_init_from_atype(store, NULL, atype); } - add_padding(&dim.size, dim.align); return dim; } @@ -948,9 +977,7 @@ 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) { - struct dimensions dim = type_init_from_atype(store, NULL, atype); - add_padding(&dim.size, dim.align); - return dim; + return type_init_from_atype(store, NULL, atype); } const struct type * @@ -962,7 +989,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, NULL); } const struct type * @@ -1064,7 +1091,9 @@ type_store_lookup_slice(struct type_store *store, struct location loc, } const struct type * -type_store_lookup_alias(struct type_store *store, const struct type *type) +type_store_lookup_alias(struct type_store *store, + const struct type *type, + const struct dimensions *dims) { struct type tmp = *type; const struct type *ret = NULL; @@ -1077,7 +1106,8 @@ type_store_lookup_alias(struct type_store *store, const struct type *type) store, type->alias.type, typeflags[i]); } tmp.flags = typeflags[i]; - const struct type *alias = _type_store_lookup_type(store, &tmp); + const struct type *alias = _type_store_lookup_type( + store, &tmp, dims); if (typeflags[i] == type->flags) { ret = alias; } diff --git a/src/typedef.c b/src/typedef.c @@ -204,8 +204,9 @@ emit_struct(const struct type *type, FILE *out) qsort(fields, n, sizeof(fields[0]), field_compar); - fprintf(out, "%s { ", type->storage == STORAGE_STRUCT - ? "struct" : "union"); + fprintf(out, "%s %s{ ", + type->storage == STORAGE_STRUCT ? "struct" : "union", + type->struct_union.packed ? "@packed " : ""); for (size_t i = 0; i < n; ++i) { const struct struct_field *f = fields[i]; if (!type->struct_union.c_compat) { diff --git a/src/types.c b/src/types.c @@ -41,6 +41,18 @@ type_dealias(const struct type *type) return type; } +bool +type_is_complete(const struct type *type) +{ + while (type->storage == STORAGE_ALIAS) { + if (type->alias.type == NULL) { + return false; + } + type = type->alias.type; + } + return true; +} + const struct struct_field * type_get_field(const struct type *type, const char *name) {