commit 7a9448df40e2927eb8ae01b408d5eac57ab1aa15
parent 782d0b67d28bb6883b6d4864bcaa903dfb9d8e33
Author: Eyal Sawady <ecs@d2evs.net>
Date: Fri, 3 Sep 2021 10:35:06 +0000
Rework embedded structs
The old version had incorrect padding and made struct subtyping
difficult to implement. As a bonus, we get aliased embeds pretty much
for free.
Signed-off-by: Eyal Sawady <ecs@d2evs.net>
Diffstat:
6 files changed, 129 insertions(+), 113 deletions(-)
diff --git a/include/ast.h b/include/ast.h
@@ -70,24 +70,11 @@ struct ast_tuple_type {
struct ast_tuple_type *next;
};
-enum struct_union_member_type {
- MEMBER_TYPE_FIELD,
- MEMBER_TYPE_EMBEDDED,
- MEMBER_TYPE_ALIAS,
-};
-
struct ast_struct_union_type {
- enum struct_union_member_type member_type;
struct ast_struct_union_type *next;
struct ast_expression *offset;
- union {
- struct {
- char *name;
- struct ast_type *type;
- } field;
- struct ast_type *embedded;
- struct identifier alias;
- };
+ char *name;
+ struct ast_type *type;
};
struct ast_type {
diff --git a/src/check.c b/src/check.c
@@ -10,6 +10,7 @@
#include "mod.h"
#include "scope.h"
#include "type_store.h"
+#include "typedef.h"
#include "types.h"
#include "util.h"
@@ -38,6 +39,17 @@ expect(const struct location *loc, bool constraint, char *fmt, ...)
}
}
+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 void
handle_errors(struct errors *errors)
{
@@ -990,7 +1002,7 @@ check_expr_call(struct context *ctx,
if (!type_is_assignable(param->type, arg->value->result)) {
error(ctx, aarg->value->loc, expr,
- "Argument is not assignable to parameter type");
+ "Argument type %s is not assignable to parameter type %s", gen_typename(arg->value->result), gen_typename(param->type));
return;
}
arg->value = lower_implicit_cast(param->type, arg->value);
@@ -2241,11 +2253,10 @@ check_expr_struct(struct context *ctx,
const struct type *ftype;
if (!stype) {
- tfield->member_type = MEMBER_TYPE_FIELD;
- tfield->field.name = afield->field.name;
- tfield->field.type = afield->field.type;
+ tfield->name = afield->field.name;
+ tfield->type = afield->field.type;
ftype = type_store_lookup_atype(
- ctx->store, tfield->field.type);
+ ctx->store, tfield->type);
} else {
if (!afield->field.name) {
error(ctx, afield->field.initializer->loc,
@@ -2302,7 +2313,7 @@ check_expr_struct(struct context *ctx,
sexpr = &expr->_struct.fields;
while (tfield) {
const struct struct_field *field = type_get_field(
- expr->result, tfield->field.name);
+ expr->result, tfield->name);
if (!field) {
// TODO: Use more specific error location
error(ctx, aexpr->loc, expr,
@@ -3005,22 +3016,8 @@ type_is_specified(struct context *ctx, const struct ast_type *atype)
if (!expr_is_specified(ctx, stype->offset)) {
return false;
}
- switch (stype->member_type) {
- case MEMBER_TYPE_FIELD:
- if (!type_is_specified(ctx, stype->field.type)) {
- return false;
- }
- break;
- case MEMBER_TYPE_EMBEDDED:
- if (!type_is_specified(ctx, stype->embedded)) {
- return false;
- }
- break;
- case MEMBER_TYPE_ALIAS:
- if (!scope_lookup(ctx->scope, &stype->alias)) {
- return false;
- }
- break;
+ if (!type_is_specified(ctx, stype->type)) {
+ return false;
}
}
return true;
diff --git a/src/parse.c b/src/parse.c
@@ -465,17 +465,18 @@ parse_struct_union_type(struct lexer *lexer)
switch (lex(lexer, &tok)) {
case T_NAME:
name = tok.name;
- struct identifier *i;
+ struct location loc = tok.loc;
switch (lex(lexer, &tok)) {
case T_COLON:
- next->member_type = MEMBER_TYPE_FIELD;
- next->field.name = name;
- next->field.type = parse_type(lexer);
+ next->name = name;
+ next->type = parse_type(lexer);
break;
case T_DOUBLE_COLON:
- next->member_type = MEMBER_TYPE_ALIAS;
- i = &next->alias;
- parse_identifier(lexer, i, false);
+ next->type = mktype(&loc);
+ next->type->storage = STORAGE_ALIAS;
+ next->type->unwrap = false;
+ parse_identifier(lexer, &next->type->alias, false);
+ struct identifier *i = &next->type->alias;
while (i->ns != NULL) {
i = i->ns;
}
@@ -484,16 +485,18 @@ parse_struct_union_type(struct lexer *lexer)
break;
default:
unlex(lexer, &tok);
- next->member_type = MEMBER_TYPE_ALIAS;
- next->alias.name = name;
+ next->type = mktype(&loc);
+ next->type->storage = STORAGE_ALIAS;
+ next->type->alias.name = name;
+ next->type->unwrap = false;
break;
}
break;
case T_STRUCT:
case T_UNION:
- next->member_type = MEMBER_TYPE_EMBEDDED;
unlex(lexer, &tok);
- next->embedded = parse_struct_union_type(lexer);
+ next->name = NULL;
+ next->type = parse_type(lexer);
break;
default:
synassert(false, &tok, T_NAME, T_STRUCT, T_UNION, T_EOF);
diff --git a/src/type_store.c b/src/type_store.c
@@ -150,36 +150,32 @@ builtin_for_type(const struct type *type)
return builtin_type_for_storage(type->storage, is_const);
}
-static void
+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)
{
- assert(atype->member_type == MEMBER_TYPE_FIELD);
- while (fields && *fields && strcmp((*fields)->name, atype->field.name) < 0) {
+ while (*fields && (!atype->name || !(*fields)->name || strcmp((*fields)->name, atype->name) < 0)) {
fields = &(*fields)->next;
}
- struct struct_field *field, _temp = {0};
- if (fields != NULL) {
- field = *fields;
- if (field != NULL && strcmp(field->name, atype->field.name) == 0) {
- error(store->check_context, atype->field.type->loc,
- "Duplicate struct/union member '%s'", atype->field.name);
- return;
- }
- *fields = xcalloc(1, sizeof(struct struct_field));
- (*fields)->next = field;
- field = *fields;
- } else {
- field = &_temp;
+ 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);
+ return NULL;
}
+ *fields = xcalloc(1, sizeof(struct struct_field));
+ (*fields)->next = field;
+ field = *fields;
- field->name = strdup(atype->field.name);
- field->type = type_store_lookup_atype(store, atype->field.type);
+ if (atype->name) {
+ field->name = strdup(atype->name);
+ }
+ field->type = type_store_lookup_atype(store, atype->type);
if (field->type->size == 0) {
- error(store->check_context, atype->field.type->loc,
+ error(store->check_context, atype->type->loc,
"Struct field size cannot be zero");
- return;
+ return NULL;
}
if (atype->offset) {
@@ -217,6 +213,57 @@ struct_insert_field(struct type_store *store, struct struct_field **fields,
*usize = field->type->size > *usize ? field->type->size : *usize;
}
*align = field->type->align > *align ? field->type->align : *align;
+ return field;
+}
+
+static const struct type *type_store_lookup_type(struct type_store *store, const struct type *type);
+
+static const struct type *
+shift_fields(struct type_store *store, const struct type *type, size_t offset)
+{
+ if (type->storage == STORAGE_ALIAS
+ && type_dealias(type)->storage != STORAGE_STRUCT
+ && type_dealias(type)->storage != STORAGE_UNION) {
+ // TODO
+ struct location loc = {
+ .path = "<unknown>",
+ .lineno = 0,
+ .colno = 0,
+ };
+ error(store->check_context, loc,
+ "Cannot embed non-struct non-union alias");
+ return &builtin_type_void;
+ }
+ if (offset == 0) {
+ // We need to return early here in order to avoid dealiasing an
+ // embedded alias. This is acceptable at nonzero offsets, but we
+ // need to keep the alias if it's at offset 0 because of
+ // subtyping.
+ return type;
+ }
+ type = type_dealias(type);
+ assert(type->storage == STORAGE_STRUCT
+ || type->storage == STORAGE_UNION);
+ struct type new = {
+ .storage = type->storage,
+ .flags = type->flags,
+ .size = type->size,
+ .align = type->align,
+ .struct_union.c_compat = type->struct_union.c_compat,
+ };
+ struct struct_field **next = &new.struct_union.fields;
+ for (struct struct_field *field = type->struct_union.fields; field;
+ field = field->next) {
+ struct struct_field *new = *next =
+ xcalloc(1, sizeof(struct struct_field));
+ next = &new->next;
+ if (field->name) {
+ new->name = strdup(field->name);
+ }
+ new->type = field->type;
+ new->offset = field->offset + offset;
+ }
+ return type_store_lookup_type(store, &new);
}
static void
@@ -228,44 +275,17 @@ struct_init_from_atype(struct type_store *store, enum type_storage storage,
size_t usize = 0;
assert(storage == STORAGE_STRUCT || storage == STORAGE_UNION);
while (atype) {
- size_t sub = *size;
- switch (atype->member_type) {
- case MEMBER_TYPE_FIELD:
- struct_insert_field(store, fields, storage,
- size, &usize, align, atype, ccompat);
- break;
- case MEMBER_TYPE_EMBEDDED:
- if (atype->embedded->storage == STORAGE_UNION) {
- *ccompat = false;
- // We need to set the offset of all union
- // members to the maximum alignment of the union
- // members, so first we do a dry run to compute
- // it:
- size_t offs = 0, align_1 = 0;
- struct_init_from_atype(store, STORAGE_UNION,
- &offs, &align_1, NULL,
- &atype->embedded->struct_union, ccompat);
- // Insert padding per the results:
- *size += *size % align_1;
- // Then insert the fields for real:
- sub = *size;
- struct_init_from_atype(store, STORAGE_UNION,
- &sub, align, fields,
- &atype->embedded->struct_union, ccompat);
- } else {
- struct_init_from_atype(store, STORAGE_STRUCT,
- &sub, align, fields,
- &atype->embedded->struct_union, ccompat);
- }
-
- if (storage == STORAGE_UNION) {
- usize = sub > usize ? sub : usize;
- } else {
- *size += sub;
- }
- break;
- case MEMBER_TYPE_ALIAS:
- assert(0); // TODO
+ struct struct_field *field = struct_insert_field(store, fields,
+ storage, size, &usize, align, atype, ccompat);
+ if (!field->name) {
+ // 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
+ // type_get_field far easier to implement and doesn't
+ // cause any trouble in gen since offsets are only used
+ // there for sorting fields.
+ field->type = shift_fields(store, field->type,
+ *size - field->type->size);
}
atype = atype->next;
}
@@ -476,8 +496,6 @@ 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
type_init_from_atype(struct type_store *store,
struct type *type,
diff --git a/src/typedef.c b/src/typedef.c
@@ -176,8 +176,9 @@ emit_struct(const struct type *type, FILE *out)
for (size_t i = 0; i < n; ++i) {
const struct struct_field *f = fields[i];
if (!type->struct_union.c_compat) {
- fprintf(out, "@offset(%zd) %s: ", f->offset, f->name);
- } else {
+ fprintf(out, "@offset(%zd) ", f->offset);
+ }
+ if (f->name) {
fprintf(out, "%s: ", f->name);
}
emit_type(f->type, out);
diff --git a/src/types.c b/src/types.c
@@ -44,8 +44,16 @@ type_get_field(const struct type *type, const char *name)
|| type->storage == STORAGE_UNION);
struct struct_field *field = type->struct_union.fields;
while (field) {
- if (strcmp(field->name, name) == 0) {
- return field;
+ if (field->name) {
+ if (strcmp(field->name, name) == 0) {
+ return field;
+ }
+ } else {
+ const struct struct_field *f =
+ type_get_field(field->type, name);
+ if (f != NULL) {
+ return f;
+ }
}
field = field->next;
}
@@ -415,7 +423,9 @@ type_hash(const struct type *type)
case STORAGE_UNION:
for (const struct struct_field *field = type->struct_union.fields;
field; field = field->next) {
- hash = fnv1a_s(hash, field->name);
+ if (field->name) {
+ hash = fnv1a_s(hash, field->name);
+ }
hash = fnv1a_u32(hash, type_hash(field->type));
hash = fnv1a_size(hash, field->offset);
}