From 2833c0041d669bc1ba7a6d13e7f92c23b84e04ae Mon Sep 17 00:00:00 2001
From: Michael Forney <mforney@mforney.org>
Date: Wed, 17 Apr 2019 15:07:39 -0700
Subject: Allow designators in __builtin_offsetof

---
 expr.c                    | 41 ++++++++++++++++++++++++++++++++++++++++-
 test/builtin-offsetof.c   |  9 +++++++++
 test/builtin-offsetof.qbe |  1 +
 3 files changed, 50 insertions(+), 1 deletion(-)
 create mode 100644 test/builtin-offsetof.c
 create mode 100644 test/builtin-offsetof.qbe

diff --git a/expr.c b/expr.c
index 5c3e688..1ed040c 100644
--- a/expr.c
+++ b/expr.c
@@ -400,11 +400,48 @@ primaryexpr(struct scope *s)
 
 static struct expr *condexpr(struct scope *);
 
+/* TODO: merge with init.c:designator() */
+static void
+designator(struct scope *s, struct type *t, uint64_t *offset)
+{
+	char *name;
+	struct member *m;
+	uint64_t i;
+
+	for (;;) {
+		switch (tok.kind) {
+		case TLBRACK:
+			if (t->kind != TYPEARRAY)
+				error(&tok.loc, "index designator is only valid for array types");
+			next();
+			i = intconstexpr(s, false);
+			expect(TRBRACK, "for index designator");
+			t = t->base;
+			*offset += i * t->size;
+			break;
+		case TPERIOD:
+			if (t->kind != TYPESTRUCT && t->kind != TYPEUNION)
+				error(&tok.loc, "member designator only valid for struct/union types");
+			next();
+			name = expect(TIDENT, "for member designator");
+			m = typemember(t, name, offset);
+			if (!m)
+				error(&tok.loc, "%s has no member named '%s'", t->kind == TYPEUNION ? "union" : "struct", name);
+			free(name);
+			t = m->type;
+			break;
+		default:
+			return;
+		}
+	}
+}
+
 static struct expr *
 builtinfunc(struct scope *s, enum builtinkind kind)
 {
 	struct expr *e, *param;
 	struct type *t;
+	struct member *m;
 	char *name;
 	uint64_t offset;
 
@@ -437,8 +474,10 @@ builtinfunc(struct scope *s, enum builtinkind kind)
 		if (t->kind != TYPESTRUCT && t->kind != TYPEUNION)
 			error(&tok.loc, "type is not a struct/union type");
 		offset = 0;
-		if (!typemember(t, name, &offset))
+		m = typemember(t, name, &offset);
+		if (!m)
 			error(&tok.loc, "struct/union has no member named '%s'", name);
+		designator(s, m->type, &offset);
 		e = mkconstexpr(&typeulong, offset);
 		free(name);
 		break;
diff --git a/test/builtin-offsetof.c b/test/builtin-offsetof.c
new file mode 100644
index 0000000..1737d0e
--- /dev/null
+++ b/test/builtin-offsetof.c
@@ -0,0 +1,9 @@
+struct s {
+	struct {
+		union {
+			float z;
+			char *c;
+		} b;
+	} a[5];
+};
+int x = __builtin_offsetof(struct s, a[2].b.c);
diff --git a/test/builtin-offsetof.qbe b/test/builtin-offsetof.qbe
new file mode 100644
index 0000000..1efe7e7
--- /dev/null
+++ b/test/builtin-offsetof.qbe
@@ -0,0 +1 @@
+export data $x = align 4 { w 16, }
-- 
cgit v1.2.3