From 4e4e4c2d2194c2c1f73d8c4503b4c6c56e1aa9a7 Mon Sep 17 00:00:00 2001 From: Jesse Rosenstock Date: Sun, 30 Aug 2020 20:47:25 +0200 Subject: [PATCH] Allow char bit-fields These are not required to be supported (only int, signed int, and unsigned int are required), but most compilers support it. https://port70.net/~nsz/c/c89/c89-draft.html#3.5.2.1 https://port70.net/~nsz/c/c89/c89-draft.html#A.6.5.8 For consistency with other integral types, plain `char` bit-fields are unsigned, regardless of the `--signed-chars` option. Fixes #1047 --- doc/cc65.sgml | 5 + src/cc65/declare.c | 6 +- src/cc65/symtab.c | 8 +- test/{err => val}/bug1047.c | 0 test/val/char-bitfield.c | 280 ++++++++++++++++++++++++++++++++++++ test/val/enum-bitfield.c | 107 ++++++++++++++ 6 files changed, 401 insertions(+), 5 deletions(-) rename test/{err => val}/bug1047.c (100%) create mode 100644 test/val/char-bitfield.c diff --git a/doc/cc65.sgml b/doc/cc65.sgml index e7b8f260d..e3d9694fe 100644 --- a/doc/cc65.sgml +++ b/doc/cc65.sgml @@ -807,6 +807,11 @@ This cc65 version has some extensions to the ISO C standard. cc65 supports bit-fields of any integral type that is int-sized or + smaller, and enumerated types with those types as their underlying + type. (Only Computed gotos, a GCC extension, has limited support. With it you can use fast jump tables from C. You can take the address of a label with a double ampersand, putting them in a static const array of type void *. diff --git a/src/cc65/declare.c b/src/cc65/declare.c index 857021671..8d0a1097c 100644 --- a/src/cc65/declare.c +++ b/src/cc65/declare.c @@ -764,9 +764,9 @@ static int ParseFieldWidth (Declaration* Decl) /* TODO: This can be relaxed to be any integral type, but ** ParseStructInit currently only supports up to int. */ - if (SizeOf (Decl->Type) != SizeOf (type_uint)) { - /* Only int sized types may be used for bit-fields for now */ - Error ("cc65 currently only supports unsigned int bit-fields"); + if (SizeOf (Decl->Type) > SizeOf (type_uint)) { + /* Only int-sized or smaller types may be used for bit-fields for now */ + Error ("cc65 currently only supports char-sized and int-sized bit-fields"); return -1; } diff --git a/src/cc65/symtab.c b/src/cc65/symtab.c index 5546d450c..56c868b2a 100644 --- a/src/cc65/symtab.c +++ b/src/cc65/symtab.c @@ -870,9 +870,13 @@ SymEntry* AddBitField (const char* Name, const Type* T, unsigned Offs, if (!SignednessSpecified) { /* int is treated as signed int everywhere except bit-fields; switch it to unsigned, ** since this is allowed for bit-fields and avoids sign-extension, so is much faster. - ** enums set SignednessSpecified to 1 to avoid this adjustment. + ** enums set SignednessSpecified to 1 to avoid this adjustment. Character types + ** actually distinguish 3 types of char; char may either be signed or unsigned, which + ** is controlled by `--signed-chars`. In bit-fields, however, we perform the same + ** `char -> unsigned char` adjustment that is performed with other integral types. */ - CHECK ((Entry->Type->C & T_MASK_SIGN) == T_SIGN_SIGNED); + CHECK ((Entry->Type->C & T_MASK_SIGN) == T_SIGN_SIGNED || + IsTypeChar (Entry->Type)); Entry->Type->C &= ~T_MASK_SIGN; Entry->Type->C |= T_SIGN_UNSIGNED; } diff --git a/test/err/bug1047.c b/test/val/bug1047.c similarity index 100% rename from test/err/bug1047.c rename to test/val/bug1047.c diff --git a/test/val/char-bitfield.c b/test/val/char-bitfield.c new file mode 100644 index 000000000..0d611f127 --- /dev/null +++ b/test/val/char-bitfield.c @@ -0,0 +1,280 @@ +/* + Copyright 2020 The cc65 Authors + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + Tests of char bit-fields; see https://github.com/cc65/cc65/issues/1047 +*/ + +#include + +static unsigned char failures = 0; + +static struct four_bits { + unsigned char x : 4; +} fb = {1}; + +static void test_four_bits (void) +{ + if (sizeof (struct four_bits) != 1) { + printf ("Got sizeof (struct four_bits) = %zu, expected 1.\n", + sizeof (struct four_bits)); + failures++; + } + + if (fb.x != 1) { + printf ("Got fb.x = %u, expected 1.\n", fb.x); + failures++; + } + + fb.x = 3; + + if (fb.x != 3) { + printf ("Got fb.x = %u, expected 3.\n", fb.x); + failures++; + } +} + +static struct four_bits_signed { + signed char x : 4; +} fbs = {1}; + +static void test_four_bits_signed (void) +{ + if (sizeof (struct four_bits_signed) != 1) { + printf ("Got sizeof (struct four_bits_signed) = %zu, expected 1.\n", + sizeof (struct four_bits)); + failures++; + } + + if (fbs.x != 1) { + printf ("Got fbs.x = %d, expected 1.\n", fbs.x); + failures++; + } + + fbs.x = 3; + + if (fbs.x != 3) { + printf ("Got fbs.x = %d, expected 3.\n", fbs.x); + failures++; + } +} + +static struct four_bits_plain { + char x : 4; +} fbp = {1}; + +static void test_four_bits_plain (void) +{ + if (sizeof (struct four_bits_plain) != 1) { + printf ("Got sizeof (struct four_bits_plain) = %zu, expected 1.\n", + sizeof (struct four_bits)); + failures++; + } + + if (fbp.x != 1) { + printf ("Got fbp.x = %d, expected 1.\n", fbp.x); + failures++; + } + + fbp.x = 3; + + if (fbp.x != 3) { + printf ("Got fbp.x = %d, expected 3.\n", fbp.x); + failures++; + } +} + +/* + Logic is somewhat diferent for bit-fields that end a struct vs + having additional fields. +*/ + +static struct four_bits_with_char { + unsigned char x : 4; + unsigned char y; +} fbi = {1, 2}; + +static void test_four_bits_with_char (void) +{ + if (sizeof (struct four_bits_with_char) != 2) { + printf ("Got sizeof (struct four_bits_with_char) = %zu, expected 2.\n", + sizeof (struct four_bits_with_char)); + failures++; + } + + if (fbi.x != 1) { + printf ("Got fbi.x = %u, expected 1.\n", fbi.x); + failures++; + } + + if (fbi.y != 2) { + printf ("Got fbi.y = %u, expected 2.\n", fbi.y); + failures++; + } + + fbi.x = 3; + fbi.y = 17; + + if (fbi.x != 3) { + printf ("Got fbi.x = %u, expected 3.\n", fbi.x); + failures++; + } + + if (fbi.y != 17) { + printf ("Got fbi.y = %u, expected 17.\n", fbi.y); + failures++; + } +} + +static struct two_chars { + unsigned char x : 4; + unsigned char y : 4; +} o = {11, 7}; + +/* Tests that bit-fields can share allocation units. */ +static void test_two_chars (void) +{ + if (sizeof (struct two_chars) != 1) { + printf ("Got sizeof (struct two_chars) = %zu, expected 1.\n", + sizeof (struct two_chars)); + failures++; + } + + if (o.x != 11) { + printf ("Got o.x = %u, expected 11.\n", o.x); + failures++; + } + + if (o.y != 7) { + printf ("Got o.y = %u, expected 7.\n", o.y); + failures++; + } + + o.x = 3; + o.y = 4; + + if (o.x != 3) { + printf ("Got o.x = %u, expected 3.\n", o.x); + failures++; + } + + if (o.y != 4) { + printf ("Got o.y = %u, expected 4.\n", o.y); + failures++; + } +} + +static struct full_width { + unsigned char x : 8; +} fw = {255}; + +static void test_full_width (void) +{ + if (sizeof (struct full_width) != 1) { + printf ("Got sizeof (struct full_width) = %zu, expected 1.\n", + sizeof (struct full_width)); + failures++; + } + + if (fw.x != 255) { + printf ("Got fw.x = %u, expected 255.\n", fw.x); + failures++; + } + + fw.x = 42; + + if (fw.x != 42) { + printf ("Got fw.x = %u, expected 42.\n", fw.x); + failures++; + } +} + +static struct aligned_end { + unsigned char : 2; + unsigned char x : 6; + unsigned char : 3; + unsigned char y : 5; +} ae = {63, 17}; + +static void test_aligned_end (void) +{ + if (sizeof (struct aligned_end) != 2) { + printf ("Got sizeof (struct aligned_end) = %zu, expected 2.\n", + sizeof (struct aligned_end)); + failures++; + } + + if (ae.x != 63) { + printf ("Got ae.x = %u, expected 63.\n", ae.x); + failures++; + } + + if (ae.y != 17) { + printf ("Got ae.y = %u, expected 17.\n", ae.y); + failures++; + } + + ae.x = 42; + ae.y = 15; + + if (ae.x != 42) { + printf ("Got ae.x = %u, expected 42.\n", ae.x); + failures++; + } + + if (ae.y != 15) { + printf ("Got ae.y = %u, expected 15.\n", ae.y); + failures++; + } +} + +struct { signed char x : 1; } sc = {-1}; +struct { unsigned char x : 1; } uc = {1}; +struct { char x : 1; } pc = {1}; + +static void test_signedness (void) +{ + if (sc.x != -1) { + printf ("Got sc.x = %d, expected -1.\n", sc.x); + failures++; + } + + if (uc.x != 1) { + printf ("Got uc.x = %u, expected 1.\n", uc.x); + failures++; + } + + if (pc.x != 1) { + printf ("Got pc.x = %u, expected 1.\n", pc.x); + failures++; + } +} + +int main (void) +{ + test_four_bits (); + test_four_bits_with_char (); + test_two_chars (); + test_full_width (); + test_aligned_end (); + test_signedness (); + printf ("failures: %u\n", failures); + return failures; +} diff --git a/test/val/enum-bitfield.c b/test/val/enum-bitfield.c index 62e05f71f..a942091c2 100644 --- a/test/val/enum-bitfield.c +++ b/test/val/enum-bitfield.c @@ -149,10 +149,117 @@ static void test_enum_bitfield_int(void) } } +/* Enum with underlying type unsigned char. */ +enum e7uc { + E7UC_100 = 100, +}; + +static struct enum_bitfield_uchar { + enum e7uc x : 1; + enum e7uc y : 4; + enum e7uc z : 8; +} e7ucbf = {0, 10, E7UC_100}; + +static void test_enum_bitfield_uchar(void) +{ + if (sizeof (struct enum_bitfield_uchar) != 2) { + printf ("Got sizeof(struct enum_bitfield_uchar) = %zu, expected 2.\n", + sizeof(struct enum_bitfield_uchar)); + failures++; + } + + if (e7ucbf.x != 0) { + printf ("Got e7ucbf.x = %u, expected 0.\n", e7ucbf.x); + failures++; + } + if (e7ucbf.y != 10) { + printf ("Got e7ucbf.y = %u, expected 10.\n", e7ucbf.y); + failures++; + } + if (e7ucbf.z != 100) { + printf ("Got e7ucbf.z = %u, expected 100.\n", e7ucbf.z); + failures++; + } + + e7ucbf.x = -1; /* Will store 1. */ + e7ucbf.y = -1; /* Will store 15. */ + e7ucbf.z = 127; + + /* Both signed char and unsigned char are converted to int in arithmetic expressions, + ** so we write this test differently to enum_bitfield_int. + */ + if (e7ucbf.x != 1) { + printf ("Got e7ucbf.x = %u, expected 1.\n", e7ucbf.x); + failures++; + } + + if (e7ucbf.y != 15) { + printf ("Got e7ucbf.y = %u, expected 15.\n", e7ucbf.y); + failures++; + } + if (e7ucbf.z != 127) { + printf ("Got e7ucbf.z = %u, expected 127.\n", e7ucbf.z); + failures++; + } +} + +/* Enum with underlying type signed char. */ +enum e8sc { + E8SC_M1 = -1, + E8SC_100 = 100, +}; + +static struct enum_bitfield_char { + enum e8sc x : 1; + enum e8sc y : 4; + enum e8sc z : 8; +} e8scbf = {0, 5, E8SC_100}; + +static void test_enum_bitfield_char(void) +{ + if (sizeof (struct enum_bitfield_char) != 2) { + printf ("Got sizeof(struct enum_bitfield_char) = %zu, expected 2.\n", + sizeof(struct enum_bitfield_char)); + failures++; + } + + if (e8scbf.x != 0) { + printf ("Got e8scbf.x = %d, expected 0.\n", e8scbf.x); + failures++; + } + if (e8scbf.y != 5) { + printf ("Got e8scbf.y = %d, expected 10.\n", e8scbf.y); + failures++; + } + if (e8scbf.z != 100) { + printf ("Got e8scbf.z = %d, expected 100.\n", e8scbf.z); + failures++; + } + + e8scbf.x = -1; + e8scbf.y = -3; + e8scbf.z = 127; + + if (e8scbf.x != -1) { + printf ("Got e8scbf.x = %d, expected -1.\n", e8scbf.x); + failures++; + } + if (e8scbf.y != -3) { + printf ("Got e8scbf.y = %d, expected -3.\n", e8scbf.y); + failures++; + } + if (e8scbf.z != 127) { + printf ("Got e8scbf.z = %d, expected 127.\n", e8scbf.z); + failures++; + } +} + int main(void) { test_enum_bitfield_uint(); test_enum_bitfield_int(); + test_enum_bitfield_uchar(); + test_enum_bitfield_char(); printf("failures: %u\n", failures); return failures; }