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:
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)
{