harec

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

commit 2eeb98fe358d0536a4f45577eadd61733f07c005
parent 7ecfa4acc36b37a549b5ff44d19cc754137af45c
Author: Drew DeVault <sir@cmpwn.com>
Date:   Sun, 10 Jan 2021 14:22:22 -0500

Implement struct field selection

There is an issue that I have yet to investigate with recursive
auto-dereferencing.

Diffstat:
Minclude/expr.h | 5++++-
Minclude/types.h | 4++--
Msrc/check.c | 20++++++++++++++++++--
Msrc/gen.c | 41++++++++++++++++++++++++++++++++++++++++-
Msrc/types.c | 2+-
Mtests/06-structs.ha | 96++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
6 files changed, 160 insertions(+), 8 deletions(-)

diff --git a/include/expr.h b/include/expr.h @@ -44,7 +44,10 @@ struct expression_access { struct expression *array; struct expression *index; }; - // TODO: Field selection + struct { + struct expression *_struct; + const struct struct_field *field; + }; }; }; diff --git a/include/types.h b/include/types.h @@ -114,8 +114,8 @@ struct type { }; const struct type *type_dereference(const struct type *type); -const struct struct_field *type_lookup_field( - const struct type *type, const char *name); +const struct struct_field *type_get_field( + const struct type *type, const char *name); const char *type_storage_unparse(enum type_storage storage); bool type_is_signed(const struct type *type); diff --git a/src/check.c b/src/check.c @@ -104,7 +104,23 @@ check_expr_access(struct context *ctx, atype->array.members, atype->flags | atype->array.members->flags); break; case ACCESS_FIELD: - assert(0); // TODO + expr->access._struct = xcalloc(1, sizeof(struct expression)); + check_expression(ctx, aexpr->access._struct, expr->access._struct); + const struct type *stype = expr->access._struct->result; + while (stype->storage == TYPE_STORAGE_POINTER) { + expect(&aexpr->access.array->loc, + !(stype->pointer.flags & PTR_NULLABLE), + "Cannot dereference nullable pointer for field selection"); + stype = stype->pointer.referent; + } + expect(&aexpr->access._struct->loc, + stype->storage == TYPE_STORAGE_STRUCT || stype->storage == TYPE_STORAGE_UNION, + "Cannot index non-struct, non-union object"); + expr->access.field = type_get_field(stype, aexpr->access.field); + expect(&aexpr->access._struct->loc, expr->access.field, + "No such struct field '%s'", aexpr->access.field); + expr->result = expr->access.field->type; + break; } } @@ -688,7 +704,7 @@ check_expr_struct(struct context *ctx, tfield = &stype.struct_union; sexpr = &expr->_struct; while (tfield) { - const struct struct_field *field = type_lookup_field( + const struct struct_field *field = type_get_field( expr->result, tfield->field.name); // TODO: Use more specific error location expect(&aexpr->loc, field, "No field by this name exists for this type"); diff --git a/src/gen.c b/src/gen.c @@ -321,6 +321,44 @@ address_index(struct gen_context *ctx, } static void +address_field(struct gen_context *ctx, + const struct expression *expr, + struct qbe_value *out) +{ + const struct type *stype = expr->access._struct->result; + while (stype->storage == TYPE_STORAGE_POINTER) { + stype = stype->pointer.referent; + } + + gen_temp(ctx, out, &qbe_long, "object.%d"); // XXX: ARCH + gen_expression(ctx, expr->access._struct, out); + + stype = expr->access._struct->result; + if (stype->storage == TYPE_STORAGE_POINTER) { + // We get one dereference for free for aggregate types + stype = stype->pointer.referent; + } + while (stype->storage == TYPE_STORAGE_POINTER) { + qval_deref(out); + struct qbe_value deref; + gen_loadtemp(ctx, &deref, out, + qtype_for_type(ctx, stype->pointer.referent, false), + type_is_signed(stype->pointer.referent)); + *out = deref; + stype = stype->pointer.referent; + } + + const struct struct_field *field = expr->access.field; + + struct qbe_value offset = {0}; + constl(&offset, field->offset); + pushi(ctx->current, out, Q_ADD, out, &offset, NULL); + if (!type_is_aggregate(field->type)) { + qval_deref(out); + } +} + +static void address_object(struct gen_context *ctx, const struct expression *expr, struct qbe_value *out) @@ -334,7 +372,8 @@ address_object(struct gen_context *ctx, address_index(ctx, expr, out); break; case ACCESS_FIELD: - assert(0); // TODO + address_field(ctx, expr, out); + break; } } diff --git a/src/types.c b/src/types.c @@ -13,7 +13,7 @@ type_dereference(const struct type *type) } const struct struct_field * -type_lookup_field(const struct type *type, const char *name) +type_get_field(const struct type *type, const char *name) { // TODO: We should consider lowering unions into structs with explicit // offsets diff --git a/tests/06-structs.ha b/tests/06-structs.ha @@ -1,7 +1,20 @@ +fn rt::compile(src: str) int; + fn padding() void = { assert(size(struct { x: i32, y: i32 }) == 8z); assert(size(struct { x: i32, y: i64 }) == 16z); assert(size(union { x: i8, y: i16, z: i32 }) == 4z); + + const s = struct { + x: i8 = 10i8, + y: i16 = 20i16, + z: i32 = 30i32, + q: i64 = 40i32, + }; + assert(&s.x: uintptr: size % 1z == 0z); + assert(&s.y: uintptr: size % 2z == 0z); + assert(&s.z: uintptr: size % 4z == 0z); + assert(&s.q: uintptr: size % 8z == 0z); }; fn storage() void = { @@ -11,8 +24,89 @@ fn storage() void = { assert(ptr[1] == 20i32); }; +fn assignment() void = { + let coords = struct { x: int = 20, y: int = 30 }; + coords.x = 40; + coords.y = 50; + assert(coords.x == 40); + assert(coords.y == 50); + coords = struct { x: int = 60, y: int = 70 }; + assert(coords.x == 60); + assert(coords.y == 70); +}; + +fn deref() void = { + let coords = struct { x: int = 20, y: int = 30 }; + let a = &coords; + assert(a.x == 20); + assert(a.y == 30); + // TODO: Why does this fail? + //let b = &a; + //assert(b.x == 20); + //assert(b.y == 30); + //b.x = 42; + //b.y = 96; + //assert(coords.x == 42); + //assert(coords.y == 96); +}; + +fn nested() void = { + let s = struct { + x: int = 10, + y: struct { + z: int, + q: int, + a: struct { b: int, c: int }, + } = struct { + z: int = 20, + q: int = 30, + a: struct { b: int, c: int } = struct { + b: int = 42, c: int = 24, + }, + }, + }; + assert(s.x == 10); + assert(s.y.z == 20); + assert(s.y.q == 30); + assert(s.y.a.b == 42); + assert(s.y.a.c == 24); + assert(&s.y: uintptr == &s.y.z: uintptr); + + s.x = 1337; + assert(s.x == 1337); + + s.y.a = struct { b: int = 1337, c: int = 7331 }; + assert(s.y.a.b == 1337); + assert(s.y.a.c == 7331); +}; + +fn invariants() void = { + const failures = [ + // Assign field from non-assignable type: + "fn test() void = { let x: struct { y: int } = struct { y: int = 10u }; };", + // Dereference non-nullable pointer: + "fn test() void = { let x: nullable *struct { y: int } = null; x.y; };", + // Select field from non-struct object: + "fn test() void = { let x = 10; x.y; };", + // Unknown field: + "fn test() void = { let x: struct { y: int } = struct { y: int = 10 }; x.z; };", + ]; + for (let i = 0z; i < len(failures); i += 1z) { + assert(rt::compile(failures[i]) != 0); + }; +}; + export fn main() void = { padding(); storage(); - // TODO: These tests need to be expanded when field selection works + assignment(); + deref(); + nested(); + invariants(); + // TODO: + // - Union tests + // - Named structs + // - Embedded structs + // - offset() + // - Autofilling (...) and its invariants };